Slic3r-1.3.0/ 0000775 0000000 0000000 00000000000 13274424355 0012663 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/.github/ 0000775 0000000 0000000 00000000000 13274424355 0014223 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/.github/CONTRIBUTING.md 0000664 0000000 0000000 00000010023 13274424355 0016450 0 ustar 00root root 0000000 0000000 Did you encounter an issue with using Slic3r? Fear not! This guide will help you to write a good bug report in just a few, simple steps.
There is a good chance that the issue, you have encountered, is already reported. Please check the [list of reported issues](https://github.com/alexrj/Slic3r/issues) before creating a new issue report. If you find an existing issue report, feel free to add further information to that report.
If you are reporting an issue relating to a release version of Slic3r, it would help a lot if you could also confirm that the behavior is still present in the newest build [(windows)](https://bintray.com/lordofhyphens/Slic3r/slic3r_dev/). Otherwise your issue will be closed as soon as someone else isn't able to reproduce it on current master.
When possible, please include the following information when [reporting an issue](https://github.com/alexrj/Slic3r/issues/new):
* Slic3r version (See the about dialog for the version number. If running from git, please include the git commit ID from `git rev-parse HEAD` also.)
* Operating system type + version
* Steps to reproduce the issue, including:
* Command line parameters used, if any
* Slic3r configuration file (Use ``Export Config...`` from the ``File`` menu - please don't export a bundle)
* Expected result
* Actual result
* Any error messages
* If the issue is related to G-code generation, please include the following:
* STL, OBJ or AMF input file (please make sure the input file is not broken, e.g. non-manifold, before reporting a bug)
* a screenshot of the G-code layer with the issue (e.g. using [Pronterface](https://github.com/kliment/Printrun) or preferably the internal preview tab in Slic3r).
* If the issue is a request for a new feature, be ready to explain why you think it's needed.
* Doing more prepatory work on your end makes it more likely it'll get done. This includes the "how" it can be done in addition to the "what".
* Define the "What" as strictly as you can. Consider what might happen with different infills than simple rectilinear.
Please make sure only to include one issue per report. If you encounter multiple, unrelated issues, please report them as such.
Simon Tatham has written an excellent on article on [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) which is well worth reading, although it is not specific to Slic3r.
Do you want to help fix issues in or add features to Slic3r? That's also very, very welcome :)
* A good place to start if you can is to look over the [Pull Request or Bust](https://github.com/alexrj/Slic3r/milestones/Pull%20Request%20or%20Bust) milestone. This contains all of the things (mostly new feature requests) that there isn't time or resources to address at this time.
* Things that are probably fixable via scripts (usually marked as such) have the lowest bar to getting something that works, as you don't need to recompile Slic3r to test.
* If you're starting on an issue, please say something in the related issues thread so that someone else doesn't start working on it too.
* If there's nothing in the [Pull Request or Bust](https://github.com/alexrj/Slic3r/milestones/Pull%20Request%20or%20Bust) milestone that interests you, the next place to look is for issues that don't have a milestone. Lots of people commit ideas to Slic3r, and it's difficult to keep up and sort through them.
* Before sending a pull request, please make sure that the changes you are submitting are contained in their own git branch, as PRs merge histories.
* Pull requests that contain unrelated changes will be rejected.
* A common workflow is to fork the master branch, create your new branch and do your work there. git-rebase and git-cherry-pick are also helpful for separating out unrelated changes.
* If you are pushing Slic3r code changes that touch the main application, it is very much appreciated if you write some tests that check the functionality of the code. It's much easier to vet and merge in code that includes tests and doing so will likely speed things up.
Slic3r-1.3.0/.github/ISSUE_TEMPLATE.md 0000664 0000000 0000000 00000002062 13274424355 0016730 0 ustar 00root root 0000000 0000000 ### Version
_Version of Slic3r used goes here_
_Use `About->About Slic3r` for release versions._
_Do not report Prusa3D Slic3r bugs here without confirming it is a problem on a development release of Slic3r, or your issue will be closed. *Only* use normal Slic3r version IDs._
_For -dev versions, use `git describe --tag` or get the hash value for the version you downloaded or `git rev-parse HEAD`_
### Operating system type + version
_What OS are you using, and state any version #s_
### Behavior
* _Describe the problem_
* _Steps needed to reproduce the problem_
* _If this is a command-line slicing issue, include the options used_
* _Expected Results_
* _Actual Results_
* _Screenshots from __*Slic3r*__ preview are preferred_
_Is this a new feature request?_
Related guides for writing feature requests: http://meta.stackexchange.com/a/259196 http://nickohrn.com/2013/09/write-great-feature-request-bug-report/
#### STL/Config (.ZIP) where problem occurs
_Upload a zipped copy of an STL and your config (`File -> Export Config`)_
Slic3r-1.3.0/.gitignore 0000664 0000000 0000000 00000000353 13274424355 0014654 0 ustar 00root root 0000000 0000000 build
Build
Build.bat
MYMETA.json
MYMETA.yml
_build
blib
xs/buildtmp
*.o
MANIFEST.bak
xs/MANIFEST.bak
xs/assertlib*
.init_bundle.ini
local-lib
package/osx/Slic3r*.app
*.dmg
*.swp
*.swo
CMakeFiles
*.orig
*.a
core
CMakeCache.txt
*.cmake
Slic3r-1.3.0/.travis.yml 0000664 0000000 0000000 00000003011 13274424355 0014767 0 ustar 00root root 0000000 0000000 language: perl
before_install:
- sh package/linux/travis-decrypt-key
install:
- export BOOST_DIR=$HOME/boost_1_63_0
- export SLIC3R_STATIC=1
- export CXX=g++-4.9
- export CC=g++-4.9
- source $HOME/perl5/perlbrew/etc/bashrc
script:
- bash package/linux/travis-setup.sh
- perlbrew switch slic3r-perl
- perl ./Build.PL
after_success:
- eval $(perl -Mlocal::lib=$TRAVIS_BUILD_DIR/local-lib)
- LD_LIBRARY_PATH=$WXDIR/lib package/linux/make_archive.sh linux-x64
- 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
- xsgui
cache:
apt: true
directories:
- "$HOME/cache"
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.9
- gcc-4.9
- libgtk2.0-0
- libgtk2.0-dev
- freeglut3
ssh_known_hosts: dl.slic3r.org
notifications:
irc:
channels:
- chat.freenode.net#slic3r
on_success: change
on_failure: always
use_notice: true
dist: trusty
env:
matrix:
global:
- secure: eEVRZNMv7FM6jrOU9iAFkDhWxFQ1WtHBEaObImcvtFUxy6vWSt3ehFFeTRouj3uHQAnbvUzziDyvPPm8/95alv5g/du8ML6YzzqKBKfazM0xQ7SF6R2DQL8lfFIp+RSV7T02byEP1f1g7Zva7xH9szIlDcSfU0pXW4KWbkBFMd8=
- secure: gj338h+qHGccTD/VQFmEJkqdg2McIe2pO0iZ4Ae9BvY5vxkIML4BpoYZQXQTqiAOETnUjlcknY9lx0hI/PfkDD9MSJc5BC/3fMYRCu3SgAclEwklWf9vvtodUeT69mtnZuw1zze1nTbExuOw2mepbqFjxKKMl+9l5oCz4O54fXU=
Slic3r-1.3.0/Build.PL 0000664 0000000 0000000 00000012665 13274424355 0014171 0 ustar 00root root 0000000 0000000 #!/usr/bin/perl
use strict;
use warnings;
use Config;
use File::Spec;
my %prereqs = qw(
Devel::CheckLib 0
Encode 0
Encode::Locale 1.05
ExtUtils::CppGuess 0
ExtUtils::MakeMaker 6.80
ExtUtils::ParseXS 3.35
File::Basename 0
File::Spec 0
Getopt::Long 0
Module::Build::WithXSpp 0.14
Moo 1.003001
POSIX 0
Scalar::Util 0
Test::More 0
Thread::Semaphore 0
IO::Scalar 0
threads 1.96
Time::HiRes 0
Unicode::Normalize 0
);
my %recommends = qw(
Class::XSAccessor 0
Test::Harness 0
Thread::Queue 0
threads::shared 0
);
my $sudo = grep { $_ eq '--sudo' } @ARGV;
my $gui = grep { $_ eq '--gui' } @ARGV;
my $xs_only = grep { $_ eq '--xs' } @ARGV;
if ($gui) {
%prereqs = qw(
Class::Accessor 0
Wx 0.9918
Socket 2.016
);
%recommends = qw(
Growl::GNTP 0.15
Wx::GLCanvas 0
OpenGL 0
LWP::UserAgent 0
Net::Bonjour 0
);
if ($^O eq 'MSWin32') {
$recommends{"Win32::TieRegistry"} = 0;
# we need an up-to-date Win32::API because older aren't thread-safe (GH #2517)
$prereqs{'Win32::API'} = 0.79;
}
} elsif ($xs_only) {
%prereqs = %recommends = ();
}
my @missing_prereqs = ();
if ($ENV{SLIC3R_NO_AUTO}) {
foreach my $module (sort keys %prereqs) {
my $version = $prereqs{$module};
next if eval "use $module $version; 1";
push @missing_prereqs, $module if exists $prereqs{$module};
print "Missing prerequisite $module $version\n";
}
foreach my $module (sort keys %recommends) {
my $version = $recommends{$module};
next if eval "use $module $version; 1";
print "Missing optional $module $version\n";
}
} else {
my @try = (
$ENV{CPANM} // (),
File::Spec->catfile($Config{sitebin}, 'cpanm'),
File::Spec->catfile($Config{installscript}, 'cpanm'),
);
my $cpanm;
foreach my $path (@try) {
if (-e $path) { # don't use -x because it fails on Windows
$cpanm = $path;
last;
}
}
if (!$cpanm) {
if ($^O =~ /^(?:darwin|linux)$/ && system(qw(which cpanm)) == 0) {
$cpanm = 'cpanm';
}
}
die <<'EOF'
cpanm was not found. Please install it before running this script.
There are several ways to install cpanm, try one of these:
apt-get install cpanminus
curl -L http://cpanmin.us | perl - --sudo App::cpanminus
cpan App::cpanminus
If it is installed in a non-standard location you can do:
CPANM=/path/to/cpanm perl Build.PL
EOF
if !$cpanm;
my @cpanm_args = ();
push @cpanm_args, "--sudo" if $sudo;
# install local::lib without --local-lib otherwise it's not usable afterwards
if (!eval "use local::lib qw(local-lib); 1") {
my $res = system $cpanm, @cpanm_args, 'local::lib';
warn "Warning: local::lib is required. You might need to run the `cpanm --sudo local::lib` command in order to install it.\n"
if $res != 0;
}
push @cpanm_args, ('--local-lib', 'local-lib');
# make sure our cpanm is updated (old ones don't support the ~ syntax)
system $cpanm, @cpanm_args, 'App::cpanminus';
my %modules = (%prereqs, %recommends);
foreach my $module (sort keys %modules) {
my $version = $modules{$module};
my @cmd = ($cpanm, @cpanm_args);
# temporary workaround for upstream bug in test
push @cmd, '--notest'
if $module =~ /^(?:OpenGL|Math::PlanePath|Test::Harness|IO::Scalar)$/;
push @cmd, "$module~$version";
my $res = system @cmd;
if ($res != 0) {
if (exists $prereqs{$module}) {
push @missing_prereqs, $module;
} else {
printf "Don't worry, this module is optional.\n";
}
}
}
if (!$gui) {
# clean xs directory before reinstalling, to make sure Build is called
# with current perl binary
if (-e './xs/Build') {
if ($^O eq 'MSWin32') {
system '.\xs\Build', 'distclean';
} else {
system './xs/Build', 'distclean';
}
}
my $res = system $cpanm, @cpanm_args, '--reinstall', '--verbose', './xs';
if ($res != 0) {
die "The XS/C++ code failed to compile, aborting\n";
}
}
}
if (@missing_prereqs) {
printf "The following prerequisites failed to install: %s\n", join(', ', @missing_prereqs);
exit 1;
} elsif (!$gui) {
eval "use App::Prove; 1" or die "Failed to load App::Prove";
my $res = App::Prove->new->run ? 0 : 1;
if ($res == 0) {
print "If you also want to use the GUI you can now run `perl Build.PL --gui` to install the required modules.\n";
} else {
print "Some tests failed. Please report the failure to the author!\n";
}
exit $res;
}
__END__
Slic3r-1.3.0/LICENSE 0000664 0000000 0000000 00000103330 13274424355 0013670 0 ustar 00root root 0000000 0000000 GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
.
Slic3r-1.3.0/README.md 0000664 0000000 0000000 00000062565 13274424355 0014160 0 ustar 00root root 0000000 0000000  Slic3r [](https://travis-ci.org/slic3r/Slic3r) [](https://ci.appveyor.com/project/lordofhyphens/slic3r) [](http://osx-build.slic3r.org:8080/job/Slic3r)
======
We have automated builds for Windows (64-bit) and OSX (>= 10.7). [Get a fresh build now](http://dl.slic3r.org/dev/) and stay up-to-date with the development!
The MacOS X build server is kindly sponsored by:
### So, what's this Slic3r?
Slic3r is mainly a **toolpath generator** for 3D printers: it reads 3D models (STL, OBJ, AMF, 3MF) and it converts them into **G-code** instructions for 3D printers. But it does much more than that, see the [features list](#features) below.
Slic3r was born in **2011** within the RepRap community and thanks to its high configurability became the swiss-army knife for 3D printing. It served as a platform for experimenting several **new ideas that later became technology standards**, such as multiple extruders, brim, variable-height layers, per-object settings, modifiers, post-processing scripts, G-code macros and more. Despite being based on volunteer efforts, Slic3r is still pushing the boundaries of 3D printing.
Slic3r is:
* **Open:** it is totally **open source** and it's **independent from any commercial company** or printer manufacturer. We want to keep 3D printing open and free.
* **Compatible:** it supports all the known G-code dialects (Marlin, Repetier, Mach3, LinuxCNC, Machinekit, Smoothie, Makerware, Sailfish).
* **Advanced:** many configuration options allow for fine-tuning and full control. While novice users often need just few options, Slic3r is mostly used by advanced users.
* **Community-driven:** new features or issues are discussed in the [GitHub repository](https://github.com/alexrj/Slic3r/issues). Join our collaborative effort and help improve it!
* **Robust:** the codebase includes more than 1,000 unit and regression tests, collected in 6 years of development.
* **Modular:** the core of Slic3r is libslic3r, a C++ library that provides a granular API and reusable components.
* **Embeddable:** a complete and powerful command line interface allows to use Slic3r from the shell or to integrate it in server-side applications.
* **Powerful:** see the list below!
See the [project homepage](http://slic3r.org/) at slic3r.org for more information.
### Features
(Most of these are also available in the command line interface.)
* **G-code generation** for FFF/FDM printers;
* **conversion** between STL, OBJ, AMF, 3MF and POV formats;
* **auto-repair** of non-manifold meshes (and ability to re-export them);
* **SVG export** of slices;
* built-in **USB/serial** host controller, supporting **multiple simultaneous printers** each one with a spool queue;
* **OctoPrint integration** (send to printer);
* built-in **projector and host for DLP printers**;
* tool for **cutting meshes** in multiple solid parts with visual preview (also in batch using a grid);
* tool for **extruding 2.5D TIN meshes**.
### What language is it written in?
The core parts of Slic3r are written in C++11, with multithreading. The graphical interface is still mostly written in Perl, but we're gradually porting it to C++ (want to help?). The goal is to port everything to C++.
### How to install?
You can download a precompiled package from [slic3r.org](http://slic3r.org/) (releases) or from [dl.slicr3r.org](http://dl.slic3r.org/dev/) (automated builds).
If you want to compile the source yourself follow the instructions on one of these wiki pages:
* [Linux](https://github.com/alexrj/Slic3r/wiki/Running-Slic3r-from-git-on-GNU-Linux)
* [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)
### Can I help?
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.
* Contribute to the [Manual](http://manual.slic3r.org/)! (see its [GitHub repository](https://github.com/alexrj/Slic3r-Manual))
* You can also find us in #slic3r on [FreeNode](https://webchat.freenode.net): talk to _Sound_, _LoH_ or the other members of the Slic3r community.
* Add an [issue](https://github.com/alexrj/Slic3r/issues) to the GitHub tracker if it isn't already present.
* Drop Alessandro a line at aar@cpan.org.
### Directory structure
* `Build.PL`: this script installs dependencies into `local-lib/`, compiles the C++ part and runs tests
* `lib/`: the Perl code
* `package/`: the scripts used for packaging the executables
* `slic3r.pl`: the main executable script, launching the GUI and providing the CLI
* `src/`: the C++ source of the `slic3r` executable the and CMake definition file for compiling it (note that this C++ `slic3r` executable can do many things but can't generate G-code yet because the porting isn't finished yet - the main executable is `slic3r.pl`)
* `t/`: the test suite
* `utils/`: various useful scripts
* `xs/src/libslic3r/`: C++ sources for libslic3r
* `xs/src/slic3r/`: C++ sources for the Slic3r GUI application
* `xs/t/`: test suite for libslic3r
* `xs/xsp/`: bindings for calling libslic3r from Perl (XS)
### Acknowledgements
The main author of Slic3r is Alessandro Ranellucci (@alexrj, *Sound* in IRC, [@alranel](http://twitter.com/alranel) on Twitter), who started the project in 2011 and still leads development.
Joseph Lenox (@lordofhyphens, *Loh* in IRC) is the current co-maintainer.
Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake, Kliment Yanev and numerous others. Original manual by Gary Hodgson. Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James, stl and gcode file icons designed by Akira Yasuda.
### How can I invoke Slic3r using the command line?
Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ...
--help Output this usage screen and exit
--version Output the version of Slic3r and exit
--save Save configuration to the specified file
--load Load configuration from the specified file. It can be used
more than once to load options from multiple files.
-o, --output File to output gcode to (by default, the file will be saved
into the same directory as the input file using the
--output-filename-format to generate the filename.) If a
directory is specified for this option, the output will
be saved under that directory, and the filename will be
generated by --output-filename-format.
Non-slicing actions (no G-code will be generated):
--repair Repair given STL files and save them as _fixed.obj
--cut Cut given input files at given Z (relative) and export
them as _upper.stl and _lower.stl
--split Split the shells contained in given STL file into several STL files
--info Output information about the supplied file(s) and exit
-j, --threads Number of threads to use (1+, default: 2)
GUI options:
--gui Forces the GUI launch instead of command line slicing (if you
supply a model file, it will be loaded into the plater)
--no-gui Forces the command line slicing instead of gui.
This takes precedence over --gui if both are present.
--autosave Automatically export current configuration to the specified file
Output options:
--output-filename-format
Output file name format; all config options enclosed in brackets
will be replaced by their values, as well as [input_filename_base]
and [input_filename] (default: [input_filename_base].gcode)
--post-process Generated G-code will be processed with the supplied script;
call this more than once to process through multiple scripts.
--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)
--z-offset Additional height in mm to add to vertical coordinates
(+/-, default: 0)
--gcode-flavor The type of G-code to generate (reprap/teacup/repetier/makerware/sailfish/mach3/machinekit/smoothie/no-extrusion,
default: reprap)
--use-relative-e-distances Enable this to get relative E values (default: no)
--use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no)
--use-volumetric-e Express E in cubic millimeters and prepend M200 (default: no)
--gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported
by all firmwares)
--gcode-comments Make G-code verbose by adding comments (default: no)
--vibration-limit Limit the frequency of moves on X and Y axes (Hz, set zero to disable;
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
Change this to alter the amount of plastic extruded. There should be
very little need to change this value, which is only useful to
compensate for filament packing (default: 1)
--temperature Extrusion temperature in degree Celsius, set 0 to disable (default: 200)
--first-layer-temperature Extrusion temperature for the first layer, in degree Celsius,
set 0 to disable (default: same as --temperature)
--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)
--small-perimeter-speed
Speed of print moves for small perimeters in mm/s or % over perimeter speed
(default: 30)
--external-perimeter-speed
Speed of print moves for the external perimeter in mm/s or % over perimeter speed
(default: 70%)
--infill-speed Speed of print moves in mm/s (default: 60)
--solid-infill-speed Speed of print moves for solid surfaces in mm/s or % over infill speed
(default: 60)
--top-solid-infill-speed Speed of print moves for top surfaces in mm/s or % over solid infill speed
(default: 50)
--support-material-speed
Speed of support material print moves in mm/s (default: 60)
--support-material-interface-speed
Speed of support material interface print moves in mm/s or % over support material
speed (default: 100%)
--bridge-speed Speed of bridge print moves in mm/s (default: 60)
--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
to disable; default: 0)
--infill-acceleration
Overrides firmware's default acceleration for infill. (mm/s^2, set zero
to disable; default: 0)
--bridge-acceleration
Overrides firmware's default acceleration for bridges. (mm/s^2, set zero
to disable; default: 0)
--first-layer-acceleration
Overrides firmware's default acceleration for first layer. (mm/s^2, set zero
to disable; default: 0)
--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)
--infill-every-layers
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)
--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)
--fill-pattern Pattern to use to fill non-solid layers (default: honeycomb)
--solid-fill-pattern Pattern to use to fill solid layers (default: rectilinear)
--start-gcode Load initial G-code from the supplied file. This will overwrite
the default command (home all axes [G28]).
--end-gcode Load final G-code from the supplied file. This will overwrite
the default commands (turn off temperature [M104 S0],
home X axis [G28 X], disable motors [M84]).
--before-layer-gcode Load before-layer-change G-code from the supplied file (default: nothing).
--layer-gcode Load after-layer-change G-code from the supplied file (default: nothing).
--toolchange-gcode Load tool-change G-code from the supplied file (default: nothing).
--seam-position Position of loop starting points (random/nearest/aligned, default: aligned).
--external-perimeters-first Reverse perimeter order. (default: no)
--spiral-vase Experimental option to raise Z gradually when printing single-walled vases
(default: no)
--only-retract-when-crossing-perimeters
Disable retraction when travelling between infill paths inside the same island.
(default: no)
--solid-infill-below-area
Force solid infill when a region has a smaller area than this threshold
(mm^2, default: 70)
--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
Overhang threshold angle (range: 0-90, set 0 for automatic detection,
default: 0)
--support-material-pattern
Pattern to use for support material (default: honeycomb)
--support-material-spacing
Spacing between pattern lines (mm, default: 2.5)
--support-material-angle
Support material angle in degrees (range: 0-90, default: 0)
--support-material-contact-distance
Vertical distance between object and support material
(0+, default: 0.2)
--support-material-interface-layers
Number of perpendicular layers between support material and object (0+, default: 3)
--support-material-interface-spacing
Spacing between interface pattern lines (mm, set 0 to get a solid layer, default: 0)
--raft-layers Number of layers to raise the printed objects by (range: 0+, default: 0)
--support-material-enforce-layers
Enforce support material on the specified number of layers from bottom,
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)
--retract-restart-extra
Additional amount of filament in mm to push after
compensating retraction (default: 0)
--retract-before-travel
Only retract before travel moves of this length in mm (default: 2)
--retract-lift Lift Z by the given distance in mm when retracting (default: 0)
--retract-lift-above Only lift Z when above the specified height (default: 0)
--retract-lift-below Only lift Z when below the specified height (default: 0)
--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%)
--max-fan-speed Maximum fan speed (default: 100%)
--bridge-fan-speed Fan speed to use when bridging (default: 100%)
--fan-below-layer-time Enable fan if layer print time is below this approximate number
of seconds (default: 60)
--slowdown-below-layer-time Slow down if layer print time is below this approximate number
of seconds (default: 30)
--min-print-speed Minimum print speed (mm/s, default: 10)
--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
(default: 6)
--skirt-height Height of skirts to draw (expressed in layers, 0+, default: 1)
--min-skirt-length Generate no less than the number of loops required to consume this length
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)
--duplicate Number of items with auto-arrange (1+, default: 1)
--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.
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)
--extruder-clearance-radius Radius in mm above which extruder won't collide with anything
(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%)
--first-layer-extrusion-width
Set a different extrusion width for first layer
--perimeter-extrusion-width
Set a different extrusion width for perimeters
--external-perimeter-extrusion-width
Set a different extrusion width for external perimeters
--infill-extrusion-width
Set a different extrusion width for infill
--solid-infill-extrusion-width
Set a different extrusion width for solid infill
--top-infill-extrusion-width
Set a different extrusion width for top infill
--support-material-extrusion-width
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)
--perimeter-extruder
Extruder to use for perimeters and brim (1+, default: 1)
--infill-extruder Extruder to use for infill (1+, default: 1)
--solid-infill-extruder Extruder to use for solid infill (1+, default: 1)
--support-material-extruder
Extruder to use for support material, raft and skirt (1+, default: 1)
--support-material-interface-extruder
Extruder to use for support material interface (1+, default: 1)
--ooze-prevention Drop temperature and park extruders outside a full skirt for automatic wiping
(default: no)
--ooze-prevention Drop temperature and park extruders outside a full skirt for automatic wiping
(default: no)
--standby-temperature-delta
Temperature difference to be applied when an extruder is not active and
--ooze-prevention is enabled (default: -5)
For more information about command line usage see the relevant [manual page](http://manual.slic3r.org/advanced/command-line).
Slic3r-1.3.0/appveyor.yml 0000664 0000000 0000000 00000003013 13274424355 0015250 0 ustar 00root root 0000000 0000000 version: 1.3.0-{branch}-{build}
image: WMF 5
init:
- ps:
environment:
LDLOADLIBS: -lstdc++
SLIC3R_STATIC: 1
SLIC3R_VERSION: 1.3.0
BOOST_DIR: C:\dev\boost_1_63_0
WXDIR: C:\dev\wxwidgets
WXSHARED: SHARED=0
FORCE_WX_BUILD: 0
FORCE_BOOST_REINSTALL: 0
ENC_SECRET:
secure: QfeTOSKXz1uFCEACqFKLNw==
UPLOAD_USER:
secure: fYPwnI3p6HNR+eMRJR3JfmyNolFn+Uc0MUn2bBXp9uU=
matrix:
- ARCH: 64bit
- ARCH: 32bit
install:
- IF DEFINED ENC_SECRET nuget install secure-file -ExcludeVersion
- IF DEFINED ENC_SECRET secure-file\tools\secure-file -decrypt package/deploy/slic3r-upload.ppk.enc -secret %ENC_SECRET%
- ps: "& package/win/appveyor_preinstall.ps1"
cache:
- C:\Users\appveyor\local-lib-64bit.7z
- C:\Users\appveyor\local-lib-32bit.7z
- C:\Users\appveyor\freeglut.64bit.7z
- C:\Users\appveyor\freeglut.32bit.7z
- C:\users\appveyor\strawberry.64bit.msi
- C:\users\appveyor\strawberry.32bit.msi
- C:\Users\appveyor\winscp.zip
- C:\Users\appveyor\extra_perl.7z
- C:\Users\appveyor\wxwidgets-64bit.7z
- C:\Users\appveyor\wxwidgets-32bit.7z
- C:\Users\appveyor\boost.1.63.0.32bit.7z
- C:\Users\appveyor\boost.1.63.0.64bit.7z
build_script:
- ps: "& package/win/appveyor_buildscript.ps1"
artifacts:
- path: .\slic3r*zip
name: slic3r-dev
deploy_script:
- ps: "cd C:/projects/slic3r; & package/win/appveyor_deploy.ps1"
on_success:
- ps:
on_failure:
- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
on_finish:
- ps:
Slic3r-1.3.0/doc/ 0000775 0000000 0000000 00000000000 13274424355 0013430 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/doc/How_to_build_Slic3r.txt 0000664 0000000 0000000 00000036413 13274424355 0020035 0 ustar 00root root 0000000 0000000 How to build Slic3r on Mac OS X 10.7 Lion 64bit
---------------------------------------------
Vojtech Bubnik, 2016-04-26
1) Install Mac OS X 10.7 Lion 64 bit with X Code
------------------------------------------------
One has to build the OSX Slic3r on a real Mac, either directly on the system, or on a virtualized OSX. On Mac, two commercial solutions are available to legally virtualize MacOS on MacOS:
http://www.parallels.com/eu/products/desktop/
http://www.vmware.com/products/workstation/
Installation of a X Code on an OS X 10.7 Lion needs a bit of work. The latest X Code supported by the Lion on a Virtual Box is 4.21. The trouble is, the certificates of the X Code 4.21 installation package expired. One way to work around the certificate is to flatten the installation package by unpacking and repacking it:
pkgutil --expand Foobar.pkg foobar
pkgutil --flatten foobar barfoo.pkg
The flattened package is available here:
\\rs.prusa\Development\Slic3r-Prusa\installxcode_421_lion_fixed.pkg
This installer does not install the X Code directly. Instead, it installs another installer with a set of 47 pkg files. These files have their certificates expired as well. You will find the packages on your MacOS here:
/Applications/Install Xcode.app/Contents/Resources/Packages/
It is best to flatten them in a loop:
cd /Applications/Install\ Xcode.app/Contents/Resources/Packages/
for f in *.pkg; do
pkgutil --expand $f /tmp/$f
rm -f $f
pkgutil --flatten /tmp/$f $f
done
After that, you may finish the installation of Xcode by running
/Applications/Install\ Xcode.app
1b) Installing the Xcode on a newer system
-------------------------------------------
You will need to register as an Apple developer on
https://developer.apple.com/
log in and download and install Xcode
https://developer.apple.com/downloads/
You will likely need to download and install Xcode Command Line Tools, though the Xcode 4.1 came with the command line tools installed.
2) Prepare the development environment
--------------------------------------
Install the brew package manager:
http://brew.sh/
The brew package manager requires the git command line tool. Normally the git tool is installed as part of the Xcode command line tools.
Copy and execute a command line from the top of http://brew.sh/ . It is possible, that the invocation of git fails because of some parameters the old git does not recognize. If so, invoke the git call manually.
Compile the boost library using brew. Following line compiles a 64bit boost with both static and shared libraries.
brew install boost --universal
Install dylibbundler tool. The dylibbundler tool serves to collect dependent dynamic libraries and fix their linkage. Execute
brew install dylibbundler
3) Install perl
---------------
We don't want to distribute perl pre-installed on the Mac OS box. First, the system perl installation is not correct on some Mac OS versions, second it is not rellocatable. To compile a 64bit rellocatable perl, we use the perlbrew distribution. The perlbrew distribution installs into a user home directory and it allows switching between multiple versions of perl.
http://perlbrew.pl/
First install perlbrew
\curl -L http://install.perlbrew.pl | bash
Then compile the newest perl with the rellocatable @INC path and with multithreading enabled, execute following line:
perlbrew install --threads -Duserelocatableinc --switch perl-5.22.0
The --switch parameter switches the active perl to the currently compiled one.
Available perl versions could be listed by calling
perlbrew available
Initialize CPAN, install PAR and PAR::Packer modules
execute cpan command, from the cpan prompt, run
install App::cpanminus
install PAR
install PAR::Packer
quit
4) Download and install Slic3r
------------------------------
git clone git://github.com/alexrj/Slic3r
cd Slic3r
perl Build.PL
Now Slic3r shall be compiled. You may try to execute
perl slic3r.pl
to get a help screen, or
perl slic3r.pl some_model.stl
to have the model sliced.
5) Download and compile the GUI libraries needed to execute Slic3r in GUI mode
------------------------------------------------------------------------------
For the current Slic3r 1.2.30 code base, set the environment variable SLIC3R_STATIC to link a static version of the boost library:
export SLIC3R_STATIC=1
then execute
perl Build.PL --gui
and keep your fingers crossed. The Build.PL script downloads and compiles the WxWidgets 3.0 through a Alien::Wx PERL package. The WxWidget shared libraries will land into
~/perl5/perlbrew/perls/perl-5.22.1/lib/site_perl/5.22.1/darwin-thread-multi-2level/Alien/wxWidgets/
On Maverics, we experienced following issue compiling WxPerl:
http://wiki.bolay.net/doku.php?id=acdsn:acdsn-a:mac
Now you could run the GUI version of slic3r by calling
perl slic3r.pl --gui
If some dependency is missing, the MacOS system will let you know.
6) Packing the Slic3r
---------------------
Perl is an operating system on its own. Many modules are shared among multiple applications and it is difficult to extract a stand-alone application from a perl installation manually. Fortunately, tools are available, which automate the process to some extent. One of the tools is the PAR::Packer. The PAR::Packer tool (pp executable) is able to create a standalone executable for a perl script. The standalone executable contains a PAR archive (a zip file) bundled with a perl interpreter. When executed, the bundled executable will decompress most of the PAR archive into a temp folder. Because of that, we will use the PAR::Packer to resolve and collect the dependencies, but we will create an installer manually.
The PAR::Packer could analyze the dependencies by a statical analysis, or at a runtime. The statical analysis does not resolve the dynamically loaded modules. On the other side, the statical analysis is pessimistic, therefore it often collects unneeded packages. The dynamic analysis may miss some package, if not all branches of a code are executed. We will try to solely depend on the dynamic analysis to keep the installation size minimal. We may need to develop a protocol or an automatic UI tool to exercise as much as possible from the Slic3r GUI to pack the GUI version reliably. Once a reliable list of dependencies is collected, we may not need the PAR::Packer anymore.
To create a PAR archive of a command line slic3r, execute
pp -e -p -x slic3r.pl --xargs cube.stl -o slic3r.par
and let the slic3r slice a cube.stl to load the dynamic modules.
To create a PAR archive of a GUI slic3r, execute
pp -e -p -x slic3r.pl --xargs --gui -o slic3r.par
and exercise the slic3r gui to load all modules.
Rename the slic3r.par file to slic3r.zip and decompress. Most of the code needed to execute Slic3r is there, only the perl executable is missing for the command line slic3r, and the WxWidgets shared libraries and the liblzma shared library are missing for the GUI version.
7) Collecting the dependent shared libraries, making the link paths relative
----------------------------------------------------------------------------
We have linked Slic3r against a static boost library, therefore the command line slic3r is not dependent on any non-system shared library. The situation is different for the GUI slic3r, which links dynamically against WxWidgets and liblzma.
The trick developed by Apple to allow rellocable shared libraries is to addres a shared library relatively to the path of the executable by encoding a special token @executable_path at the start of the path. Unfortunately the libraries requried by Slic3r are compiled with absolute paths.
Once the slic3r.par archive is unpacked, one may list the native shared libraries by
find ./ -name '*.bundle'
and one may list the dependencies by running
otool -L somefile.bundle
Most of the dependencies point to system directores and these dependences are always fulfilled. Dependencies pointing to the WxWidget libraries need to be fixed. These have a form
~/perlbrew/perls/perl-5.22.1/lib/site_perl/5.22.1/darwin-thread-multi-2level/Alien/wxWidgets/osx_cocoa_3_0_2_uni/lib/libwx_*.dylib
and we need to replace them with
@executable_path/../Frameworks/libwx_*.dylib
Another dependency, which needs our attention is
/usr/local/Cellar/xz/5.2.2/lib/liblzma.5.dylib
Fortunately, a tool dylibbundler was developed to address this problem.
First install dylibbundler by calling
brew dylibbundler
For some installations, the dylibbundler tool sufficiently fixes all dependencies. Unfortunately, the WxWidgets installation is inconsistent in the versioning, therefore a certain clean-up is required. Namely, the WxWidgets libraries are compiled with the full build number in their file name. For example, the base library is built as libwx_baseu-3.0.0.2.0.dylib and a symlink is created libwx_baseu-3.0.dylib pointing to the full name. Then some of the Wx libraries link against the full name and some against the symlink, leading the dylibbundler to pack both. We solved the problem by whipping up a following script:
\\rs.prusa\Development\Slic3r-Prusa\How_to_build_on_MacOSX_Lion\fix_dependencies.sh
call
slic3r_dependencies.sh --fix
to collect the shared libraries into Content/Frameworks and to fix their linkage.
call
slic3r_dependencies.sh --show
to list dependent libraries in a sorted order. All the non-system dependencies shall start with @executable_path after the fix.
8) Packing Slic3r into a dmg image using a bunch of scripts
-----------------------------------------------------------
Instead of relying on the PAR::Packer to collect the dependencies, we have used PAR::Packer to extract the dependencies, we manually cleaned them up and created an installer script.
\\rs.prusa\Development\Slic3r-Prusa\How_to_build_on_MacOSX_Lion\Slic3r-Build-MacOS
First compile Slic3r, then call build_dmg.sh with a path to the Slic3r source tree as a parameter.
The script will collect all dependencies into Slic3r.app and it will create Slic3r.dmg.
If SLIC3R_GUI variable is defined, a GUI variant of Slic3r will be packed.
How to build on Windows
-----------------------
The prefered perl distribution on MS Windows is the Strawberry Perl 5.22.1.3 (32bit)
http://strawberryperl.com/
Let it install into c:\strawberry
You may make a copy of the distribution. We recommend to make following copies:
For a release command line only build: c:\strawberry-minimal
For a release GUI build: c:\strawberry-minimal-gui
For a development build with debugging information: c:\strawberry-debug
and to make one of them active by making a directory junction:
mklink /d c:\Strawberry c:\Strawberry-debug
Building boost:
Slic3r seems to have a trouble with the latest boost 1.60.0 on Windows. Please use 1.59.
Decompress it to
c:\dev\
otherwise it will not be found by the Build.PL on Windows. You may consider to hack xs\Build.PL with your prefered boost path.
run
bootstrap.bat mingw
b2 toolset=gcc
Install git command line
https://git-scm.com/
Download Slic3r source code
git clone git://github.com/alexrj/Slic3r.git
Run compilation
cd Slic3r
perl Build.PL
perl Build.PL --gui
With a bit of luck, you will end up with a working Slic3r including GUI.
Packing on Windows
------------------
Life is easy on Windows. PAR::Packer will help again to collect the dependencies. The basic procedure is the same as for MacOS:
To create a PAR archive of a command line slic3r, execute
pp -e -p -x slic3r.pl --xargs cube.stl -o slic3r.par
and let the slic3r slice a cube.stl to load the dynamic modules.
To create a PAR archive of a GUI slic3r, execute
pp -e -p -x slic3r.pl --xargs --gui -o slic3r.par
and exercise the slic3r gui to load all modules.
The standalone installation is then created from the PAR archive by renaming it into a zip and adding following binaries from c:\strawberry to the root of the extracted zip:
perl5.22.1.exe
perl522.dll
libgcc_s_sjlj-1.dll
libstdc++-6.dll
libwinpthread-1.dll
The GUI build requires following DLLs in addition:
libglut-0_.dll
wxbase30u_gcc_custom.dll
wxmsw30u_adv_gcc_custom.dll
wxmsw30u_core_gcc_custom.dll
wxmsw30u_gl_gcc_custom.dll
wxmsw30u_html_gcc_custom.dll
and the var directory with the icons needs to be copied to the destination directory.
To run the slic3r, move the script\slic3r.pl one level up and create a following tiny windows batch in the root of the unpacked zip:
@perl5.22.1.exe slic3r.pl %*
A windows shortcut may be created for the GUI version. Instead of the perl.exe, it is better to use the wperl.exe to start the GUI Slic3r, because it does not open a text console.
The strawberry perl is already rellocatable, which means that the perl interpreter will find the perl modules in the lib directory,
and Windows look up the missing DLLs in the directory of the executable, therefore no further rellocation effort is necessary.
Packing on Windows, a single EXE solution
-----------------------------------------
One may try to create a PAR executable for command line slic3r:
pp -M Encode::Locale -M Moo -M Thread::Semaphore -M Slic3r::XS -M Unicode::Normalize -o slic3r.exe slic3r.pl
One may as well create a PAR executable for Windows GUI:
pp -M Encode::Locale -M Moo -M Thread::Semaphore -M OpenGL -M Slic3r::XS -M Unicode::Normalize -M Wx -M Class::Accessor -M Wx::DND -M Wx::Grid -M Wx::Print -M Wx::Html -M Wx::GLCanvas -M Math::Trig -M threads -M threads::shared -M Thread::Queue -l C:\strawberry\perl\site\lib\auto\Wx\Wx.xs.dll -o -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxbase30u_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_core_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_gl_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_adv_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_html_gcc_custom.dll -o slic3r.exe slic3r.pl
Remember, that these executables will unpack into a temporary directory. The directory may be declared by setting an environment variable PAR_GLOBAL_TEMP. Otherwise the temporaries are unpacked into
C:\Users\xxx\AppData\Local\Temp\par-xxxxxx
Debugging the perl, debugging on Windows
----------------------------------------
It is possible to debug perl using the integrated debugger. The EPIC plugin for Eclipse works very well with an older eclipse-SDK-3.6.2. There is a catch though: The Perl debugger does not work correctly with multiple threads running under the Perl interpreter. If that happens, the EPIC plugin gets confused and the debugger stops working. The same happens with the Komodo IDE.
Debugging the C++ code works fine using the latest Eclipse for C++ and the gdb of MinGW. The gdb packed with the Strawberry distribution does not contain the Python support, so pretty printing of the stl containers only works if another gdb build is used. The one of the QT installation works well.
It is yet a bit more complicated. The Strawberry MINGW is compiled for a different C++ exception passing model (SJLJ) than the other MINGWs, so one cannot simply combine MINGWs on Windows. For an unknown reason the nice debugger of the QT Creator hangs when debugging the C++ compiled by the Strawberry MINGW. Mabe it is because of the different exception passing models.
And to disable optimization of the C/C++ code, one has to manually modify Config_heavy.pl in the Perl central installation. The SLIC3R_DEBUG environment variable did not override all the -O2 and -O3 flags that the perl build adds the gcc execution line.
Slic3r-1.3.0/lib/ 0000775 0000000 0000000 00000000000 13274424355 0013431 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/lib/Slic3r.pm 0000664 0000000 0000000 00000027506 13274424355 0015140 0 ustar 00root root 0000000 0000000 # This package loads all the non-GUI Slic3r perl packages.
# In addition, it implements utility functions for file handling and threading.
package Slic3r;
# Copyright holder: Alessandro Ranellucci
# This application is licensed under the GNU Affero General Public License, version 3
use strict;
use warnings;
require v5.10;
our $VERSION = VERSION();
our $debug = 0;
sub debugf {
printf @_ if $debug;
}
# load threads before Moo as required by it
our $have_threads;
BEGIN {
# Test, whether the perl was compiled with ithreads support and ithreads actually work.
use Config;
$have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1";
warn "threads.pm >= 1.96 is required, please update\n" if $have_threads && $threads::VERSION < 1.96;
### temporarily disable threads if using the broken Moo version
use Moo;
$have_threads = 0 if $Moo::VERSION == 1.003000;
# Disable multi threading completely by an environment value.
# This is useful for debugging as the Perl debugger does not work
# in multi-threaded context at all.
# A good interactive perl debugger is the ActiveState Komodo IDE
# or the EPIC http://www.epic-ide.org/
$have_threads = 0 if (defined($ENV{'SLIC3R_SINGLETHREADED'}) && $ENV{'SLIC3R_SINGLETHREADED'} == 1)
}
warn "Running Slic3r under Perl 5.16 is neither supported nor recommended\n"
if $^V == v5.16;
use FindBin;
# Path to the images.
my $varpath = decode_path($FindBin::Bin) . "/var";
if ($^O eq 'darwin' && !-d $varpath) {
$varpath = decode_path($FindBin::Bin) . "/../Resources/var";
}
our $var = sub { "$varpath/$_[0]" };
use Moo 1.003001;
use Slic3r::XS; # import all symbols (constants etc.) before they get parsed
use Slic3r::Config;
use Slic3r::ExPolygon;
use Slic3r::ExtrusionLoop;
use Slic3r::ExtrusionPath;
use Slic3r::Flow;
use Slic3r::GCode::ArcFitting;
use Slic3r::GCode::MotionPlanner;
use Slic3r::GCode::PressureRegulator;
use Slic3r::GCode::Reader;
use Slic3r::GCode::VibrationLimit;
use Slic3r::Geometry qw(PI);
use Slic3r::Geometry::Clipper;
use Slic3r::Layer;
use Slic3r::Line;
use Slic3r::Model;
use Slic3r::Point;
use Slic3r::Polygon;
use Slic3r::Polyline;
use Slic3r::Print;
use Slic3r::Print::GCode;
use Slic3r::Print::Object;
use Slic3r::Print::Simple;
use Slic3r::Print::SupportMaterial;
use Slic3r::Surface;
our $build = eval "use Slic3r::Build; 1";
use Thread::Semaphore;
use Encode::Locale 1.05;
use Encode;
use Unicode::Normalize;
# Scaling between the float and integer coordinates.
# Floats are in mm.
use constant SCALING_FACTOR => 0.000001;
# Resolution to simplify perimeters to. These constants are now used in C++ code only. Better to publish them to Perl from the C++ code.
# use constant RESOLUTION => 0.0125;
# use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR;
use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15;
# use constant INFILL_OVERLAP_OVER_SPACING => 0.3;
# Keep track of threads we created. Each thread keeps its own list of threads it spwaned.
my @my_threads = ();
my @threads : shared = ();
my $pause_sema = Thread::Semaphore->new;
my $parallel_sema;
my $paused = 0;
sub spawn_thread {
my ($cb) = @_;
my $parent_tid = threads->tid;
lock @threads;
# Set up a default handler for preventing crashes in case signals are received before
# thread sets its handlers.
$SIG{'STOP'} = sub {};
@_ = ();
my $thread = threads->create(sub {
@my_threads = ();
local $SIG{'KILL'} = sub {
Slic3r::debugf "Exiting thread %d...\n", threads->tid;
$parallel_sema->up if $parallel_sema;
kill_all_threads();
Slic3r::thread_cleanup();
threads->exit();
};
local $SIG{'STOP'} = sub {
$pause_sema->down;
$pause_sema->up;
};
Slic3r::debugf "Starting thread %d (parent: %d)...\n", threads->tid, $parent_tid;
$cb->();
});
push @my_threads, $thread->tid;
push @threads, $thread->tid;
return $thread;
}
# If the threading is enabled, spawn a set of threads.
# Otherwise run the task on the current thread.
# Used for
# Slic3r::Print::Object->layers->make_perimeters
# Slic3r::Print::Object->layers->make_fill
# Slic3r::Print::SupportMaterial::generate_toolpaths
sub parallelize {
my %params = @_;
lock @threads;
if (!$params{disable} && $Slic3r::have_threads && $params{threads} > 1) {
my @items = (ref $params{items} eq 'CODE') ? $params{items}->() : @{$params{items}};
my $q = Thread::Queue->new;
$q->enqueue(@items, (map undef, 1..$params{threads}));
$parallel_sema = Thread::Semaphore->new(-$params{threads});
$parallel_sema->up;
my $thread_cb = sub {
# execute thread callback
$params{thread_cb}->($q);
# signal the parent thread that we're done
$parallel_sema->up;
# cleanup before terminating thread
Slic3r::thread_cleanup();
# This explicit exit avoids an untrappable
# "Attempt to free unreferenced scalar" error
# triggered on Ubuntu 12.04 32-bit when we're running
# from the Wx plater and
# we're reusing the same plater object more than once.
# The downside to using this exit is that we can't return
# any value to the main thread but we're not doing that
# anymore anyway.
threads->exit;
};
@_ = ();
my @my_threads = map spawn_thread($thread_cb), 1..$params{threads};
# We use a semaphore instead of $th->join because joined threads are
# not listed by threads->list or threads->object anymore, thus can't
# be signalled.
$parallel_sema->down;
$_->detach for @my_threads;
} else {
$params{no_threads_cb}->();
}
}
# call this at the very end of each thread (except the main one)
# so that it does not try to free existing objects.
# at that stage, existing objects are only those that we
# inherited at the thread creation (thus shared) and those
# that we are returning: destruction will be handled by the
# main thread in both cases.
# reminder: do not destroy inherited objects in other threads,
# as the main thread will still try to destroy them when they
# go out of scope; in other words, if you're undef()'ing an
# object in a thread, make sure the main thread still holds a
# reference so that it won't be destroyed in thread.
sub thread_cleanup {
return if !$Slic3r::have_threads;
if (threads->tid == 0) {
warn "Calling thread_cleanup() from main thread\n";
return;
}
# prevent destruction of shared objects
no warnings 'redefine';
*Slic3r::BridgeDetector::DESTROY = sub {};
*Slic3r::Config::DESTROY = sub {};
*Slic3r::Config::Full::DESTROY = sub {};
*Slic3r::Config::GCode::DESTROY = sub {};
*Slic3r::Config::Print::DESTROY = sub {};
*Slic3r::Config::PrintObject::DESTROY = sub {};
*Slic3r::Config::PrintRegion::DESTROY = sub {};
*Slic3r::Config::Static::DESTROY = sub {};
*Slic3r::ExPolygon::DESTROY = sub {};
*Slic3r::ExPolygon::Collection::DESTROY = sub {};
*Slic3r::Extruder::DESTROY = sub {};
*Slic3r::ExtrusionLoop::DESTROY = sub {};
*Slic3r::ExtrusionPath::DESTROY = sub {};
*Slic3r::ExtrusionPath::Collection::DESTROY = sub {};
*Slic3r::Filler::DESTROY = sub {};
*Slic3r::Flow::DESTROY = sub {};
*Slic3r::GCode::DESTROY = sub {};
*Slic3r::GCode::AvoidCrossingPerimeters::DESTROY = sub {};
*Slic3r::GCode::OozePrevention::DESTROY = sub {};
*Slic3r::GCode::PlaceholderParser::DESTROY = sub {};
*Slic3r::GCode::Sender::DESTROY = sub {};
*Slic3r::GCode::Wipe::DESTROY = sub {};
*Slic3r::GCode::Writer::DESTROY = sub {};
*Slic3r::Geometry::BoundingBox::DESTROY = sub {};
*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 {};
*Slic3r::Model::Object::DESTROY = sub {};
*Slic3r::Point::DESTROY = sub {};
*Slic3r::Pointf::DESTROY = sub {};
*Slic3r::Pointf3::DESTROY = sub {};
*Slic3r::Polygon::DESTROY = sub {};
*Slic3r::Polyline::DESTROY = sub {};
*Slic3r::Polyline::Collection::DESTROY = sub {};
*Slic3r::Print::DESTROY = sub {};
*Slic3r::Print::Object::DESTROY = sub {};
*Slic3r::Print::Region::DESTROY = sub {};
*Slic3r::SLAPrint::DESTROY = sub {};
*Slic3r::Surface::DESTROY = sub {};
*Slic3r::Surface::Collection::DESTROY = sub {};
*Slic3r::TriangleMesh::DESTROY = sub {};
return undef; # this prevents a "Scalars leaked" warning
}
sub get_running_threads {
return grep defined($_), map threads->object($_), @_;
}
sub kill_all_threads {
# if we're the main thread, we send SIGKILL to all the running threads
if (threads->tid == 0) {
lock @threads;
foreach my $thread (get_running_threads(@threads)) {
Slic3r::debugf "Thread %d killing %d...\n", threads->tid, $thread->tid;
$thread->kill('KILL');
}
# unlock semaphore before we block on wait
# otherwise we'd get a deadlock if threads were paused
resume_all_threads();
}
# in any thread we wait for our children
foreach my $thread (get_running_threads(@my_threads)) {
Slic3r::debugf " Thread %d waiting for %d...\n", threads->tid, $thread->tid;
$thread->join; # block until threads are killed
Slic3r::debugf " Thread %d finished waiting for %d...\n", threads->tid, $thread->tid;
}
@my_threads = ();
}
sub pause_all_threads {
return if $paused;
lock @threads;
$paused = 1;
$pause_sema->down;
$_->kill('STOP') for get_running_threads(@threads);
}
sub resume_all_threads {
return unless $paused;
lock @threads;
$paused = 0;
$pause_sema->up;
}
# Convert a Unicode path to a file system locale.
# The encoding is (from Encode::Locale POD):
# Alias | Windows | Mac OS X | POSIX
# locale_fs | ANSI | UTF-8 | nl_langinfo
# where nl_langinfo is en-US.UTF-8 on a modern Linux as well.
# So this conversion seems to make the most sense on Windows.
sub encode_path {
my ($path) = @_;
return undef if !defined $path;
$path = Unicode::Normalize::NFC($path);
$path = Encode::encode(locale_fs => $path);
return $path;
}
# Convert a path coded by a file system locale to Unicode.
sub decode_path {
my ($path) = @_;
return undef if !defined $path;
$path = Encode::decode(locale_fs => $path)
unless utf8::is_utf8($path);
# The filesystem might force a normalization form (like HFS+ does) so
# if we rely on the filename being comparable after the open() + readdir()
# roundtrip (like when creating and then selecting a preset), we need to
# restore our normalization form.
$path = Unicode::Normalize::NFC($path);
return $path;
}
# Open a file by converting $filename to local file system locales.
sub open {
my ($fh, $mode, $filename) = @_;
return CORE::open $$fh, $mode, encode_path($filename);
}
# this package declaration prevents an ugly fatal warning to be emitted when
# spawning a new thread
package GLUquadricObjPtr;
1;
Slic3r-1.3.0/lib/Slic3r/ 0000775 0000000 0000000 00000000000 13274424355 0014570 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/lib/Slic3r/Config.pm 0000664 0000000 0000000 00000030541 13274424355 0016336 0 ustar 00root root 0000000 0000000 # Extends C++ class Slic3r::DynamicPrintConfig
# This perl class does not keep any perl class variables,
# all the storage is handled by the underlying C++ code.
package Slic3r::Config;
use strict;
use warnings;
use utf8;
use List::Util qw(first max);
# C++ Slic3r::PrintConfigDef exported as a Perl hash of hashes.
# The C++ counterpart is a constant singleton.
our $Options = print_config_def();
# overwrite the hard-coded readonly value (this information is not available in XS)
$Options->{threads}{readonly} = !$Slic3r::have_threads;
# generate accessors
{
no strict 'refs';
for my $opt_key (keys %$Options) {
*{$opt_key} = sub { $_[0]->get($opt_key) };
}
}
# Fill in the underlying C++ Slic3r::DynamicPrintConfig with the content of the defaults
# provided by the C++ class Slic3r::FullPrintConfig.
sub new_from_defaults {
my $class = shift;
my (@opt_keys) = @_;
my $self = $class->new;
if (@opt_keys) {
$self->set($_, $Options->{$_}{default})
for grep exists $Options->{$_}{default}, @opt_keys;
} else {
$self->apply_static(Slic3r::Config::Full->new);
}
return $self;
}
# From command line parameters
sub new_from_cli {
my $class = shift;
my %args = @_;
# Delete hash keys with undefined value.
delete $args{$_} for grep !defined $args{$_}, keys %args;
# Replace the start_gcode, end_gcode ... hash values
# with the content of the files they reference.
for (qw(start end layer toolchange)) {
my $opt_key = "${_}_gcode";
if ($args{$opt_key}) {
if (-e $args{$opt_key}) {
Slic3r::open(\my $fh, "<", $args{$opt_key})
or die "Failed to open $args{$opt_key}\n";
binmode $fh, ':utf8';
$args{$opt_key} = do { local $/; <$fh> };
close $fh;
}
}
}
my $self = $class->new;
foreach my $opt_key (keys %args) {
my $opt_def = $Options->{$opt_key};
# we use set_deserialize() for bool options since GetOpt::Long doesn't handle
# arrays of boolean values
if ($opt_key =~ /^(?:bed_shape|duplicate_grid|extruder_offset)$/ || $opt_def->{type} eq 'bool') {
$self->set_deserialize($opt_key, $args{$opt_key});
} elsif (my $shortcut = $opt_def->{shortcut}) {
$self->set($_, $args{$opt_key}) for @$shortcut;
} else {
$self->set($opt_key, $args{$opt_key});
}
}
return $self;
}
sub merge {
my $class = shift;
my $config = $class->new;
$config->apply($_) for @_;
return $config;
}
# Load a flat ini file without a category into the underlying C++ Slic3r::DynamicConfig class,
# convert legacy configuration names.
sub load {
my $class = shift;
my ($file) = @_;
# legacy syntax of load()
my $config = $class->new;
$config->_load($file);
return $config;
}
sub save {
my $self = shift;
my ($file) = @_;
return $self->_save($file);
}
# Deserialize a perl hash into the underlying C++ Slic3r::DynamicConfig class,
# convert legacy configuration names.
sub load_ini_hash {
my $class = shift;
my ($ini_hash) = @_;
my $config = $class->new;
$config->set_deserialize($_, $ini_hash->{$_}) for keys %$ini_hash;
return $config;
}
sub clone {
my $self = shift;
my $new = (ref $self)->new;
$new->apply($self);
return $new;
}
sub get_value {
my $self = shift;
my ($opt_key) = @_;
return $Options->{$opt_key}{ratio_over}
? $self->get_abs_value($opt_key)
: $self->get($opt_key);
}
# Create a hash of hashes from the underlying C++ Slic3r::DynamicPrintConfig.
# The first hash key is '_' meaning no category.
sub as_ini {
my ($self) = @_;
my $ini = { _ => {} };
foreach my $opt_key (sort @{$self->get_keys}) {
next if $Options->{$opt_key}{shortcut};
$ini->{_}{$opt_key} = $self->serialize($opt_key);
}
return $ini;
}
# this method is idempotent by design and only applies to ::DynamicConfig or ::Full
# objects because it performs cross checks
sub validate {
my $self = shift;
# -j, --threads
die "Invalid value for --threads\n"
if $self->threads < 1;
# --layer-height
die "Invalid value for --layer-height\n"
if $self->layer_height <= 0;
die "--layer-height must be a multiple of print resolution\n"
if $self->layer_height / &Slic3r::SCALING_FACTOR % 1 != 0;
# --first-layer-height
die "Invalid value for --first-layer-height\n"
if $self->first_layer_height !~ /^(?:\d*(?:\.\d+)?)%?$/;
die "Invalid value for --first-layer-height\n"
if $self->get_value('first_layer_height') <= 0;
# --filament-diameter
die "Invalid value for --filament-diameter\n"
if grep $_ < 1, @{$self->filament_diameter};
# --nozzle-diameter
die "Invalid value for --nozzle-diameter\n"
if grep $_ < 0, @{$self->nozzle_diameter};
# --perimeters
die "Invalid value for --perimeters\n"
if $self->perimeters < 0;
# --solid-layers
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;
# --gcode-flavor
die "Invalid value for --gcode-flavor\n"
if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}};
die "--use-firmware-retraction is only supported by Marlin, Smoothie, Repetier and Machinekit firmware\n"
if $self->use_firmware_retraction && $self->gcode_flavor ne 'smoothie'
&& $self->gcode_flavor ne 'reprap'
&& $self->gcode_flavor ne 'machinekit'
&& $self->gcode_flavor ne 'repetier';
die "--use-firmware-retraction is not compatible with --wipe\n"
if $self->use_firmware_retraction && first {$_} @{$self->wipe};
# --fill-pattern
die "Invalid value for --fill-pattern\n"
if !first { $_ eq $self->fill_pattern } @{$Options->{fill_pattern}{values}};
# --external-fill-pattern
die "Invalid value for --top-infill-pattern\n"
if !first { $_ eq $self->top_infill_pattern } @{$Options->{top_infill_pattern}{values}};
die "Invalid value for --bottom-infill-pattern\n"
if !first { $_ eq $self->bottom_infill_pattern } @{$Options->{bottom_infill_pattern}{values}};
# --fill-density
die "The selected fill pattern is not supposed to work at 100% density\n"
if $self->fill_density == 100
&& !first { $_ eq $self->fill_pattern } @{$Options->{external_fill_pattern}{values}};
# --infill-every-layers
die "Invalid value for --infill-every-layers\n"
if $self->infill_every_layers !~ /^\d+$/ || $self->infill_every_layers < 1;
# --skirt-height
die "Invalid value for --skirt-height\n"
if $self->skirt_height < -1; # -1 means as tall as the object
# --bridge-flow-ratio
die "Invalid value for --bridge-flow-ratio\n"
if $self->bridge_flow_ratio <= 0;
# extruder clearance
die "Invalid value for --extruder-clearance-radius\n"
if $self->extruder_clearance_radius <= 0;
die "Invalid value for --extruder-clearance-height\n"
if $self->extruder_clearance_height <= 0;
# --extrusion-multiplier
die "Invalid value for --extrusion-multiplier\n"
if defined first { $_ <= 0 } @{$self->extrusion_multiplier};
# --default-acceleration
die "Invalid zero value for --default-acceleration when using other acceleration settings\n"
if ($self->perimeter_acceleration || $self->infill_acceleration || $self->bridge_acceleration || $self->first_layer_acceleration)
&& !$self->default_acceleration;
# --spiral-vase
if ($self->spiral_vase) {
# Note that we might want to have more than one perimeter on the bottom
# solid layers.
die "Can't make more than one perimeter when spiral vase mode is enabled\n"
if $self->perimeters > 1;
die "Can't make less than one perimeter when spiral vase mode is enabled\n"
if $self->perimeters < 1;
die "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0\n"
if $self->fill_density > 0;
die "Spiral vase mode is not compatible with top solid layers\n"
if $self->top_solid_layers > 0;
die "Spiral vase mode is not compatible with support material\n"
if $self->support_material || $self->support_material_enforce_layers > 0;
}
# extrusion widths
{
my $max_nozzle_diameter = max(@{ $self->nozzle_diameter });
die "Invalid extrusion width (too large)\n"
if defined first { $_ > 10 * $max_nozzle_diameter }
map $self->get_abs_value_over("${_}_extrusion_width", $max_nozzle_diameter),
qw(perimeter infill solid_infill top_infill support_material first_layer);
}
# general validation, quick and dirty
foreach my $opt_key (@{$self->get_keys}) {
my $opt = $Options->{$opt_key};
next unless defined $self->$opt_key;
next unless defined $opt->{cli} && $opt->{cli} =~ /=(.+)$/;
my $type = $1;
my @values = ();
if ($type =~ s/\@$//) {
die "Invalid value for $opt_key\n" if ref($self->$opt_key) ne 'ARRAY';
@values = @{ $self->$opt_key };
} else {
@values = ($self->$opt_key);
}
foreach my $value (@values) {
if ($type eq 'i' || $type eq 'f' || $opt->{type} eq 'percent') {
$value =~ s/%$// if $opt->{type} eq 'percent';
die "Invalid value for $opt_key\n"
if ($type eq 'i' && $value !~ /^-?\d+$/)
|| (($type eq 'f' || $opt->{type} eq 'percent') && $value !~ /^-?(?:\d+|\d*\.\d+)$/)
|| (defined $opt->{min} && $value < $opt->{min})
|| (defined $opt->{max} && $value > $opt->{max});
} elsif ($type eq 's' && $opt->{type} eq 'select') {
die "Invalid value for $opt_key\n"
unless first { $_ eq $value } @{ $opt->{values} };
}
}
}
return 1;
}
# CLASS METHODS:
# Write a "Windows" style ini file with categories enclosed in squre brackets.
sub write_ini {
my $class = shift;
my ($file, $ini) = @_;
Slic3r::open(\my $fh, '>', $file);
binmode $fh, ':utf8';
my $localtime = localtime;
printf $fh "# generated by Slic3r $Slic3r::VERSION on %s\n", "$localtime";
# make sure the _ category is the first one written
foreach my $category (sort { ($a eq '_') ? -1 : ($a cmp $b) } keys %$ini) {
printf $fh "\n[%s]\n", $category if $category ne '_';
foreach my $key (sort keys %{$ini->{$category}}) {
printf $fh "%s = %s\n", $key, $ini->{$category}{$key};
}
}
close $fh;
}
# Parse a "Windows" style ini file with categories enclosed in squre brackets.
# Returns a hash of hashes over strings.
# {category}{name}=value
# Non-categorized entries are stored under a category '_'.
sub read_ini {
my $class = shift;
my ($file) = @_;
local $/ = "\n";
Slic3r::open(\my $fh, '<', $file)
or die "Unable to open $file: $!\n";
binmode $fh, ':utf8';
my $ini = { _ => {} };
my $category = '_';
while (<$fh>) {
s/\R+$//;
next if /^\s+/;
next if /^$/;
next if /^\s*#/;
if (/^\[(.+?)\]$/) {
$category = $1;
next;
}
/^(\w+) *= *(.*)/ or die "Unreadable configuration file (invalid data at line $.)\n";
$ini->{$category}{$1} = $2;
}
close $fh;
return $ini;
}
package Slic3r::Config::Static;
use parent 'Slic3r::Config';
sub Slic3r::Config::GCode::new { Slic3r::Config::Static::new_GCodeConfig }
sub Slic3r::Config::Print::new { Slic3r::Config::Static::new_PrintConfig }
sub Slic3r::Config::PrintObject::new { Slic3r::Config::Static::new_PrintObjectConfig }
sub Slic3r::Config::PrintRegion::new { Slic3r::Config::Static::new_PrintRegionConfig }
sub Slic3r::Config::Full::new { Slic3r::Config::Static::new_FullPrintConfig }
sub Slic3r::Config::SLAPrint::new { Slic3r::Config::Static::new_SLAPrintConfig }
1;
Slic3r-1.3.0/lib/Slic3r/ExPolygon.pm 0000664 0000000 0000000 00000001776 13274424355 0017065 0 ustar 00root root 0000000 0000000 package Slic3r::ExPolygon;
use strict;
use warnings;
# an ExPolygon is a polygon with holes
use List::Util qw(first);
use Slic3r::Geometry qw(X Y A B point_in_polygon epsilon scaled_epsilon);
use Slic3r::Geometry::Clipper qw(union_ex diff_pl);
sub wkt {
my $self = shift;
return sprintf "POLYGON(%s)",
join ',', map "($_)", map { join ',', map "$_->[0] $_->[1]", @$_ } @$self;
}
sub dump_perl {
my $self = shift;
return sprintf "[%s]",
join ',', map "[$_]", map { join ',', map "[$_->[0],$_->[1]]", @$_ } @$self;
}
sub offset {
my $self = shift;
return Slic3r::Geometry::Clipper::offset(\@$self, @_);
}
sub offset_ex {
my $self = shift;
return Slic3r::Geometry::Clipper::offset_ex(\@$self, @_);
}
sub bounding_box {
my $self = shift;
return $self->contour->bounding_box;
}
package Slic3r::ExPolygon::Collection;
use Slic3r::Geometry qw(X1 Y1);
sub size {
my $self = shift;
return [ Slic3r::Geometry::size_2D([ map @$_, map @$_, @$self ]) ];
}
1;
Slic3r-1.3.0/lib/Slic3r/ExtrusionLoop.pm 0000664 0000000 0000000 00000000350 13274424355 0017756 0 ustar 00root root 0000000 0000000 package Slic3r::ExtrusionLoop;
use strict;
use warnings;
use parent qw(Exporter);
our @EXPORT_OK = qw(EXTRL_ROLE_DEFAULT
EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER EXTRL_ROLE_SKIRT);
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
1;
Slic3r-1.3.0/lib/Slic3r/ExtrusionPath.pm 0000664 0000000 0000000 00000000656 13274424355 0017752 0 ustar 00root root 0000000 0000000 package Slic3r::ExtrusionPath;
use strict;
use warnings;
use parent qw(Exporter);
our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER
EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_GAPFILL EXTR_ROLE_BRIDGE
EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE
EXTR_ROLE_NONE);
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
1;
Slic3r-1.3.0/lib/Slic3r/Flow.pm 0000664 0000000 0000000 00000000521 13274424355 0016033 0 ustar 00root root 0000000 0000000 package Slic3r::Flow;
use strict;
use warnings;
use parent qw(Exporter);
our @EXPORT_OK = qw(FLOW_ROLE_EXTERNAL_PERIMETER FLOW_ROLE_PERIMETER FLOW_ROLE_INFILL
FLOW_ROLE_SOLID_INFILL
FLOW_ROLE_TOP_SOLID_INFILL FLOW_ROLE_SUPPORT_MATERIAL
FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE);
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
1;
Slic3r-1.3.0/lib/Slic3r/GCode/ 0000775 0000000 0000000 00000000000 13274424355 0015551 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/lib/Slic3r/GCode/ArcFitting.pm 0000664 0000000 0000000 00000020563 13274424355 0020147 0 ustar 00root root 0000000 0000000 package Slic3r::GCode::ArcFitting;
use Moo;
use Slic3r::Geometry qw(X Y PI scale unscale epsilon scaled_epsilon deg2rad angle3points);
extends 'Slic3r::GCode::Reader';
has 'config' => (is => 'ro', required => 0);
has 'min_segments' => (is => 'rw', default => sub { 2 });
has 'min_total_angle' => (is => 'rw', default => sub { deg2rad(30) });
has 'max_relative_angle' => (is => 'rw', default => sub { deg2rad(15) });
has 'len_epsilon' => (is => 'rw', default => sub { scale 0.2 });
has 'angle_epsilon' => (is => 'rw', default => sub { abs(deg2rad(10)) });
has '_extrusion_axis' => (is => 'lazy');
has '_path' => (is => 'rw');
has '_cur_F' => (is => 'rw');
has '_cur_E' => (is => 'rw');
has '_cur_E0' => (is => 'rw');
has '_comment' => (is => 'rw');
sub _build__extrusion_axis {
my ($self) = @_;
return $self->config ? $self->config->get_extrusion_axis : 'E';
}
sub process {
my $self = shift;
my ($gcode) = @_;
die "Arc fitting is not available (incomplete feature)\n";
die "Arc fitting doesn't support extrusion axis not being E\n" if $self->_extrusion_axis ne 'E';
my $new_gcode = "";
$self->parse($gcode, sub {
my ($reader, $cmd, $args, $info) = @_;
if ($info->{extruding} && $info->{dist_XY} > 0) {
# this is an extrusion segment
#Â get segment
my $line = Slic3r::Line->new(
Slic3r::Point->new_scale($self->X, $self->Y),
Slic3r::Point->new_scale($args->{X}, $args->{Y}),
);
# get segment speed
my $F = $args->{F} // $reader->F;
# get extrusion per unscaled distance unit
my $e = $info->{dist_E} / unscale($line->length);
if ($self->_path && $F == $self->_cur_F && abs($e - $self->_cur_E) < epsilon) {
# if speed and extrusion per unit are the same as the previous segments,
# append this segment to path
$self->_path->append($line->b);
} elsif ($self->_path) {
# segment can't be appended to previous path, so we flush the previous one
# and start over
$new_gcode .= $self->path_to_gcode;
$self->_path(undef);
}
if (!$self->_path) {
# if this is the first segment of a path, start it from scratch
$self->_path(Slic3r::Polyline->new(@$line));
$self->_cur_F($F);
$self->_cur_E($e);
$self->_cur_E0($self->E);
$self->_comment($info->{comment});
}
} else {
# if we have a path, we flush it and go on
$new_gcode .= $self->path_to_gcode if $self->_path;
$new_gcode .= $info->{raw} . "\n";
$self->_path(undef);
}
});
$new_gcode .= $self->path_to_gcode if $self->_path;
return $new_gcode;
}
sub path_to_gcode {
my ($self) = @_;
my @chunks = $self->detect_arcs($self->_path);
my $gcode = "";
my $E = $self->_cur_E0;
foreach my $chunk (@chunks) {
if ($chunk->isa('Slic3r::Polyline')) {
my @lines = @{$chunk->lines};
$gcode .= sprintf "G1 F%s\n", $self->_cur_F;
foreach my $line (@lines) {
$E += $self->_cur_E * unscale($line->length);
$gcode .= sprintf "G1 X%.3f Y%.3f %s%.5f",
(map unscale($_), @{$line->b}),
$self->_extrusion_axis, $E;
$gcode .= sprintf " ; %s", $self->_comment if $self->_comment;
$gcode .= "\n";
}
} elsif ($chunk->isa('Slic3r::GCode::ArcFitting::Arc')) {
$gcode .= !$chunk->is_ccw ? "G2" : "G3";
$gcode .= sprintf " X%.3f Y%.3f", map unscale($_), @{$chunk->end}; # destination point
# XY distance of the center from the start position
$gcode .= sprintf " I%.3f", unscale($chunk->center->[X] - $chunk->start->[X]);
$gcode .= sprintf " J%.3f", unscale($chunk->center->[Y] - $chunk->start->[Y]);
$E += $self->_cur_E * unscale($chunk->length);
$gcode .= sprintf " %s%.5f", $self->_extrusion_axis, $E;
$gcode .= sprintf " F%s\n", $self->_cur_F;
}
}
return $gcode;
}
sub detect_arcs {
my ($self, $path) = @_;
my @chunks = ();
my @arc_points = ();
my $polyline = undef;
my $arc_start = undef;
my @points = @$path;
for (my $i = 1; $i <= $#points; ++$i) {
my $end = undef;
# we need at least three points to check whether they form an arc
if ($i < $#points) {
my $len = $points[$i-1]->distance_to($points[$i]);
my $rel_angle = PI - angle3points(@points[$i, $i-1, $i+1]);
if (abs($rel_angle) <= $self->max_relative_angle) {
for (my $j = $i+1; $j <= $#points; ++$j) {
# check whether @points[($i-1)..$j] form an arc
last if abs($points[$j-1]->distance_to($points[$j]) - $len) > $self->len_epsilon;
last if abs(PI - angle3points(@points[$j-1, $j-2, $j]) - $rel_angle) > $self->angle_epsilon;
$end = $j;
}
}
}
if (defined $end && ($end - $i + 1) >= $self->min_segments) {
my $arc = polyline_to_arc(Slic3r::Polyline->new(@points[($i-1)..$end]));
if (1||$arc->angle >= $self->min_total_angle) {
push @chunks, $arc;
#Â continue scanning after arc points
$i = $end;
next;
}
}
# if last chunk was a polyline, append to it
if (@chunks && $chunks[-1]->isa('Slic3r::Polyline')) {
$chunks[-1]->append($points[$i]);
} else {
push @chunks, Slic3r::Polyline->new(@points[($i-1)..$i]);
}
}
return @chunks;
}
sub polyline_to_arc {
my ($polyline) = @_;
my @points = @$polyline;
my $is_ccw = $points[2]->ccw(@points[0,1]) > 0;
# to find the center, we intersect the perpendicular lines
# passing by first and last vertex;
# a better method would be to draw all the perpendicular lines
# and find the centroid of the enclosed polygon, or to
# intersect multiple lines and find the centroid of the convex hull
# around the intersections
my $arc_center;
{
my $first_ray = Slic3r::Line->new(@points[0,1]);
$first_ray->rotate(PI/2 * ($is_ccw ? 1 : -1), $points[0]);
my $last_ray = Slic3r::Line->new(@points[-2,-1]);
$last_ray->rotate(PI/2 * ($is_ccw ? -1 : 1), $points[-1]);
# require non-parallel rays in order to compute an accurate center
return if abs($first_ray->atan2_ - $last_ray->atan2_) < deg2rad(30);
$arc_center = $first_ray->intersection($last_ray, 0) or return;
}
# angle measured in ccw orientation
my $abs_angle = Slic3r::Geometry::angle3points($arc_center, @points[0,-1]);
my $rel_angle = $is_ccw
? $abs_angle
: (2*PI - $abs_angle);
my $arc = Slic3r::GCode::ArcFitting::Arc->new(
start => $points[0]->clone,
end => $points[-1]->clone,
center => $arc_center,
is_ccw => $is_ccw || 0,
angle => $rel_angle,
);
if (0) {
printf "points = %d, path length = %f, arc angle = %f, arc length = %f\n",
scalar(@points),
unscale(Slic3r::Polyline->new(@points)->length),
Slic3r::Geometry::rad2deg($rel_angle),
unscale($arc->length);
}
return $arc;
}
package Slic3r::GCode::ArcFitting::Arc;
use Moo;
has 'start' => (is => 'ro', required => 1);
has 'end' => (is => 'ro', required => 1);
has 'center' => (is => 'ro', required => 1);
has 'is_ccw' => (is => 'ro', required => 1);
has 'angle' => (is => 'ro', required => 1);
sub radius {
my ($self) = @_;
return $self->start->distance_to($self->center);
}
sub length {
my ($self) = @_;
return $self->radius * $self->angle;
}
1;
Slic3r-1.3.0/lib/Slic3r/GCode/MotionPlanner.pm 0000664 0000000 0000000 00000025333 13274424355 0020702 0 ustar 00root root 0000000 0000000 package Slic3r::GCode::MotionPlanner;
use Moo;
has 'islands' => (is => 'ro', required => 1); # arrayref of ExPolygons
has 'internal' => (is => 'ro', default => sub { 1 });
has '_space' => (is => 'ro', default => sub { Slic3r::GCode::MotionPlanner::ConfigurationSpace->new });
has '_inner' => (is => 'ro', default => sub { [] }); # arrayref of ExPolygons
use List::Util qw(first max);
use Slic3r::Geometry qw(A B scale epsilon);
use Slic3r::Geometry::Clipper qw(offset offset_ex diff_ex intersection_pl);
# clearance (in mm) from the perimeters
has '_inner_margin' => (is => 'ro', default => sub { scale 1 });
has '_outer_margin' => (is => 'ro', default => sub { scale 2 });
# this factor weigths the crossing of a perimeter
# vs. the alternative path. a value of 5 means that
# a perimeter will be crossed if the alternative path
# is >= 5x the length of the straight line we could
# follow if we decided to cross the perimeter.
# a nearly-infinite value for this will only permit
# perimeter crossing when there's no alternative path.
use constant CROSSING_PENALTY => 20;
use constant POINT_DISTANCE => 10; # unscaled
# setup our configuration space
sub BUILD {
my $self = shift;
my $point_distance = scale POINT_DISTANCE;
my $nodes = $self->_space->nodes;
my $edges = $self->_space->edges;
# process individual islands
for my $i (0 .. $#{$self->islands}) {
my $expolygon = $self->islands->[$i];
# find external margin
my $outer = offset([ @$expolygon ], +$self->_outer_margin);
my @outer_points = map @{$_->equally_spaced_points($point_distance)}, @$outer;
# add outer points to graph
my $o_outer = $self->_space->add_nodes(@outer_points);
# find pairs of visible outer points and add them to the graph
for my $i (0 .. $#outer_points) {
for my $j (($i+1) .. $#outer_points) {
my ($a, $b) = ($outer_points[$i], $outer_points[$j]);
my $line = Slic3r::Polyline->new($a, $b);
# outer points are visible when their line has empty intersection with islands
my $intersection = intersection_pl(
[ $line ],
[ map @$_, @{$self->islands} ],
);
if (!@$intersection) {
$self->_space->add_edge($i+$o_outer, $j+$o_outer, $line->length);
}
}
}
if ($self->internal) {
# find internal margin
my $inner = offset_ex([ @$expolygon ], -$self->_inner_margin);
push @{ $self->_inner }, @$inner;
my @inner_points = map @{$_->equally_spaced_points($point_distance)}, map @$_, @$inner;
# add points to graph and get their offset
my $o_inner = $self->_space->add_nodes(@inner_points);
# find pairs of visible inner points and add them to the graph
for my $i (0 .. $#inner_points) {
for my $j (($i+1) .. $#inner_points) {
my ($a, $b) = ($inner_points[$i], $inner_points[$j]);
my $line = Slic3r::Line->new($a, $b);
# turn $inner into an ExPolygonCollection and use $inner->contains_line()
if (first { $_->contains_line($line) } @$inner) {
$self->_space->add_edge($i+$o_inner, $j+$o_inner, $line->length);
}
}
}
# generate the stripe around slice contours
my $contour = diff_ex(
$outer,
[ map @$_, @$inner ],
);
# find pairs of visible points in this area and add them to the graph
for my $i (0 .. $#inner_points) {
for my $j (0 .. $#outer_points) {
my ($a, $b) = ($inner_points[$i], $outer_points[$j]);
my $line = Slic3r::Line->new($a, $b);
# turn $contour into an ExPolygonCollection and use $contour->contains_line()
if (first { $_->contains_line($line) } @$contour) {
$self->_space->add_edge($i+$o_inner, $j+$o_outer, $line->length * CROSSING_PENALTY);
}
}
}
}
}
# since Perl has no infinity symbol and we don't want to overcomplicate
# the Dijkstra algorithm with string constants or -1 values
$self->_space->_infinity(10 * (max(map values %$_, values %{$self->_space->edges}) // 0));
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("space.svg",
no_arrows => 1,
expolygons => $self->islands,
lines => $self->_space->get_lines,
points => $self->_space->nodes,
);
printf "%d islands\n", scalar @{$self->islands};
eval "use Devel::Size";
print "MEMORY USAGE:\n";
printf " %-19s = %.1fMb\n", $_, Devel::Size::total_size($self->$_)/1024/1024
for qw(_space islands);
printf " %-19s = %.1fMb\n", $_, Devel::Size::total_size($self->_space->$_)/1024/1024
for qw(nodes edges);
printf " %-19s = %.1fMb\n", 'self', Devel::Size::total_size($self)/1024/1024;
exit if $self->internal;
}
}
sub shortest_path {
my $self = shift;
my ($from, $to) = @_;
return Slic3r::Polyline->new($from, $to)
if !@{$self->_space->nodes};
# create a temporary configuration space
my $space = $self->_space->clone;
# add from/to points to the temporary configuration space
my $node_from = $self->_add_point_to_space($from, $space);
my $node_to = $self->_add_point_to_space($to, $space);
# compute shortest path
my $path = $space->shortest_path($node_from, $node_to);
if (!$path->is_valid) {
Slic3r::debugf "Failed to compute shortest path.\n";
return Slic3r::Polyline->new($from, $to);
}
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("path.svg",
no_arrows => 1,
expolygons => $self->islands,
lines => $space->get_lines,
red_points => [$from, $to],
red_polylines => [$path],
);
exit;
}
return $path;
}
# returns the index of the new node
sub _add_point_to_space {
my ($self, $point, $space) = @_;
my $n = $space->add_nodes($point);
# check whether we are inside an island or outside
my $inside = defined first { $self->islands->[$_]->contains_point($point) } 0..$#{$self->islands};
# find candidates by checking visibility from $from to them
foreach my $idx (0..$#{$space->nodes}) {
my $line = Slic3r::Line->new($point, $space->nodes->[$idx]);
# if $point is inside an island, it is visible from $idx when island contains their line
# if $point is outside an island, it is visible from $idx when their line does not cross any island
if (
($inside && defined first { $_->contains_line($line) } @{$self->_inner})
|| (!$inside && !@{intersection_pl(
[ $line->as_polyline ],
[ map @$_, @{$self->islands} ],
)})
) {
# $n ($point) and $idx are visible
$space->add_edge($n, $idx, $line->length);
}
}
# if we found no visibility, retry with larger margins
if (!exists $space->edges->{$n} && $inside) {
foreach my $idx (0..$#{$space->nodes}) {
my $line = Slic3r::Line->new($point, $space->nodes->[$idx]);
if (defined first { $_->contains_line($line) } @{$self->islands}) {
# $n ($point) and $idx are visible
$space->add_edge($n, $idx, $line->length);
}
}
}
warn "Temporary node is not visible from any other node"
if !exists $space->edges->{$n};
return $n;
}
package Slic3r::GCode::MotionPlanner::ConfigurationSpace;
use Moo;
has 'nodes' => (is => 'rw', default => sub { [] }); # [ Point, ... ]
has 'edges' => (is => 'rw', default => sub { {} }); # node_idx => { node_idx => distance, ... }
has '_infinity' => (is => 'rw');
sub clone {
my $self = shift;
return (ref $self)->new(
nodes => [ map $_->clone, @{$self->nodes} ],
edges => { map { $_ => { %{$self->edges->{$_}} } } keys %{$self->edges} },
_infinity => $self->_infinity,
);
}
sub nodes_count {
my $self = shift;
return scalar(@{ $self->nodes });
}
sub add_nodes {
my ($self, @nodes) = @_;
my $offset = $self->nodes_count;
push @{ $self->nodes }, @nodes;
return $offset;
}
sub add_edge {
my ($self, $a, $b, $dist) = @_;
$self->edges->{$a}{$b} = $self->edges->{$b}{$a} = $dist;
}
sub shortest_path {
my ($self, $node_from, $node_to) = @_;
my $edges = $self->edges;
my (%dist, %visited, %prev);
$dist{$_} = $self->_infinity for keys %$edges;
$dist{$node_from} = 0;
my @queue = ($node_from);
while (@queue) {
my $u = -1;
{
# find node in @queue with smallest distance in %dist and has not been visited
my $d = -1;
foreach my $n (@queue) {
next if $visited{$n};
if ($u == -1 || $dist{$n} < $d) {
$u = $n;
$d = $dist{$n};
}
}
}
last if $u == $node_to;
# remove $u from @queue
@queue = grep $_ != $u, @queue;
$visited{$u} = 1;
# loop through neighbors of $u
foreach my $v (keys %{ $edges->{$u} }) {
my $alt = $dist{$u} + $edges->{$u}{$v};
if ($alt < $dist{$v}) {
$dist{$v} = $alt;
$prev{$v} = $u;
if (!$visited{$v}) {
push @queue, $v;
}
}
}
}
my @points = ();
{
my $u = $node_to;
while (exists $prev{$u}) {
unshift @points, $self->nodes->[$u];
$u = $prev{$u};
}
unshift @points, $self->nodes->[$node_from];
}
return Slic3r::Polyline->new(@points);
}
# for debugging purposes
sub get_lines {
my $self = shift;
my @lines = ();
my %lines = ();
for my $i (keys %{$self->edges}) {
for my $j (keys %{$self->edges->{$i}}) {
my $line_id = join '_', sort $i, $j;
next if $lines{$line_id};
$lines{$line_id} = 1;
push @lines, Slic3r::Line->new(map $self->nodes->[$_], $i, $j);
}
}
return [@lines];
}
1;
Slic3r-1.3.0/lib/Slic3r/GCode/PressureRegulator.pm 0000664 0000000 0000000 00000007614 13274424355 0021614 0 ustar 00root root 0000000 0000000 # A pure perl (no C++ implementation) G-code filter, to control the pressure inside the nozzle.
package Slic3r::GCode::PressureRegulator;
use Moo;
has 'config' => (is => 'ro', required => 1);
has 'enable' => (is => 'rw', default => sub { 0 });
has 'reader' => (is => 'ro', default => sub { Slic3r::GCode::Reader->new });
has '_extrusion_axis' => (is => 'rw', default => sub { "E" });
has '_tool' => (is => 'rw', default => sub { 0 });
has '_last_print_F' => (is => 'rw', default => sub { 0 });
has '_advance' => (is => 'rw', default => sub { 0 }); # extra E injected
use Slic3r::Geometry qw(epsilon);
# Acknowledgements:
# The advance algorithm was proposed by Matthew Roberts.
# The initial work on this Slic3r feature was done by LuÃs Andrade (lluis)
sub BUILD {
my ($self) = @_;
$self->reader->apply_print_config($self->config);
$self->_extrusion_axis($self->config->get_extrusion_axis);
}
sub process {
my $self = shift;
my ($gcode, $flush) = @_;
my $new_gcode = "";
$self->reader->parse($gcode, sub {
my ($reader, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
$self->_tool($1);
} elsif ($info->{extruding} && $info->{dist_XY} > 0) {
# This is a print move.
my $F = $args->{F} // $reader->F;
if ($F != $self->_last_print_F || ($F == $self->_last_print_F && $self->_advance == 0)) {
# We are setting a (potentially) new speed or a discharge event happend since the last speed change, so we calculate the new advance amount.
# First calculate relative flow rate (mm of filament over mm of travel)
my $rel_flow_rate = $info->{dist_E} / $info->{dist_XY};
# Then calculate absolute flow rate (mm/sec of feedstock)
my $flow_rate = $rel_flow_rate * $F / 60;
# And finally calculate advance by using the user-configured K factor.
my $new_advance = $self->config->pressure_advance * ($flow_rate**2);
if (abs($new_advance - $self->_advance) > 1E-5) {
my $new_E = ($self->config->use_relative_e_distances ? 0 : $reader->E) + ($new_advance - $self->_advance);
$new_gcode .= sprintf "G1 %s%.5f F%.3f ; pressure advance\n",
$self->_extrusion_axis, $new_E, $self->_unretract_speed;
$new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E
if !$self->config->use_relative_e_distances;
$new_gcode .= sprintf "G1 F%.3f ; restore F\n", $F;
$self->_advance($new_advance);
}
$self->_last_print_F($F);
}
} elsif (($info->{retracting} || $cmd eq 'G10') && $self->_advance != 0) {
# We need to bring pressure to zero when retracting.
$new_gcode .= $self->_discharge($args->{F}, $args->{F} // $reader->F);
}
$new_gcode .= "$info->{raw}\n";
});
if ($flush) {
$new_gcode .= $self->_discharge;
}
return $new_gcode;
}
sub _discharge {
my ($self, $F, $oldSpeed) = @_;
my $new_E = ($self->config->use_relative_e_distances ? 0 : $self->reader->E) - $self->_advance;
my $gcode = sprintf "G1 %s%.5f F%.3f ; pressure discharge\n",
$self->_extrusion_axis, $new_E, $F // $self->_unretract_speed;
$gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $self->reader->E
if !$self->config->use_relative_e_distances;
$gcode .= sprintf "G1 F%.3f ; restore F\n", $oldSpeed
if $oldSpeed;
$self->_advance(0);
return $gcode;
}
sub _unretract_speed {
my ($self) = @_;
return $self->config->get_at('retract_speed', $self->_tool) * 60;
}
1;
Slic3r-1.3.0/lib/Slic3r/GCode/Reader.pm 0000664 0000000 0000000 00000005200 13274424355 0017306 0 ustar 00root root 0000000 0000000 package Slic3r::GCode::Reader;
use Moo;
has 'config' => (is => 'ro', default => sub { Slic3r::Config::GCode->new });
has 'X' => (is => 'rw', default => sub {0});
has 'Y' => (is => 'rw', default => sub {0});
has 'Z' => (is => 'rw', default => sub {0});
has 'E' => (is => 'rw', default => sub {0});
has 'F' => (is => 'rw', default => sub {0});
has '_extrusion_axis' => (is => 'rw', default => sub {"E"});
our $Verbose = 0;
my @AXES = qw(X Y Z E);
sub apply_print_config {
my ($self, $print_config) = @_;
$self->config->apply_static($print_config);
$self->_extrusion_axis($self->config->get_extrusion_axis);
}
sub clone {
my $self = shift;
return (ref $self)->new(
map { $_ => $self->$_ } (@AXES, 'F', '_extrusion_axis', 'config'),
);
}
sub parse {
my $self = shift;
my ($gcode, $cb) = @_;
foreach my $raw_line (split /\R+/, $gcode) {
print "$raw_line\n" if $Verbose || $ENV{SLIC3R_TESTS_GCODE};
my $line = $raw_line;
$line =~ s/\s*;(.*)//; # strip comment
my %info = (comment => $1, raw => $raw_line);
# parse command
my ($command, @args) = split /\s+/, $line;
$command //= '';
my %args = map { /([A-Z])(.*)/; ($1 => $2) } @args;
# convert extrusion axis
if (exists $args{ $self->_extrusion_axis }) {
$args{E} = $args{ $self->_extrusion_axis };
}
# check motion
if ($command =~ /^G[01]$/) {
foreach my $axis (@AXES) {
if (exists $args{$axis}) {
$self->$axis(0) if $axis eq 'E' && $self->config->use_relative_e_distances;
$info{"dist_$axis"} = $args{$axis} - $self->$axis;
$info{"new_$axis"} = $args{$axis};
} else {
$info{"dist_$axis"} = 0;
$info{"new_$axis"} = $self->$axis;
}
}
$info{dist_XY} = sqrt(($info{dist_X}**2) + ($info{dist_Y}**2));
if (exists $args{E}) {
if ($info{dist_E} > 0) {
$info{extruding} = 1;
} elsif ($info{dist_E} < 0) {
$info{retracting} = 1
}
} else {
$info{travel} = 1;
}
}
# run callback
$cb->($self, $command, \%args, \%info);
# update coordinates
if ($command =~ /^(?:G[01]|G92)$/) {
for my $axis (@AXES, 'F') {
$self->$axis($args{$axis}) if exists $args{$axis};
}
}
# TODO: update temperatures
}
}
1;
Slic3r-1.3.0/lib/Slic3r/GCode/VibrationLimit.pm 0000664 0000000 0000000 00000004016 13274424355 0021044 0 ustar 00root root 0000000 0000000 package Slic3r::GCode::VibrationLimit;
use Moo;
extends 'Slic3r::GCode::Reader';
has '_min_time' => (is => 'lazy');
has '_last_dir' => (is => 'ro', default => sub { [0,0] });
has '_dir_time' => (is => 'ro', default => sub { [0,0] });
# inspired by http://hydraraptor.blogspot.it/2010/12/frequency-limit.html
use List::Util qw(max);
sub _build__min_time {
my ($self) = @_;
return 1 / ($self->config->vibration_limit * 60); # in minutes
}
sub process {
my $self = shift;
my ($gcode) = @_;
my $new_gcode = "";
$self->parse($gcode, sub {
my ($reader, $cmd, $args, $info) = @_;
if ($cmd eq 'G1' && $info->{dist_XY} > 0) {
my $point = Slic3r::Pointf->new($args->{X} // $reader->X, $args->{Y} // $reader->Y);
my @dir = (
($point->x <=> $reader->X),
($point->y <=> $reader->Y), #$
);
my $time = $info->{dist_XY} / ($args->{F} // $reader->F); # in minutes
if ($time > 0) {
my @pause = ();
foreach my $axis (0..$#dir) {
if ($dir[$axis] != 0 && $self->_last_dir->[$axis] != $dir[$axis]) {
if ($self->_last_dir->[$axis] != 0) {
# this axis is changing direction: check whether we need to pause
if ($self->_dir_time->[$axis] < $self->_min_time) {
push @pause, ($self->_min_time - $self->_dir_time->[$axis]);
}
}
$self->_last_dir->[$axis] = $dir[$axis];
$self->_dir_time->[$axis] = 0;
}
$self->_dir_time->[$axis] += $time;
}
if (@pause) {
$new_gcode .= sprintf "G4 P%d\n", max(@pause) * 60 * 1000;
}
}
}
$new_gcode .= $info->{raw} . "\n";
});
return $new_gcode;
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI.pm 0000664 0000000 0000000 00000041453 13274424355 0015561 0 ustar 00root root 0000000 0000000 package Slic3r::GUI;
use strict;
use warnings;
use utf8;
use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow
:filedialog :font);
use Wx::Event qw(EVT_MENU);
BEGIN {
# Wrap the Wx::_load_plugin() function which doesn't work with non-ASCII paths
no warnings 'redefine';
my $orig = *Wx::_load_plugin{CODE};
*Wx::_load_plugin = sub {
$_[0] = Slic3r::decode_path($_[0]);
$orig->(@_);
};
}
use File::Basename qw(basename);
use FindBin;
use List::Util qw(first any);
use Slic3r::Geometry qw(X Y);
use Slic3r::GUI::2DBed;
use Slic3r::GUI::AboutDialog;
use Slic3r::GUI::BedShapeDialog;
use Slic3r::GUI::BonjourBrowser;
use Slic3r::GUI::ConfigWizard;
use Slic3r::GUI::Controller;
use Slic3r::GUI::Controller::ManualControlDialog;
use Slic3r::GUI::Controller::PrinterPanel;
use Slic3r::GUI::MainFrame;
use Slic3r::GUI::Notifier;
use Slic3r::GUI::Plater;
use Slic3r::GUI::Plater::2D;
use Slic3r::GUI::Plater::2DToolpaths;
use Slic3r::GUI::Plater::3D;
use Slic3r::GUI::Plater::3DPreview;
use Slic3r::GUI::Plater::ObjectPartsPanel;
use Slic3r::GUI::Plater::ObjectCutDialog;
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;
use Slic3r::GUI::OptionsGroup;
use Slic3r::GUI::OptionsGroup::Field;
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";
use Wx::Event qw(EVT_IDLE EVT_COMMAND);
use base 'Wx::App';
use constant FILE_WILDCARDS => {
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 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.
our $autosave;
our $threads;
our @cb;
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
},
};
our $have_button_icons = &Wx::wxVERSION_STRING =~ / (?:2\.9\.[1-9]|3\.)/;
our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$small_font->SetPointSize(11) if &Wx::wxMAC;
our $small_bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$small_bold_font->SetPointSize(11) if &Wx::wxMAC;
$small_bold_font->SetWeight(wxFONTWEIGHT_BOLD);
our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$medium_font->SetPointSize(12);
our $grey = Wx::Colour->new(200,200,200);
# to use in ScrolledWindow::SetScrollRate(xstep, ystep)
# step related to system font point size
our $scroll_step = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)->GetPointSize;
our $VERSION_CHECK_EVENT : shared = Wx::NewEventType;
our $DLP_projection_screen;
sub OnInit {
my ($self) = @_;
$self->SetAppName('Slic3r');
Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION;
$self->{notifier} = Slic3r::GUI::Notifier->new;
$self->{presets} = { print => [], filament => [], printer => [] };
# locate or create data directory
# Unix: ~/.Slic3r
# Windows: "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
# Mac: "~/Library/Application Support/Slic3r"
$datadir ||= Slic3r::decode_path(Wx::StandardPaths::Get->GetUserDataDir);
my $enc_datadir = Slic3r::encode_path($datadir);
Slic3r::debugf "Data directory: %s\n", $datadir;
# just checking for existence of $datadir is not enough: it may be an empty directory
# supplied as argument to --datadir; in that case we should still run the wizard
my $run_wizard = (-d $enc_datadir && -e "$enc_datadir/slic3r.ini") ? 0 : 1;
foreach my $dir ($enc_datadir, "$enc_datadir/print", "$enc_datadir/filament", "$enc_datadir/printer") {
next if -d $dir;
if (!mkdir $dir) {
my $error = "Slic3r was unable to create its data directory at $dir ($!).";
warn "$error\n";
fatal_error(undef, $error);
}
}
# load settings
my $last_version;
if (-f "$enc_datadir/slic3r.ini") {
my $ini = eval { Slic3r::Config->read_ini("$datadir/slic3r.ini") };
if ($ini) {
$last_version = $ini->{_}{version};
$ini->{_}{$_} = $Settings->{_}{$_}
for grep !exists $ini->{_}{$_}, keys %{$Settings->{_}};
$Settings = $ini;
}
delete $Settings->{_}{mode}; # handle legacy
}
$Settings->{_}{version} = $Slic3r::VERSION;
$Settings->{_}{threads} = $threads if $threads;
$self->save_settings;
if (-f "$enc_datadir/simple.ini") {
# The Simple Mode settings were already automatically duplicated to presets
# named "Simple Mode" in each group, so we already support retrocompatibility.
unlink "$enc_datadir/simple.ini";
}
$self->load_presets;
# application frame
Wx::Image::AddHandler(Wx::PNGHandler->new);
$self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new;
$self->SetTopWindow($frame);
# load init bundle
{
my @dirs = ($FindBin::Bin);
if (&Wx::wxMAC) {
push @dirs, qw();
} elsif (&Wx::wxMSW) {
push @dirs, qw();
}
my $init_bundle = first { -e $_ } map "$_/.init_bundle.ini", @dirs;
if ($init_bundle) {
Slic3r::debugf "Loading config bundle from %s\n", $init_bundle;
$self->{mainframe}->load_configbundle($init_bundle, 1);
$run_wizard = 0;
}
}
if (!$run_wizard && (!defined $last_version || $last_version ne $Slic3r::VERSION)) {
# user was running another Slic3r version on this computer
if (!defined $last_version || $last_version =~ /^0\./) {
show_info($self->{mainframe}, "Hello! Support material was improved since the "
. "last version of Slic3r you used. It is strongly recommended to revert "
. "your support material settings to the factory defaults and start from "
. "those. Enjoy and provide feedback!", "Support Material");
}
if (!defined $last_version || $last_version =~ /^(?:0|1\.[01])\./) {
show_info($self->{mainframe}, "Hello! In this version a new Bed Shape option was "
. "added. If the bed coordinates in the plater preview screen look wrong, go "
. "to Print Settings and click the \"Set\" button next to \"Bed Shape\".", "Bed Shape");
}
}
$self->{mainframe}->config_wizard if $run_wizard;
$self->check_version
if $self->have_version_check
&& ($Settings->{_}{version_check} // 1)
&& (!$Settings->{_}{last_version_check} || (time - $Settings->{_}{last_version_check}) >= 86400);
EVT_IDLE($frame, sub {
while (my $cb = shift @cb) {
$cb->();
}
});
EVT_COMMAND($self, -1, $VERSION_CHECK_EVENT, sub {
my ($self, $event) = @_;
my ($success, $response, $manual_check) = @{$event->GetData};
if ($success) {
if ($response =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) {
my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?",
'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal;
Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES;
} else {
Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $manual_check;
}
$Settings->{_}{last_version_check} = time();
$self->save_settings;
} else {
Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $manual_check;
}
});
return 1;
}
sub about {
my ($self) = @_;
my $about = Slic3r::GUI::AboutDialog->new(undef);
$about->ShowModal;
$about->Destroy;
}
# static method accepting a wxWindow object as first parameter
sub catch_error {
my ($self, $cb, $message_dialog) = @_;
if (my $err = $@) {
$cb->() if $cb;
$message_dialog
? $message_dialog->($err, 'Error', wxOK | wxICON_ERROR)
: Slic3r::GUI::show_error($self, $err);
return 1;
}
return 0;
}
# static method accepting a wxWindow object as first parameter
sub show_error {
my ($parent, $message) = @_;
Wx::MessageDialog->new($parent, $message, 'Error', wxOK | wxICON_ERROR)->ShowModal;
}
# static method accepting a wxWindow object as first parameter
sub show_info {
my ($parent, $message, $title) = @_;
Wx::MessageDialog->new($parent, $message, $title || 'Notice', wxOK | wxICON_INFORMATION)->ShowModal;
}
# static method accepting a wxWindow object as first parameter
sub fatal_error {
show_error(@_);
exit 1;
}
# static method accepting a wxWindow object as first parameter
sub warning_catcher {
my ($self, $message_dialog) = @_;
return sub {
my $message = shift;
return if $message =~ /GLUquadricObjPtr|Attempt to free unreferenced scalar/;
my @params = ($message, 'Warning', wxOK | wxICON_WARNING);
$message_dialog
? $message_dialog->(@params)
: Wx::MessageDialog->new($self, @params)->ShowModal;
};
}
sub notify {
my ($self, $message) = @_;
my $frame = $self->GetTopWindow;
# try harder to attract user attention on OS X
$frame->RequestUserAttention(&Wx::wxMAC ? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO)
unless ($frame->IsActive);
$self->{notifier}->notify($message);
}
sub save_settings {
my ($self) = @_;
Slic3r::Config->write_ini("$datadir/slic3r.ini", $Settings);
}
sub presets { return $_[0]->{presets}; }
sub load_presets {
my ($self) = @_;
for my $group (qw(printer filament print)) {
my $presets = $self->{presets}{$group};
# keep external or dirty presets
@$presets = grep { ($_->external && $_->file_exists) || $_->dirty } @$presets;
my $dir = "$Slic3r::GUI::datadir/$group";
opendir my $dh, Slic3r::encode_path($dir)
or die "Failed to read directory $dir (errno: $!)\n";
foreach my $file (grep /\.ini$/i, readdir $dh) {
$file = Slic3r::decode_path($file);
my $name = basename($file);
$name =~ s/\.ini$//i;
# skip if we already have it
next if any { $_->name eq $name } @$presets;
push @$presets, Slic3r::GUI::Preset->new(
group => $group,
name => $name,
file => "$dir/$file",
);
}
closedir $dh;
@$presets = sort { $a->name cmp $b->name } @$presets;
unshift @$presets, Slic3r::GUI::Preset->new(
group => $group,
default => 1,
name => '- default -',
);
}
}
sub add_external_preset {
my ($self, $file) = @_;
my $name = basename($file); # keep .ini suffix
for my $group (qw(printer filament print)) {
my $presets = $self->{presets}{$group};
# remove any existing preset with the same name
@$presets = grep { $_->name ne $name } @$presets;
push @$presets, Slic3r::GUI::Preset->new(
group => $group,
name => $name,
file => $file,
external => 1,
);
}
return $name;
}
sub have_version_check {
my ($self) = @_;
# return an explicit 0
return ($Slic3r::have_threads && $Slic3r::VERSION !~ /-dev$/ && $have_LWP) || 0;
}
sub check_version {
my ($self, $manual_check) = @_;
Slic3r::debugf "Checking for updates...\n";
@_ = ();
threads->create(sub {
my $ua = LWP::UserAgent->new;
$ua->timeout(10);
my $response = $ua->get('http://slic3r.org/updatecheck');
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $VERSION_CHECK_EVENT,
threads::shared::shared_clone([ $response->is_success, $response->decoded_content, $manual_check ])));
Slic3r::thread_cleanup();
})->detach;
}
sub output_path {
my ($self, $dir) = @_;
return ($Settings->{_}{last_output_path} && $Settings->{_}{remember_output_path})
? $Settings->{_}{last_output_path}
: $dir;
}
sub open_model {
my ($self, $window) = @_;
my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory}
|| $Slic3r::GUI::Settings->{recent}{config_directory}
|| '';
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;
return;
}
my @input_files = map Slic3r::decode_path($_), $dialog->GetPaths;
$dialog->Destroy;
return @input_files;
}
sub CallAfter {
my ($self, $cb) = @_;
push @cb, $cb;
}
sub scan_serial_ports {
my ($self) = @_;
my @ports = ();
if ($^O eq 'MSWin32') {
# Windows
if (eval "use Win32::TieRegistry; 1") {
my $ts = Win32::TieRegistry->new("HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM",
{ Access => 'KEY_READ' });
if ($ts) {
# when no serial ports are available, the registry key doesn't exist and
# TieRegistry->new returns undef
$ts->Tie(\my %reg);
push @ports, sort values %reg;
}
}
} else {
# UNIX and OS X
push @ports, glob '/dev/{ttyUSB,ttyACM,tty.,cu.,rfcomm}*';
}
return grep !/Bluetooth|FireFly/, @ports;
}
sub append_menu_item {
my ($self, $menu, $string, $description, $cb, $id, $icon, $kind) = @_;
$id //= &Wx::NewId();
my $item = Wx::MenuItem->new($menu, $id, $string, $description // '', $kind // 0);
$self->set_menu_item_icon($item, $icon);
$menu->Append($item);
EVT_MENU($self, $id, $cb);
return $item;
}
sub append_submenu {
my ($self, $menu, $string, $description, $submenu, $id, $icon) = @_;
$id //= &Wx::NewId();
my $item = Wx::MenuItem->new($menu, $id, $string, $description // '');
$self->set_menu_item_icon($item, $icon);
$item->SetSubMenu($submenu);
$menu->Append($item);
return $item;
}
sub set_menu_item_icon {
my ($self, $menuItem, $icon) = @_;
# SetBitmap was not available on OS X before Wx 0.9927
if ($icon && $menuItem->can('SetBitmap')) {
$menuItem->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icon), wxBITMAP_TYPE_PNG));
}
}
sub save_window_pos {
my ($self, $window, $name) = @_;
$Settings->{_}{"${name}_pos"} = join ',', $window->GetScreenPositionXY;
$Settings->{_}{"${name}_size"} = join ',', $window->GetSizeWH;
$Settings->{_}{"${name}_maximized"} = $window->IsMaximized;
$self->save_settings;
}
sub restore_window_pos {
my ($self, $window, $name) = @_;
if (defined $Settings->{_}{"${name}_pos"}) {
my $size = [ split ',', $Settings->{_}{"${name}_size"}, 2 ];
$window->SetSize($size);
my $display = Wx::Display->new->GetClientArea();
my $pos = [ split ',', $Settings->{_}{"${name}_pos"}, 2 ];
if (($pos->[X] + $size->[X]/2) < $display->GetRight && ($pos->[Y] + $size->[Y]/2) < $display->GetBottom) {
$window->Move($pos);
}
$window->Maximize(1) if $Settings->{_}{"${name}_maximized"};
}
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/ 0000775 0000000 0000000 00000000000 13274424355 0015214 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/lib/Slic3r/GUI/2DBed.pm 0000664 0000000 0000000 00000017575 13274424355 0016451 0 ustar 00root root 0000000 0000000 # Bed shape dialog
package Slic3r::GUI::2DBed;
use strict;
use warnings;
use List::Util qw(min max);
use Slic3r::Geometry qw(PI X Y unscale deg2rad);
use Slic3r::Geometry::Clipper qw(intersection_pl);
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 // []);
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->Refresh; });
return $self;
}
sub _repaint {
my ($self, $event) = @_;
my $dc = Wx::AutoBufferedPaintDC->new($self);
my ($cw, $ch) = $self->GetSizeWH;
return if $cw == 0; # when canvas is not rendered yet, size is 0,0
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());
}
# turn $cw and $ch from sizes to max coordinates
$cw--;
$ch--;
my $cbb = Slic3r::Geometry::BoundingBoxf->new_from_points([
Slic3r::Pointf->new(0, 0),
Slic3r::Pointf->new($cw, $ch),
]);
# leave space for origin point
$cbb->set_x_min($cbb->x_min + 4);
$cbb->set_x_max($cbb->x_max - 4);
$cbb->set_y_max($cbb->y_max - 4);
# leave space for origin label
$cbb->set_y_max($cbb->y_max - 13);
# read new size
($cw, $ch) = @{$cbb->size};
my $ccenter = $cbb->center;
# get bounding box of bed shape in G-code coordinates
my $bed_shape = $self->bed_shape;
my $bed_polygon = Slic3r::Polygon->new_scale(@$bed_shape);
my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($bed_shape);
$bb->merge_point(Slic3r::Pointf->new(0,0)); # origin needs to be in the visible area
my ($bw, $bh) = @{$bb->size};
my $bcenter = $bb->center;
# calculate the scaling factor for fitting bed shape in canvas area
my $sfactor = min($cw/$bw, $ch/$bh);
my $shift = Slic3r::Pointf->new(
$ccenter->x - $bcenter->x * $sfactor,
$ccenter->y - $bcenter->y * $sfactor, #-
);
$self->_scale_factor($sfactor);
$self->_shift(Slic3r::Pointf->new(
$shift->x + $cbb->x_min,
$shift->y - ($cbb->y_max-$self->GetSize->GetHeight), #++
));
# draw bed fill
{
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(@BED_COLOR), wxSOLID));
$dc->DrawPolygon([ map $self->to_pixels($_), @$bed_shape ], 0, 0);
}
# draw grid
{
my $step = 10; # 1cm grid
my @polylines = ();
for (my $x = $bb->x_min - ($bb->x_min % $step) + $step; $x < $bb->x_max; $x += $step) {
push @polylines, Slic3r::Polyline->new_scale([$x, $bb->y_min], [$x, $bb->y_max]);
}
for (my $y = $bb->y_min - ($bb->y_min % $step) + $step; $y < $bb->y_max; $y += $step) {
push @polylines, Slic3r::Polyline->new_scale([$bb->x_min, $y], [$bb->x_max, $y]);
}
@polylines = @{intersection_pl(\@polylines, [$bed_polygon])};
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(@BED_GRID), 1, wxSOLID));
$dc->DrawLine(map @{$self->to_pixels([map unscale($_), @$_])}, @$_[0,-1]) for @polylines;
}
# draw bed contour
{
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(255,255,255), wxTRANSPARENT));
$dc->DrawPolygon([ map $self->to_pixels($_), @$bed_shape ], 0, 0);
}
my $origin_px = $self->to_pixels(Slic3r::Pointf->new(0,0));
# draw axes
{
my $axes_len = 50;
my $arrow_len = 6;
my $arrow_angle = deg2rad(45);
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(255,0,0), 2, wxSOLID)); # red
my $x_end = Slic3r::Pointf->new($origin_px->[X] + $axes_len, $origin_px->[Y]);
$dc->DrawLine(@$origin_px, @$x_end);
foreach my $angle (-$arrow_angle, +$arrow_angle) {
my $end = $x_end->clone;
$end->translate(-$arrow_len, 0);
$end->rotate($angle, $x_end);
$dc->DrawLine(@$x_end, @$end);
}
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,255,0), 2, wxSOLID)); # green
my $y_end = Slic3r::Pointf->new($origin_px->[X], $origin_px->[Y] - $axes_len);
$dc->DrawLine(@$origin_px, @$y_end);
foreach my $angle (-$arrow_angle, +$arrow_angle) {
my $end = $y_end->clone;
$end->translate(0, +$arrow_len);
$end->rotate($angle, $y_end);
$dc->DrawLine(@$y_end, @$end);
}
}
# draw origin
{
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(0,0,0), wxSOLID));
$dc->DrawCircle(@$origin_px, 3);
$dc->SetTextForeground(Wx::Colour->new(0,0,0));
$dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
$dc->DrawText("(0,0)", $origin_px->[X] + 1, $origin_px->[Y] + 2);
}
# draw current position
if (defined $self->pos) {
my $pos_px = $self->to_pixels($self->pos);
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(200,0,0), 2, wxSOLID));
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(200,0,0), wxTRANSPARENT));
$dc->DrawCircle(@$pos_px, 5);
$dc->DrawLine($pos_px->[X]-15, $pos_px->[Y], $pos_px->[X]+15, $pos_px->[Y]);
$dc->DrawLine($pos_px->[X], $pos_px->[Y]-15, $pos_px->[X], $pos_px->[Y]+15);
}
$self->_painted(1);
}
sub _mouse_event {
my ($self, $event) = @_;
return if !$self->interactive;
return if !$self->_painted;
my $pos = $event->GetPosition;
my $point = $self->to_units([ $pos->x, $pos->y ]); #]]
if ($event->LeftDown || $event->Dragging) {
$self->on_move->($point) if $self->on_move;
$self->Refresh;
}
}
# convert G-code coordinates into pixels
sub to_pixels {
my ($self, $point) = @_;
my $p = Slic3r::Pointf->new(@$point);
$p->scale($self->_scale_factor);
$p->translate(@{$self->_shift});
return [$p->x, $self->GetSize->GetHeight - $p->y]; #]]
}
# convert pixels into G-code coordinates
sub to_units {
my ($self, $point) = @_;
my $p = Slic3r::Pointf->new(
$point->[X],
$self->GetSize->GetHeight - $point->[Y],
);
$p->translate(@{$self->_shift->negative});
$p->scale(1/$self->_scale_factor);
return $p;
}
sub set_pos {
my ($self, $pos) = @_;
$self->pos($pos);
$self->Refresh;
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/3DScene.pm 0000664 0000000 0000000 00000161531 13274424355 0017005 0 ustar 00root root 0000000 0000000 package Slic3r::GUI::3DScene::Base;
use strict;
use warnings;
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
# must load OpenGL *before* Wx::GLCanvas
use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
use base qw(Wx::GLCanvas Class::Accessor);
use Math::Trig qw(asin);
use List::Util qw(reduce min max first);
use Slic3r::Geometry qw(X Y Z MIN MAX triangle_normal normalize deg2rad tan scale unscale scaled_epsilon);
use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl);
use Wx::GLCanvas qw(:all);
__PACKAGE__->mk_accessors( qw(_quat _dirty init
enable_picking
enable_moving
on_viewport_changed
on_hover
on_select
on_double_click
on_right_click
on_move
volumes
_sphi _stheta
cutting_plane_axis
cutting_plane_z
cut_lines_vertices
bed_shape
bed_triangles
bed_grid_lines
background
origin
_mouse_pos
_hover_volume_idx
_drag_volume_idx
_drag_start_pos
_drag_start_xy
_dragged
_camera_target
_zoom
) );
use constant TRACKBALLSIZE => 0.8;
use constant TURNTABLE_MODE => 1;
use constant GROUND_Z => -0.02;
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); GL_ARRAY_BUFFER_ARB; 1 };
# phi / theta angles to orient the camera.
use constant VIEW_TOP => [0.0,0.0];
use constant VIEW_BOTTOM => [0.0,180.0];
use constant VIEW_LEFT => [90.0,90.0];
use constant VIEW_RIGHT => [-90.0,90.0];
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
{
no warnings 'redefine';
*OpenGL::Array::CLONE_SKIP = sub { 1 };
}
sub new {
my ($class, $parent) = @_;
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 &&
defined Wx::GLCanvas->can('WX_GL_SAMPLE_BUFFERS') &&
defined Wx::GLCanvas->can('WX_GL_SAMPLES');
my $attrib = [WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24];
if ($can_multisample) {
# Request a window with multi sampled anti aliasing. This is a new feature in Wx 3.0.3 (backported from 3.1.0).
# Use eval to avoid compilation, if the subs WX_GL_SAMPLE_BUFFERS and WX_GL_SAMPLES are missing.
eval 'push(@$attrib, (WX_GL_SAMPLE_BUFFERS, 1, WX_GL_SAMPLES, 4));';
}
# wxWidgets expect the attrib list to be ended by zero.
push(@$attrib, 0);
# we request a depth buffer explicitely because it looks like it's not created by
# default on Linux, causing transparency issues
my $self = $class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "", $attrib);
if (Wx::wxVERSION >= 3.000003) {
# Wx 3.0.3 contains an ugly hack to support some advanced OpenGL attributes through the attribute list.
# The attribute list is transferred between the wxGLCanvas and wxGLContext constructors using a single static array s_wglContextAttribs.
# Immediatelly force creation of the OpenGL context to consume the static variable s_wglContextAttribs.
$self->GetContext();
}
$self->{can_multisample} = $can_multisample;
$self->background(1);
$self->_quat((0, 0, 0, 1));
$self->_stheta(45);
$self->_sphi(45);
$self->_zoom(1);
# 3D point in model space
$self->_camera_target(Slic3r::Pointf3->new(0,0,0));
$self->reset_objects;
EVT_PAINT($self, sub {
my $dc = Wx::PaintDC->new($self);
$self->Render($dc);
});
EVT_SIZE($self, sub { $self->_dirty(1) });
EVT_IDLE($self, sub {
return unless $self->_dirty;
return if !$self->IsShownOnScreen;
$self->Resize( $self->GetSizeWH );
$self->Refresh;
});
EVT_MOUSEWHEEL($self, sub {
my ($self, $e) = @_;
# Calculate the zoom delta and apply it to the current zoom factor
my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
if ($Slic3r::GUI::Settings->{_}{invert_zoom}) {
$zoom *= -1;
}
$zoom = max(min($zoom, 4), -4);
$zoom /= 10;
$self->_zoom($self->_zoom / (1-$zoom));
# In order to zoom around the mouse point we need to translate
#Â the camera target
my $size = Slic3r::Pointf->new($self->GetSizeWH);
my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #-
$self->_camera_target->translate(
# ($pos - $size/2) represents the vector from the viewport center
# to the mouse point. By multiplying it by $zoom we get the new,
# transformed, length of such vector.
# Since we want that point to stay fixed, we move our camera target
# in the opposite direction by the delta of the length of such vector
# ($zoom - 1). We then scale everything by 1/$self->_zoom since
# $self->_camera_target is expressed in terms of model units.
-($pos->x - $size->x/2) * ($zoom) / $self->_zoom,
-($pos->y - $size->y/2) * ($zoom) / $self->_zoom,
0,
) if 0;
$self->on_viewport_changed->() if $self->on_viewport_changed;
$self->_dirty(1);
$self->Refresh;
});
EVT_MOUSE_EVENTS($self, \&mouse_event);
return $self;
}
sub mouse_event {
my ($self, $e) = @_;
my $pos = Slic3r::Pointf->new($e->GetPositionXY);
if ($e->Entering && &Wx::wxMSW) {
# wxMSW needs focus in order to catch mouse wheel events
$self->SetFocus;
} elsif ($e->LeftDClick) {
$self->on_double_click->()
if $self->on_double_click;
} elsif ($e->MiddleDClick) {
if (@{$self->volumes}) {
$self->zoom_to_volumes;
} else {
$self->zoom_to_bed;
}
$self->_dirty(1);
$self->Refresh;
} elsif (($e->LeftDown || $e->RightDown) && not $e->ShiftDown) {
# If user pressed left or right button we first check whether this happened
# on a volume or not.
my $volume_idx = $self->_hover_volume_idx // -1;
# select volume in this 3D canvas
if ($self->enable_picking) {
$self->deselect_volumes;
$self->select_volume($volume_idx);
if ($volume_idx != -1) {
my $group_id = $self->volumes->[$volume_idx]->select_group_id;
my @volumes;
if ($group_id != -1) {
$self->select_volume($_)
for grep $self->volumes->[$_]->select_group_id == $group_id,
0..$#{$self->volumes};
}
}
$self->Refresh;
}
# propagate event through callback
$self->on_select->($volume_idx)
if $self->on_select;
if ($volume_idx != -1) {
if ($e->LeftDown && $self->enable_moving) {
$self->_drag_volume_idx($volume_idx);
$self->_drag_start_pos($self->mouse_to_3d(@$pos));
} elsif ($e->RightDown) {
# if right clicking on volume, propagate event through callback
$self->on_right_click->($e->GetPosition)
if $self->on_right_click;
}
}
} elsif ($e->Dragging && $e->LeftIsDown && defined($self->_drag_volume_idx)) {
# get new position at the same Z of the initial click point
my $mouse_ray = $self->mouse_ray($e->GetX, $e->GetY);
my $cur_pos = $mouse_ray->intersect_plane($self->_drag_start_pos->z);
# calculate the translation vector
my $vector = $self->_drag_start_pos->vector_to($cur_pos);
# get volume being dragged
my $volume = $self->volumes->[$self->_drag_volume_idx];
# get all volumes belonging to the same group, if any
my @volumes;
if ($volume->drag_group_id == -1) {
@volumes = ($volume);
} else {
@volumes = grep $_->drag_group_id == $volume->drag_group_id, @{$self->volumes};
}
# apply new temporary volume origin and ignore Z
$_->origin->translate($vector->x, $vector->y, 0) for @volumes; #,,
$self->_drag_start_pos($cur_pos);
$self->_dragged(1);
$self->Refresh;
} elsif ($e->Dragging) {
if ($e->AltDown) {
# Move the camera center on the Z axis based on mouse Y axis movement
if (defined $self->_drag_start_pos) {
my $orig = $self->_drag_start_pos;
$self->_camera_target->translate(0, 0, $pos->y - $orig->y);
$self->on_viewport_changed->() if $self->on_viewport_changed;
$self->Refresh;
}
$self->_drag_start_pos($pos);
} elsif ($e->LeftIsDown) {
# if dragging over blank area with left button, rotate
if (defined $self->_drag_start_pos) {
my $orig = $self->_drag_start_pos;
if (TURNTABLE_MODE) {
$self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE);
$self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #-
$self->_stheta(GIMBAL_LOCK_THETA_MAX) if $self->_stheta > GIMBAL_LOCK_THETA_MAX;
$self->_stheta(0) if $self->_stheta < 0;
} else {
my $size = $self->GetClientSize;
my @quat = trackball(
$orig->x / ($size->width / 2) - 1,
1 - $orig->y / ($size->height / 2), #/
$pos->x / ($size->width / 2) - 1,
1 - $pos->y / ($size->height / 2), #/
);
$self->_quat(mulquats($self->_quat, \@quat));
}
$self->on_viewport_changed->() if $self->on_viewport_changed;
$self->Refresh;
}
$self->_drag_start_pos($pos);
} elsif ($e->MiddleIsDown || $e->RightIsDown) {
# if dragging over blank area with right button, translate
if (defined $self->_drag_start_xy) {
# get point in model space at Z = 0
my $cur_pos = $self->mouse_ray($e->GetX, $e->GetY)->intersect_plane(0);
my $orig = $self->mouse_ray(@{$self->_drag_start_xy})->intersect_plane(0);
$self->_camera_target->translate(
@{$orig->vector_to($cur_pos)->negative},
);
$self->on_viewport_changed->() if $self->on_viewport_changed;
$self->Refresh;
}
$self->_drag_start_xy($pos);
}
} elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
if ($self->on_move && defined($self->_drag_volume_idx) && $self->_dragged) {
# get all volumes belonging to the same group, if any
my @volume_idxs;
my $group_id = $self->volumes->[$self->_drag_volume_idx]->drag_group_id;
if ($group_id == -1) {
@volume_idxs = ($self->_drag_volume_idx);
} else {
@volume_idxs = grep $self->volumes->[$_]->drag_group_id == $group_id,
0..$#{$self->volumes};
}
$self->on_move->(@volume_idxs);
}
$self->_drag_volume_idx(undef);
$self->_drag_start_pos(undef);
$self->_drag_start_xy(undef);
$self->_dragged(undef);
} elsif ($e->Moving) {
$self->_mouse_pos($pos);
$self->Refresh if $self->enable_picking;
} else {
$e->Skip();
}
}
sub reset_objects {
my ($self) = @_;
$self->volumes([]);
$self->_dirty(1);
}
sub set_viewport_from_scene {
my ($self, $scene) = @_;
$self->_sphi($scene->_sphi);
$self->_stheta($scene->_stheta);
$self->_camera_target($scene->_camera_target);
$self->_zoom($scene->_zoom);
$self->_quat($scene->_quat);
$self->_dirty(1);
}
sub zoom{
my ($self, $direction) = @_;
if( $direction eq 'in'){
$self->_zoom($self->_zoom / (1-0.3));
}
elsif($direction eq 'out'){
$self->_zoom($self->_zoom / (1+0.3));
}
$self->on_viewport_changed->() if $self->on_viewport_changed;
$self->_dirty(1);
$self->Refresh;
}
# Set the camera to a default orientation,
# zoom to volumes.
sub select_view {
my ($self, $direction) = @_;
my $dirvec;
if (ref($direction)) {
$dirvec = $direction;
} else {
if ($direction eq 'top') {
$dirvec = VIEW_TOP;
} elsif ($direction eq 'bottom') {
$dirvec = VIEW_BOTTOM;
} elsif ($direction eq 'left') {
$dirvec = VIEW_LEFT;
} elsif ($direction eq 'right') {
$dirvec = VIEW_RIGHT;
} elsif ($direction eq 'front') {
$dirvec = VIEW_FRONT;
} elsif ($direction eq 'back') {
$dirvec = VIEW_BACK;
} elsif ($direction eq 'diagonal') {
$dirvec = VIEW_DIAGONAL;
}
}
$self->_sphi($dirvec->[0]);
$self->_stheta($dirvec->[1]);
# Avoid gimbal lock.
$self->_stheta(GIMBAL_LOCK_THETA_MAX) if $self->_stheta > GIMBAL_LOCK_THETA_MAX;
$self->_stheta(0) if $self->_stheta < 0;
# View everything.
$self->volumes_bounding_box->defined
? $self->zoom_to_volumes
: $self->zoom_to_bed;
$self->on_viewport_changed->() if $self->on_viewport_changed;
$self->_dirty(1);
$self->Refresh;
}
sub zoom_to_bounding_box {
my ($self, $bb) = @_;
# calculate the zoom factor needed to adjust viewport to
# bounding box
my $max_size = max(@{$bb->size}) * 1.05;
my $min_viewport_size = min($self->GetSizeWH);
if ($max_size != 0) {
# only re-zoom if we have a valid bounding box, avoid a divide by 0 error.
$self->_zoom($min_viewport_size / $max_size);
# center view around bounding box center
$self->_camera_target($bb->center);
$self->on_viewport_changed->() if $self->on_viewport_changed;
}
}
sub zoom_to_bed {
my ($self) = @_;
if ($self->bed_shape) {
$self->zoom_to_bounding_box($self->bed_bounding_box);
}
}
sub zoom_to_volume {
my ($self, $volume_idx) = @_;
my $volume = $self->volumes->[$volume_idx];
my $bb = $volume->transformed_bounding_box;
$self->zoom_to_bounding_box($bb);
}
sub zoom_to_volumes {
my ($self) = @_;
$self->zoom_to_bounding_box($self->volumes_bounding_box);
}
sub volumes_bounding_box {
my ($self) = @_;
my $bb = Slic3r::Geometry::BoundingBoxf3->new;
$bb->merge($_->transformed_bounding_box) for @{$self->volumes};
return $bb;
}
sub bed_bounding_box {
my ($self) = @_;
my $bb = Slic3r::Geometry::BoundingBoxf3->new;
if ($self->bed_shape) {
$bb->merge_point(Slic3r::Pointf3->new(@$_, 0)) for @{$self->bed_shape};
}
return $bb;
}
sub max_bounding_box {
my ($self) = @_;
my $bb = $self->bed_bounding_box;
$bb->merge($self->volumes_bounding_box);
return $bb;
}
sub set_auto_bed_shape {
my ($self, $bed_shape) = @_;
# draw a default square bed around object center
my $max_size = max(@{ $self->volumes_bounding_box->size });
my $center = $self->volumes_bounding_box->center;
$self->set_bed_shape([
[ $center->x - $max_size, $center->y - $max_size ], #--
[ $center->x + $max_size, $center->y - $max_size ], #--
[ $center->x + $max_size, $center->y + $max_size ], #++
[ $center->x - $max_size, $center->y + $max_size ], #++
]);
$self->origin(Slic3r::Pointf->new(@$center[X,Y]));
}
sub set_bed_shape {
my ($self, $bed_shape) = @_;
$self->bed_shape($bed_shape);
# triangulate bed
my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]);
my $bed_bb = $expolygon->bounding_box;
{
my @points = ();
foreach my $triangle (@{ $expolygon->triangulate }) {
push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle; #))
}
$self->bed_triangles(OpenGL::Array->new_list(GL_FLOAT, @points));
}
{
my @polylines = ();
for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) {
push @polylines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]);
}
for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) {
push @polylines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]);
}
# clip with a slightly grown expolygon because our lines lay on the contours and
# may get erroneously clipped
my @lines = map Slic3r::Line->new(@$_[0,-1]),
@{intersection_pl(\@polylines, [ @{$expolygon->offset(+scaled_epsilon)} ])};
# append bed contours
push @lines, map @{$_->lines}, @$expolygon;
my @points = ();
foreach my $line (@lines) {
push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$line; #))
}
$self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points));
}
$self->origin(Slic3r::Pointf->new(0,0));
}
sub deselect_volumes {
my ($self) = @_;
$_->selected(0) for @{$self->volumes};
}
sub select_volume {
my ($self, $volume_idx) = @_;
$self->volumes->[$volume_idx]->selected(1)
if $volume_idx != -1;
}
sub SetCuttingPlane {
my ($self, $axis, $z, $expolygons) = @_;
$self->cutting_plane_axis($axis);
$self->cutting_plane_z($z);
# grow slices in order to display them better
$expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1);
my $bb = $self->volumes_bounding_box;
my @verts = ();
foreach my $line (map @{$_->lines}, map @$_, @$expolygons) {
if ($axis == X) {
push @verts, (
$bb->x_min + $z, unscale($line->a->x), unscale($line->a->y), #))
$bb->x_min + $z, unscale($line->b->x), unscale($line->b->y), #))
);
} elsif ($axis == Y) {
push @verts, (
unscale($line->a->y), $bb->y_min + $z, unscale($line->a->x), #))
unscale($line->b->y), $bb->y_min + $z, unscale($line->b->x), #))
);
} else {
push @verts, (
unscale($line->a->x), unscale($line->a->y), $z, #))
unscale($line->b->x), unscale($line->b->y), $z, #))
);
}
}
$self->cut_lines_vertices(OpenGL::Array->new_list(GL_FLOAT, @verts));
}
# Given an axis and angle, compute quaternion.
sub axis_to_quat {
my ($ax, $phi) = @_;
my $lena = sqrt(reduce { $a + $b } (map { $_ * $_ } @$ax));
my @q = map { $_ * (1 / $lena) } @$ax;
@q = map { $_ * sin($phi / 2.0) } @q;
$q[$#q + 1] = cos($phi / 2.0);
return @q;
}
# Project a point on the virtual trackball.
# If it is inside the sphere, map it to the sphere, if it outside map it
# to a hyperbola.
sub project_to_sphere {
my ($r, $x, $y) = @_;
my $d = sqrt($x * $x + $y * $y);
if ($d < $r * 0.70710678118654752440) { # Inside sphere
return sqrt($r * $r - $d * $d);
} else { # On hyperbola
my $t = $r / 1.41421356237309504880;
return $t * $t / $d;
}
}
sub cross {
my ($v1, $v2) = @_;
return (@$v1[1] * @$v2[2] - @$v1[2] * @$v2[1],
@$v1[2] * @$v2[0] - @$v1[0] * @$v2[2],
@$v1[0] * @$v2[1] - @$v1[1] * @$v2[0]);
}
# Simulate a track-ball. Project the points onto the virtual trackball,
# then figure out the axis of rotation, which is the cross product of
# P1 P2 and O P1 (O is the center of the ball, 0,0,0) Note: This is a
# deformed trackball-- is a trackball in the center, but is deformed
# into a hyperbolic sheet of rotation away from the center.
# It is assumed that the arguments to this routine are in the range
# (-1.0 ... 1.0).
sub trackball {
my ($p1x, $p1y, $p2x, $p2y) = @_;
if ($p1x == $p2x && $p1y == $p2y) {
# zero rotation
return (0.0, 0.0, 0.0, 1.0);
}
# First, figure out z-coordinates for projection of P1 and P2 to
# deformed sphere
my @p1 = ($p1x, $p1y, project_to_sphere(TRACKBALLSIZE, $p1x, $p1y));
my @p2 = ($p2x, $p2y, project_to_sphere(TRACKBALLSIZE, $p2x, $p2y));
# axis of rotation (cross product of P1 and P2)
my @a = cross(\@p2, \@p1);
# Figure out how much to rotate around that axis.
my @d = map { $_ * $_ } (map { $p1[$_] - $p2[$_] } 0 .. $#p1);
my $t = sqrt(reduce { $a + $b } @d) / (2.0 * TRACKBALLSIZE);
# Avoid problems with out-of-control values...
$t = 1.0 if ($t > 1.0);
$t = -1.0 if ($t < -1.0);
my $phi = 2.0 * asin($t);
return axis_to_quat(\@a, $phi);
}
# Build a rotation matrix, given a quaternion rotation.
sub quat_to_rotmatrix {
my ($q) = @_;
my @m = ();
$m[0] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[2] * @$q[2]);
$m[1] = 2.0 * (@$q[0] * @$q[1] - @$q[2] * @$q[3]);
$m[2] = 2.0 * (@$q[2] * @$q[0] + @$q[1] * @$q[3]);
$m[3] = 0.0;
$m[4] = 2.0 * (@$q[0] * @$q[1] + @$q[2] * @$q[3]);
$m[5] = 1.0 - 2.0 * (@$q[2] * @$q[2] + @$q[0] * @$q[0]);
$m[6] = 2.0 * (@$q[1] * @$q[2] - @$q[0] * @$q[3]);
$m[7] = 0.0;
$m[8] = 2.0 * (@$q[2] * @$q[0] - @$q[1] * @$q[3]);
$m[9] = 2.0 * (@$q[1] * @$q[2] + @$q[0] * @$q[3]);
$m[10] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[0] * @$q[0]);
$m[11] = 0.0;
$m[12] = 0.0;
$m[13] = 0.0;
$m[14] = 0.0;
$m[15] = 1.0;
return @m;
}
sub mulquats {
my ($q1, $rq) = @_;
return (@$q1[3] * @$rq[0] + @$q1[0] * @$rq[3] + @$q1[1] * @$rq[2] - @$q1[2] * @$rq[1],
@$q1[3] * @$rq[1] + @$q1[1] * @$rq[3] + @$q1[2] * @$rq[0] - @$q1[0] * @$rq[2],
@$q1[3] * @$rq[2] + @$q1[2] * @$rq[3] + @$q1[0] * @$rq[1] - @$q1[1] * @$rq[0],
@$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2])
}
sub mouse_to_3d {
my ($self, $x, $y, $z) = @_;
my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items
my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items
my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items
$y = $viewport[3] - $y;
$z //= glReadPixels_p($x, $y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT);
my @projected = gluUnProject_p($x, $y, $z, @mview, @proj, @viewport);
return Slic3r::Pointf3->new(@projected);
}
sub mouse_ray {
my ($self, $x, $y) = @_;
return Slic3r::Linef3->new(
$self->mouse_to_3d($x, $y, 0),
$self->mouse_to_3d($x, $y, 1),
);
}
sub GetContext {
my ($self) = @_;
if (Wx::wxVERSION >= 2.009) {
return $self->{context} ||= Wx::GLContext->new($self);
} else {
return $self->SUPER::GetContext;
}
}
sub SetCurrent {
my ($self, $context) = @_;
if (Wx::wxVERSION >= 2.009) {
return $self->SUPER::SetCurrent($context);
} else {
return $self->SUPER::SetCurrent;
}
}
sub Resize {
my ($self, $x, $y) = @_;
return unless $self->GetContext;
$self->_dirty(0);
$self->SetCurrent($self->GetContext);
glViewport(0, 0, $x, $y);
$x /= $self->_zoom;
$y /= $self->_zoom;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
my $depth = 10 * max(@{ $self->max_bounding_box->size });
glOrtho(
-$x/2, $x/2, -$y/2, $y/2,
-$depth, 2*$depth,
);
glMatrixMode(GL_MODELVIEW);
}
sub InitGL {
my $self = shift;
return if $self->init;
return unless $self->GetContext;
$self->init(1);
glClearColor(0, 0, 0, 1);
glColor3f(1, 0, 0);
glEnable(GL_DEPTH_TEST);
glClearDepth(1.0);
glDepthFunc(GL_LEQUAL);
glEnable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
# Set antialiasing/multisampling
glDisable(GL_LINE_SMOOTH);
glDisable(GL_POLYGON_SMOOTH);
glEnable(GL_MULTISAMPLE) if ($self->{can_multisample});
# ambient lighting
glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.1, 0.1, 0.1, 1);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHT1);
# light from camera
glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0);
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. Default: GL_SMOOTH
glShadeModel(GL_SMOOTH);
glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.3, 0.3, 0.3, 1);
glMaterialfv_p(GL_FRONT_AND_BACK, GL_SPECULAR, 1, 1, 1, 1);
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50);
glMaterialfv_p(GL_FRONT_AND_BACK, GL_EMISSION, 0.1, 0.1, 0.1, 0.9);
# 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) if ($self->{can_multisample});
}
sub Render {
my ($self, $dc) = @_;
# prevent calling SetCurrent() when window is not shown yet
return unless $self->IsShownOnScreen;
return unless my $context = $self->GetContext;
$self->SetCurrent($context);
$self->InitGL;
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);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
if (TURNTABLE_MODE) {
glRotatef(-$self->_stheta, 1, 0, 0); #Â pitch
glRotatef($self->_sphi, 0, 0, 1); # yaw
} else {
my @rotmat = quat_to_rotmatrix($self->quat);
glMultMatrixd_p(@rotmat[0..15]);
}
glTranslatef(@{ $self->_camera_target->negative });
# light from above
glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0);
glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1);
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();
glFinish();
if (my $pos = $self->_mouse_pos) {
my $col = [ glReadPixels_p($pos->x, $self->GetSize->GetHeight - $pos->y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE) ];
my $volume_idx = $col->[0] + $col->[1]*256 + $col->[2]*256*256;
$self->_hover_volume_idx(undef);
$_->hover(0) for @{$self->volumes};
if ($volume_idx <= $#{$self->volumes}) {
$self->_hover_volume_idx($volume_idx);
$self->volumes->[$volume_idx]->hover(1);
my $group_id = $self->volumes->[$volume_idx]->select_group_id;
if ($group_id != -1) {
$_->hover(1) for grep { $_->select_group_id == $group_id } @{$self->volumes};
}
$self->on_hover->($volume_idx) if $self->on_hover;
}
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glFlush();
glFinish();
glEnable(GL_LIGHTING);
}
# draw fixed background
if ($SOLID_BACKGROUNDCOLOR == 0 && $self->background) {
glDisable(GL_LIGHTING);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glBegin(GL_QUADS);
glColor3f( @BOTTOM_COLOR ); # bottom color
glVertex2f(-1.0,-1.0);
glVertex2f(1,-1.0);
glColor3f( @TOP_COLOR ); # top color
glVertex2f(1, 1);
glVertex2f(-1.0, 1);
glEnd();
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glEnable(GL_LIGHTING);
}
# draw ground and axes
glDisable(GL_LIGHTING);
# draw ground
my $ground_z = GROUND_Z;
if ($self->bed_triangles) {
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableClientState(GL_VERTEX_ARRAY);
my $triangle_vertex;
if (HAS_VBO) {
($triangle_vertex) =
glGenBuffersARB_p(1);
$self->bed_triangles->bind($triangle_vertex);
glBufferDataARB_p(GL_ARRAY_BUFFER_ARB, $self->bed_triangles, GL_STATIC_DRAW_ARB);
glVertexPointer_c(3, GL_FLOAT, 0, 0);
} else {
# fall back on old behavior
glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_triangles->ptr());
}
glColor4f( @GROUND_COLOR );
glNormal3d(0,0,1);
glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
glDisableClientState(GL_VERTEX_ARRAY);
# we need depth test for grid, otherwise it would disappear when looking
#Â the object from below
glEnable(GL_DEPTH_TEST);
# draw grid
glLineWidth(2);
glEnableClientState(GL_VERTEX_ARRAY);
my $grid_vertex;
if (HAS_VBO) {
($grid_vertex) =
glGenBuffersARB_p(1);
$self->bed_grid_lines->bind($grid_vertex);
glBufferDataARB_p(GL_ARRAY_BUFFER_ARB, $self->bed_grid_lines, GL_STATIC_DRAW_ARB);
glVertexPointer_c(3, GL_FLOAT, 0, 0);
} else {
# fall back on old behavior
glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_grid_lines->ptr());
}
glColor4f( @GRID_COLOR );
glNormal3d(0,0,1);
glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
glDisableClientState(GL_VERTEX_ARRAY);
glDisable(GL_BLEND);
if (HAS_VBO) {
# Turn off buffer objects to let the rest of the draw code work.
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
glDeleteBuffersARB_p($grid_vertex);
glDeleteBuffersARB_p($triangle_vertex);
}
}
my $volumes_bb = $self->volumes_bounding_box;
{
# draw axes
# disable depth testing so that axes are not covered by ground
glDisable(GL_DEPTH_TEST);
my $origin = $self->origin;
my $axis_len = max(
max(@{ $self->bed_bounding_box->size }),
1.2 * max(@{ $volumes_bb->size }),
);
glLineWidth(2);
glBegin(GL_LINES);
# draw line for x axis
glColor3f(1, 0, 0);
glVertex3f(@$origin, $ground_z);
glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,,
# draw line for y axis
glColor3f(0, 1, 0);
glVertex3f(@$origin, $ground_z);
glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++
glEnd();
# draw line for Z axis
# (re-enable depth test so that axis is correctly shown when objects are behind it)
glEnable(GL_DEPTH_TEST);
glBegin(GL_LINES);
glColor3f(0, 0, 1);
glVertex3f(@$origin, $ground_z);
glVertex3f(@$origin, $ground_z+$axis_len);
glEnd();
}
glEnable(GL_LIGHTING);
# draw objects
$self->draw_volumes;
# draw cutting plane
if (defined $self->cutting_plane_z) {
my $plane_z = $self->cutting_plane_z;
my $bb = $volumes_bb;
glDisable(GL_CULL_FACE);
glDisable(GL_LIGHTING);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBegin(GL_QUADS);
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);
glVertex3f($bb->x_min+$plane_z, $bb->y_max+20, $bb->z_max+20);
glVertex3f($bb->x_min+$plane_z, $bb->y_min-20, $bb->z_max+20);
} elsif ($self->cutting_plane_axis == Y) {
glVertex3f($bb->x_min-20, $bb->y_min+$plane_z, $bb->z_min-20);
glVertex3f($bb->x_max+20, $bb->y_min+$plane_z, $bb->z_min-20);
glVertex3f($bb->x_max+20, $bb->y_min+$plane_z, $bb->z_max+20);
glVertex3f($bb->x_min-20, $bb->y_min+$plane_z, $bb->z_max+20);
} elsif ($self->cutting_plane_axis == Z) {
glVertex3f($bb->x_min-20, $bb->y_min-20, $bb->z_min+$plane_z);
glVertex3f($bb->x_max+20, $bb->y_min-20, $bb->z_min+$plane_z);
glVertex3f($bb->x_max+20, $bb->y_max+20, $bb->z_min+$plane_z);
glVertex3f($bb->x_min-20, $bb->y_max+20, $bb->z_min+$plane_z);
}
glEnd();
glEnable(GL_CULL_FACE);
glDisable(GL_BLEND);
}
if (defined $self->_drag_start_pos || defined $self->_drag_start_xy) {
$self->draw_center_of_rotation($self->_camera_target->x, $self->_camera_target->y, $self->_camera_target->z);
}
glFlush();
$self->SwapBuffers();
# Calling glFinish has a performance penalty, but it seems to fix some OpenGL driver hang-up with extremely large scenes.
glFinish();
}
sub draw_axes {
my ($self, $x, $y, $z, $length, $width, $allways_visible) = @_;
if ($allways_visible) {
glDisable(GL_DEPTH_TEST);
} else {
glEnable(GL_DEPTH_TEST);
}
glLineWidth($width);
glBegin(GL_LINES);
# draw line for x axis
glColor3f(1, 0, 0);
glVertex3f($x, $y, $z);
glVertex3f($x + $length, $y, $z);
# draw line for y axis
glColor3f(0, 1, 0);
glVertex3f($x, $y, $z);
glVertex3f($x, $y + $length, $z);
# draw line for Z axis
glColor3f(0, 0, 1);
glVertex3f($x, $y, $z);
glVertex3f($x, $y, $z + $length);
glEnd();
}
sub draw_center_of_rotation {
my ($self, $x, $y, $z) = @_;
$self->draw_axes($x, $y, $z, 10, 1, 1);
$self->draw_axes($x, $y, $z, 10, 4, 0);
}
sub draw_volumes {
my ($self, $fakecolor) = @_;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
foreach my $volume_idx (0..$#{$self->volumes}) {
my $volume = $self->volumes->[$volume_idx];
glPushMatrix();
glTranslatef(@{$volume->origin});
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);
} elsif ($volume->selected) {
glColor4f( @SELECTED_COLOR , $volume->color->[3]);
} elsif ($volume->hover) {
glColor4f( @HOVER_COLOR, $volume->color->[3]);
} else {
glColor4f(@{ $volume->color });
}
my @sorted_z = ();
my ($min_z, $max_z);
if ($volume->range && $volume->offsets) {
@sorted_z = sort { $a <=> $b } keys %{$volume->offsets};
($min_z, $max_z) = @{$volume->range};
$min_z = first { $_ >= $min_z } @sorted_z;
$max_z = first { $_ > $max_z } @sorted_z;
}
glCullFace(GL_BACK);
if ($volume->qverts) {
my ($min_offset, $max_offset);
if (defined $min_z) {
$min_offset = $volume->offsets->{$min_z}->[0];
}
if (defined $max_z) {
$max_offset = $volume->offsets->{$max_z}->[0];
}
$min_offset //= 0;
$max_offset //= $volume->qverts->size;
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);
}
if ($volume->tverts) {
my ($min_offset, $max_offset);
if (defined $min_z) {
$min_offset = $volume->offsets->{$min_z}->[1];
}
if (defined $max_z) {
$max_offset = $volume->offsets->{$max_z}->[1];
}
$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);
}
glPopMatrix();
}
glDisableClientState(GL_NORMAL_ARRAY);
glDisable(GL_BLEND);
my $cut_vertex;
if (defined $self->cutting_plane_z) {
if (HAS_VBO) {
# Use Vertex Buffer Object for cutting plane (previous method crashes on modern POGL).
($cut_vertex) = glGenBuffersARB_p(1);
$self->cut_lines_vertices->bind($cut_vertex);
glBufferDataARB_p(GL_ARRAY_BUFFER_ARB, $self->cut_lines_vertices, GL_STATIC_DRAW_ARB);
glVertexPointer_c(3, GL_FLOAT, 0, 0);
} else {
# Use legacy method.
glVertexPointer_c(3, GL_FLOAT, 0, $self->cut_lines_vertices->ptr());
}
glLineWidth(2);
glColor3f(0, 0, 0);
glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3);
if (HAS_VBO) {
# Turn off buffer objects to let the rest of the draw code work.
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
glDeleteBuffersARB_p($cut_vertex);
}
}
glDisableClientState(GL_VERTEX_ARRAY);
}
package Slic3r::GUI::3DScene::Volume;
use Moo;
has 'bounding_box' => (is => 'ro', required => 1);
has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) });
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 'hover' => (is => 'rw', default => sub { 0 });
has 'range' => (is => 'rw');
# geometric data
has 'qverts' => (is => 'rw'); # GLVertexArray object
has 'tverts' => (is => 'rw'); # GLVertexArray object
has 'mesh' => (is => 'rw'); # only required for cut contours
has 'offsets' => (is => 'rw'); # [ z => [ qverts_idx, tverts_idx ] ]
sub transformed_bounding_box {
my ($self) = @_;
my $bb = $self->bounding_box;
$bb->translate(@{$self->origin});
return $bb;
}
package Slic3r::GUI::3DScene;
use base qw(Slic3r::GUI::3DScene::Base);
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
color_by
color_toolpaths_by
select_by
drag_by
volumes_by_object
_objects_by_volumes
));
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
$self->select_by('object'); # object | volume | instance
$self->drag_by('instance'); # object | instance
$self->volumes_by_object({}); # obj_idx => [ volume_idx, volume_idx ... ]
$self->_objects_by_volumes({}); # volume_idx => [ obj_idx, instance_idx ]
return $self;
}
sub load_object {
my ($self, $model, $obj_idx, $instance_idxs) = @_;
my $model_object;
if ($model->isa('Slic3r::Model::Object')) {
$model_object = $model;
$model = $model_object->model;
$obj_idx = 0;
} else {
$model_object = $model->get_object($obj_idx);
}
$instance_idxs ||= [0..$#{$model_object->instances}];
my @volumes_idx = ();
foreach my $volume_idx (0..$#{$model_object->volumes}) {
my $volume = $model_object->volumes->[$volume_idx];
foreach my $instance_idx (@$instance_idxs) {
my $instance = $model_object->instances->[$instance_idx];
my $mesh = $volume->mesh->clone;
$instance->transform_mesh($mesh);
my $color_idx;
if ($self->color_by eq 'volume') {
$color_idx = $volume_idx;
} elsif ($self->color_by eq 'object') {
$color_idx = $obj_idx;
}
my $color = [ @{$self->colors->[ $color_idx % scalar(@{$self->colors}) ]} ];
$color->[3] = $volume->modifier ? 0.5 : 1;
push @{$self->volumes}, my $v = Slic3r::GUI::3DScene::Volume->new(
bounding_box => $mesh->bounding_box,
color => $color,
);
if ($self->select_by eq 'object') {
$v->select_group_id($obj_idx*1000000);
} elsif ($self->select_by eq 'volume') {
$v->select_group_id($obj_idx*1000000 + $volume_idx*1000);
} elsif ($self->select_by eq 'instance') {
$v->select_group_id($obj_idx*1000000 + $volume_idx*1000 + $instance_idx);
}
if ($self->drag_by eq 'object') {
$v->drag_group_id($obj_idx*1000);
} elsif ($self->drag_by eq 'instance') {
$v->drag_group_id($obj_idx*1000 + $instance_idx);
}
push @volumes_idx, my $scene_volume_idx = $#{$self->volumes};
$self->_objects_by_volumes->{$scene_volume_idx} = [ $obj_idx, $volume_idx, $instance_idx ];
my $verts = Slic3r::GUI::_3DScene::GLVertexArray->new;
$verts->load_mesh($mesh);
$v->tverts($verts);
}
}
$self->volumes_by_object->{$obj_idx} = [@volumes_idx];
return @volumes_idx;
}
sub load_print_object_slices {
my ($self, $object) = @_;
my @verts = ();
my @norms = ();
my @quad_verts = ();
my @quad_norms = ();
foreach my $layer (@{$object->layers}) {
my $gap = 0;
my $top_z = $layer->print_z;
my $bottom_z = $layer->print_z - $layer->height + $gap;
foreach my $copy (@{ $object->_shifted_copies }) {
{
my @expolygons = map $_->clone, @{$layer->slices};
$_->translate(@$copy) for @expolygons;
$self->_expolygons_to_verts(\@expolygons, $layer->print_z, \@verts, \@norms);
}
foreach my $slice (@{$layer->slices}) {
foreach my $polygon (@$slice) {
foreach my $line (@{$polygon->lines}) {
$line->translate(@$copy);
push @quad_norms, (0,0,-1), (0,0,-1);
push @quad_verts, (map unscale($_), @{$line->a}), $bottom_z;
push @quad_verts, (map unscale($_), @{$line->b}), $bottom_z;
push @quad_norms, (0,0,1), (0,0,1);
push @quad_verts, (map unscale($_), @{$line->b}), $top_z;
push @quad_verts, (map unscale($_), @{$line->a}), $top_z;
# We'll use this for the middle normal when using 4 quads:
#my $xy_normal = $line->normal;
#$_xynormal->scale(1/$line->length);
}
}
}
}
}
my $obb = $object->bounding_box;
my $bb = Slic3r::Geometry::BoundingBoxf3->new;
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$obb->min_point}, 0));
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$obb->max_point}, $object->size->z));
push @{$self->volumes}, my $v = Slic3r::GUI::3DScene::Volume->new(
bounding_box => $bb,
color => $self->colors->[0],
verts => OpenGL::Array->new_list(GL_FLOAT, @verts),
norms => OpenGL::Array->new_list(GL_FLOAT, @norms),
quad_verts => OpenGL::Array->new_list(GL_FLOAT, @quad_verts),
quad_norms => OpenGL::Array->new_list(GL_FLOAT, @quad_norms),
);
}
sub load_print_toolpaths {
my ($self, $print) = @_;
return if !$print->step_done(STEP_SKIRT);
return if !$print->step_done(STEP_BRIM);
return if !$print->has_skirt
&& $print->config->brim_width == 0
&& $print->config->interior_brim_width == 0
&& $print->config->brim_connections_width == 0;
my $bb = Slic3r::Geometry::BoundingBoxf3->new;
{
my $pbb = $print->bounding_box;
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$pbb->min_point}));
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$pbb->max_point}));
}
my $color_by_extruder = $self->color_toolpaths_by eq 'extruder';
if (!$print->brim->empty) {
my $color = $color_by_extruder
? $self->colors->[ ($print->brim_extruder-1) % @{$self->colors} ]
: $self->colors->[2];
push @{$self->volumes}, my $volume = Slic3r::GUI::3DScene::Volume->new(
bounding_box => $bb,
color => $color,
qverts => Slic3r::GUI::_3DScene::GLVertexArray->new,
tverts => Slic3r::GUI::_3DScene::GLVertexArray->new,
offsets => {}, # print_z => [ qverts, tverts ]
);
my $top_z = $print->get_object(0)->get_layer(0)->print_z;
$volume->offsets->{$top_z} = [0, 0];
$self->_extrusionentity_to_verts($print->brim, $top_z, Slic3r::Point->new(0,0),
$volume->qverts, $volume->tverts);
}
if (!$print->skirt->empty) {
# TODO: it's a bit difficult to predict skirt extruders with the current skirt logic.
#Â We need to rewrite it anyway.
my $color = +($self->default_colors)[0];
push @{$self->volumes}, my $volume = Slic3r::GUI::3DScene::Volume->new(
bounding_box => $bb,
color => $color,
qverts => Slic3r::GUI::_3DScene::GLVertexArray->new,
tverts => Slic3r::GUI::_3DScene::GLVertexArray->new,
offsets => {}, # print_z => [ qverts, tverts ]
);
my $skirt_height = 0; # number of layers
if ($print->has_infinite_skirt) {
$skirt_height = $print->total_layer_count;
} else {
$skirt_height = min($print->config->skirt_height, $print->total_layer_count);
}
$skirt_height ||= 1 if $print->config->brim_width > 0 || $print->config->interior_brim_width;
# get first $skirt_height layers (maybe this should be moved to a PrintObject method?)
my $object0 = $print->get_object(0);
my @layers = ();
push @layers, map $object0->get_layer($_-1), 1..min($skirt_height, $object0->layer_count);
push @layers, map $object0->get_support_layer($_-1), 1..min($skirt_height, $object0->support_layer_count);
@layers = sort { $a->print_z <=> $b->print_z } @layers;
@layers = @layers[0..($skirt_height-1)];
foreach my $i (0..($skirt_height-1)) {
my $top_z = $layers[$i]->print_z;
$volume->offsets->{$top_z} = [$volume->qverts->size, $volume->tverts->size];
$self->_extrusionentity_to_verts($print->skirt, $top_z, Slic3r::Point->new(0,0),
$volume->qverts, $volume->tverts);
}
}
}
sub load_print_object_toolpaths {
my ($self, $object) = @_;
# order layers by print_z
my @layers = sort { $a->print_z <=> $b->print_z }
@{$object->layers}, @{$object->support_layers};
# Bounding box of the object and its copies.
my $bb = Slic3r::Geometry::BoundingBoxf3->new;
{
my $obb = $object->bounding_box;
foreach my $copy (@{ $object->_shifted_copies }) {
my $cbb = $obb->clone;
$cbb->translate(@$copy);
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->min_point}, 0));
$bb->merge_point(Slic3r::Pointf3->new_unscale(@{$cbb->max_point}, $object->size->z));
}
}
my %volumes = (); # color => Volume
# Maximum size of an allocation block: 32MB / sizeof(float)
my $alloc_size_max = 32 * 1048576 / 4;
my $add = sub {
my ($coll, $top_z, $copy, $color) = @_;
my $volume = $volumes{$color};
if (!$volume) {
push @{$self->volumes}, $volumes{$color} = $volume = Slic3r::GUI::3DScene::Volume->new(
bounding_box => $bb,
color => $color,
qverts => Slic3r::GUI::_3DScene::GLVertexArray->new,
tverts => Slic3r::GUI::_3DScene::GLVertexArray->new,
offsets => {}, # print_z => [ qverts, tverts ]
);
}
$volume->offsets->{$top_z} //= [ $volume->qverts->size, $volume->tverts->size ];
$self->_extrusionentity_to_verts($coll, $top_z, $copy, $volume->qverts, $volume->tverts);
};
my $color_by_extruder = $self->color_toolpaths_by eq 'extruder';
foreach my $layer (@layers) {
my $top_z = $layer->print_z;
foreach my $copy (@{ $object->_shifted_copies }) {
foreach my $layerm (@{$layer->regions}) {
if ($object->step_done(STEP_PERIMETERS)) {
my $color = $color_by_extruder
? $self->colors->[ ($layerm->region->config->perimeter_extruder-1) % @{$self->colors} ]
: $self->colors->[0];
$add->($layerm->perimeters, $top_z, $copy, $color);
}
if ($object->step_done(STEP_INFILL)) {
my $color = $color_by_extruder
? $self->colors->[ ($layerm->region->config->infill_extruder-1) % @{$self->colors} ]
: $self->colors->[1];
if ($color_by_extruder && $layerm->region->config->infill_extruder != $layerm->region->config->solid_infill_extruder) {
# divide solid and non-solid infill
my $solid = Slic3r::ExtrusionPath::Collection->new;
my $non_solid = Slic3r::ExtrusionPath::Collection->new;
foreach my $fill (@{$layerm->fills}) {
if ($fill->[0]->is_solid_infill) {
$solid->append($fill);
} else {
$non_solid->append($fill);
}
}
$add->($non_solid, $top_z, $copy, $color);
$color = $self->colors->[ ($layerm->region->config->solid_infill_extruder-1) % @{$self->colors} ];
$add->($solid, $top_z, $copy, $color);
} else {
$add->($layerm->fills, $top_z, $copy, $color);
}
}
}
if ($layer->isa('Slic3r::Layer::Support') && $object->step_done(STEP_SUPPORTMATERIAL)) {
{
my $color = $color_by_extruder
? $self->colors->[ ($layer->object->config->support_material_extruder-1) % @{$self->colors} ]
: $self->colors->[2];
$add->($layer->support_fills, $top_z, $copy, $color);
}
{
my $color = ($color_by_extruder)
? $self->colors->[ ($layer->object->config->support_material_interface_extruder-1) % @{$self->colors} ]
: $self->colors->[2];
$add->($layer->support_interface_fills, $top_z, $copy, $color);
}
}
}
foreach my $color (keys %volumes) {
my $volume = $volumes{$color};
if ($volume->qverts->size() > $alloc_size_max || $volume->tverts->size() > $alloc_size_max) {
# stop appending to this volume; create a new one next time
delete $volumes{$color};
}
}
}
}
sub set_toolpaths_range {
my ($self, $min_z, $max_z) = @_;
foreach my $volume (@{$self->volumes}) {
$volume->range([ $min_z, $max_z ]);
}
}
sub _expolygons_to_verts {
my ($self, $expolygons, $z, $verts, $norms) = @_;
my $tess = gluNewTess();
gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_END, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_VERTEX, sub {
my ($x, $y, $z) = @_;
push @$verts, $x, $y, $z;
push @$norms, (0,0,1), (0,0,1), (0,0,1);
});
gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT');
foreach my $expolygon (@$expolygons) {
gluTessBeginPolygon($tess);
foreach my $polygon (@$expolygon) {
gluTessBeginContour($tess);
gluTessVertex_p($tess, (map unscale($_), @$_), $z) for @$polygon;
gluTessEndContour($tess);
}
gluTessEndPolygon($tess);
}
gluDeleteTess($tess);
}
# Fill in the $qverts and $tverts with quads and triangles
# for the extrusion $entity.
sub _extrusionentity_to_verts {
my ($self, $entity, $top_z, $copy, $qverts, $tverts) = @_;
my ($lines, $widths, $heights, $closed);
if ($entity->isa('Slic3r::ExtrusionPath::Collection')) {
$self->_extrusionentity_to_verts($_, $top_z, $copy, $qverts, $tverts)
for @$entity;
return;
} elsif ($entity->isa('Slic3r::ExtrusionPath')) {
my $polyline = $entity->polyline->clone;
$polyline->remove_duplicate_points;
$polyline->translate(@$copy);
$lines = $polyline->lines;
$widths = [ map $entity->width, 0..$#$lines ];
$heights = [ map $entity->height, 0..$#$lines ];
$closed = 0;
} else {
$lines = [];
$widths = [];
$heights = [];
$closed = 1;
foreach my $path (@$entity) {
my $polyline = $path->polyline->clone;
$polyline->remove_duplicate_points;
$polyline->translate(@$copy);
my $path_lines = $polyline->lines;
push @$lines, @$path_lines;
push @$widths, map $path->width, 0..$#$path_lines;
push @$heights, map $path->height, 0..$#$path_lines;
}
}
# Calling the C++ implementation Slic3r::_3DScene::_extrusionentity_to_verts_do()
# This adds new vertices to the $qverts and $tverts.
Slic3r::GUI::_3DScene::_extrusionentity_to_verts_do($lines, $widths, $heights,
$closed,
# Top height of the extrusion.
$top_z,
# $copy is not used here.
$copy,
# GLVertexArray object: C++ class maintaining an std::vector for coords and normals.
$qverts,
$tverts);
}
sub object_idx {
my ($self, $volume_idx) = @_;
return $self->_objects_by_volumes->{$volume_idx}[0];
}
sub volume_idx {
my ($self, $volume_idx) = @_;
return $self->_objects_by_volumes->{$volume_idx}[1];
}
sub instance_idx {
my ($self, $volume_idx) = @_;
return $self->_objects_by_volumes->{$volume_idx}[2];
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/AboutDialog.pm 0000664 0000000 0000000 00000010366 13274424355 0017752 0 ustar 00root root 0000000 0000000 package Slic3r::GUI::AboutDialog;
use strict;
use warnings;
use utf8;
use Wx qw(:font :html :misc :dialog :sizer :systemsettings :frame :id);
use Wx::Event qw(EVT_HTML_LINK_CLICKED EVT_LEFT_DOWN EVT_BUTTON);
use Wx::Print;
use Wx::Html;
use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 340], wxCAPTION);
$self->SetBackgroundColour(Wx::wxWHITE);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
$self->SetSizer($hsizer);
# logo
my $logo = Slic3r::GUI::AboutDialog::Logo->new($self, -1, wxDefaultPosition, wxDefaultSize);
$logo->SetBackgroundColour(Wx::wxWHITE);
$hsizer->Add($logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 30);
my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
$hsizer->Add($vsizer, 1, wxEXPAND, 0);
# title
my $title = Wx::StaticText->new($self, -1, 'Slic3r', wxDefaultPosition, wxDefaultSize);
my $title_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$title_font->SetWeight(wxFONTWEIGHT_BOLD);
$title_font->SetFamily(wxFONTFAMILY_ROMAN);
$title_font->SetPointSize(24);
$title->SetFont($title_font);
$vsizer->Add($title, 0, wxALIGN_LEFT | wxTOP, 30);
# version
my $version = Wx::StaticText->new($self, -1, "Version $Slic3r::VERSION", wxDefaultPosition, wxDefaultSize);
my $version_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$version_font->SetPointSize(&Wx::wxMSW ? 9 : 11);
$version->SetFont($version_font);
$vsizer->Add($version, 0, wxALIGN_LEFT | wxBOTTOM, 10);
# text
my $text =
'' .
'' .
'' .
'Copyright © 2011-2017 Alessandro Ranellucci.
' .
'Slic3r is licensed under the ' .
'GNU Affero General Public License, version 3.' .
'
' .
'Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Kliment Yanev and numerous others. ' .
'Manual by Gary Hodgson. Inspired by the RepRap community.
' .
'Slic3r logo designed by Corey Daniels, Silk Icon Set designed by Mark James. ' .
'' .
'' .
'';
my $html = Wx::HtmlWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER);
my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
my $size = &Wx::wxMSW ? 8 : 10;
$html->SetFonts($font->GetFaceName, $font->GetFaceName, [$size, $size, $size, $size, $size, $size, $size]);
$html->SetBorders(2);
$html->SetPage($text);
$vsizer->Add($html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20);
EVT_HTML_LINK_CLICKED($self, $html, \&link_clicked);
my $buttons = $self->CreateStdDialogButtonSizer(wxOK);
$self->SetEscapeId(wxID_CLOSE);
EVT_BUTTON($self, wxID_CLOSE, sub {
$self->EndModal(wxID_CLOSE);
$self->Close;
});
$vsizer->Add($buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
EVT_LEFT_DOWN($self, sub { $self->Close });
EVT_LEFT_DOWN($logo, sub { $self->Close });
return $self;
}
sub link_clicked {
my ($self, $event) = @_;
Wx::LaunchDefaultBrowser($event->GetLinkInfo->GetHref);
$event->Skip(0);
}
package Slic3r::GUI::AboutDialog::Logo;
use Wx qw(:bitmap :dc);
use Wx::Event qw(EVT_PAINT);
use base 'Wx::Panel';
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->{logo} = Wx::Bitmap->new($Slic3r::var->("Slic3r_192px.png"), wxBITMAP_TYPE_PNG);
$self->SetMinSize(Wx::Size->new($self->{logo}->GetWidth, $self->{logo}->GetHeight));
EVT_PAINT($self, \&repaint);
return $self;
}
sub repaint {
my ($self, $event) = @_;
my $dc = Wx::PaintDC->new($self);
$dc->SetBackgroundMode(wxTRANSPARENT);
my $size = $self->GetSize;
my $logo_w = $self->{logo}->GetWidth;
my $logo_h = $self->{logo}->GetHeight;
$dc->DrawBitmap($self->{logo}, ($size->GetWidth - $logo_w) / 2, ($size->GetHeight - $logo_h) / 2, 1);
$event->Skip;
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/BedShapeDialog.pm 0000664 0000000 0000000 00000025347 13274424355 0020360 0 ustar 00root root 0000000 0000000 # The bed shape dialog.
# The dialog opens from Print Settins tab -> Bed Shape: Set...
package Slic3r::GUI::BedShapeDialog;
use strict;
use warnings;
use utf8;
use List::Util qw(min max);
use Slic3r::Geometry qw(PI X Y unscale);
use Wx qw(: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) = @_;
my $self = $class->SUPER::new($parent, -1, "Bed Shape", wxDefaultPosition, [350,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
$self->{panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $default);
my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
$main_sizer->Add($panel, 1, wxEXPAND);
$main_sizer->Add($self->CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND);
$self->SetSizer($main_sizer);
$self->SetMinSize($self->GetSize);
$main_sizer->SetSizeHints($self);
# needed to actually free memory
EVT_CLOSE($self, sub {
$self->EndModal(wxID_OK);
$self->Destroy;
});
return $self;
}
sub GetValue {
my ($self) = @_;
return $self->{panel}->GetValue;
}
package Slic3r::GUI::BedShapePanel;
use List::Util qw(min max sum first);
use Scalar::Util qw(looks_like_number);
use Slic3r::Geometry qw(PI X Y scale unscale scaled_epsilon deg2rad);
use Wx qw(:font :id :misc :sizer :choicebook :filedialog :pen :brush wxTAB_TRAVERSAL);
use Wx::Event qw(EVT_CLOSE EVT_CHOICEBOOK_PAGE_CHANGED EVT_BUTTON);
use base 'Wx::Panel';
use constant SHAPE_RECTANGULAR => 0;
use constant SHAPE_CIRCULAR => 1;
use constant SHAPE_CUSTOM => 2;
sub new {
my $class = shift;
my ($parent, $default) = @_;
my $self = $class->SUPER::new($parent, -1);
$self->on_change(undef);
my $box = Wx::StaticBox->new($self, -1, "Shape");
my $sbsizer = Wx::StaticBoxSizer->new($box, wxVERTICAL);
# shape options
$self->{shape_options_book} = Wx::Choicebook->new($self, -1, wxDefaultPosition, [300,-1], wxCHB_TOP);
$sbsizer->Add($self->{shape_options_book});
$self->{optgroups} = [];
{
my $optgroup = $self->_init_shape_options_page('Rectangular');
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'rect_size',
type => 'point',
label => 'Size',
tooltip => 'Size in X and Y of the rectangular plate.',
default => [200,200],
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'rect_origin',
type => 'point',
label => 'Origin',
tooltip => 'Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.',
default => [0,0],
));
}
{
my $optgroup = $self->_init_shape_options_page('Circular');
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'diameter',
type => 'f',
label => 'Diameter',
tooltip => 'Diameter of the print bed. It is assumed that origin (0,0) is located in the center.',
sidetext => 'mm',
default => 200,
));
}
{
my $optgroup = $self->_init_shape_options_page('Custom');
$optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new(
full_width => 1,
widget => sub {
my ($parent) = @_;
my $btn = Wx::Button->new($parent, -1, "Load shape from STL...", wxDefaultPosition, wxDefaultSize);
EVT_BUTTON($self, $btn, sub { $self->_load_stl });
return $btn;
}
));
}
EVT_CHOICEBOOK_PAGE_CHANGED($self, -1, sub {
$self->_update_shape;
});
# right pane with preview canvas
my $canvas = $self->{canvas} = Slic3r::GUI::2DBed->new($self);
# main sizer
my $top_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$top_sizer->Add($sbsizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
$top_sizer->Add($canvas, 1, wxEXPAND | wxALL, 10) if $canvas;
$self->SetSizerAndFit($top_sizer);
$self->_set_shape($default);
$self->_update_preview;
return $self;
}
sub on_change {
my ($self, $cb) = @_;
$self->{on_change} = $cb // sub {};
}
# Called from the constructor.
# Set the initial bed shape from a list of points.
# Deduce the bed shape type (rect, circle, custom)
# This routine shall be smart enough if the user messes up
# with the list of points in the ini file directly.
sub _set_shape {
my ($self, $points) = @_;
# is this a rectangle?
if (@$points == 4) {
my $polygon = Slic3r::Polygon->new_scale(@$points);
my $lines = $polygon->lines;
if ($lines->[0]->parallel_to_line($lines->[2]) && $lines->[1]->parallel_to_line($lines->[3])) {
# okay, it's a rectangle
# find origin
# the || 0 hack prevents "-0" which might confuse the user
my $x_min = min(map $_->[X], @$points) || 0;
my $x_max = max(map $_->[X], @$points) || 0;
my $y_min = min(map $_->[Y], @$points) || 0;
my $y_max = max(map $_->[Y], @$points) || 0;
my $origin = [-$x_min, -$y_min];
$self->{shape_options_book}->SetSelection(SHAPE_RECTANGULAR);
my $optgroup = $self->{optgroups}[SHAPE_RECTANGULAR];
$optgroup->set_value('rect_size', [ $x_max-$x_min, $y_max-$y_min ]);
$optgroup->set_value('rect_origin', $origin);
$self->_update_shape;
return;
}
}
# is this a circle?
{
# Analyze the array of points. Do they reside on a circle?
my $polygon = Slic3r::Polygon->new_scale(@$points);
my $center = $polygon->bounding_box->center;
my @vertex_distances = map $center->distance_to($_), @$polygon;
my $avg_dist = sum(@vertex_distances)/@vertex_distances;
if (!defined first { abs($_ - $avg_dist) > 10*scaled_epsilon } @vertex_distances) {
# all vertices are equidistant to center
$self->{shape_options_book}->SetSelection(SHAPE_CIRCULAR);
my $optgroup = $self->{optgroups}[SHAPE_CIRCULAR];
$optgroup->set_value('diameter', sprintf("%.0f", unscale($avg_dist*2)));
$self->_update_shape;
return;
}
}
if (@$points < 3) {
# Invalid polygon. Revert to default bed dimensions.
$self->{shape_options_book}->SetSelection(SHAPE_RECTANGULAR);
my $optgroup = $self->{optgroups}[SHAPE_RECTANGULAR];
$optgroup->set_value('rect_size', [200, 200]);
$optgroup->set_value('rect_origin', [0, 0]);
$self->_update_shape;
return;
}
# This is a custom bed shape, use the polygon provided.
$self->{shape_options_book}->SetSelection(SHAPE_CUSTOM);
# Copy the polygon to the canvas, make a copy of the array.
$self->{canvas}->bed_shape([@$points]);
$self->_update_shape;
}
# Update the bed shape from the dialog fields.
sub _update_shape {
my ($self) = @_;
my $page_idx = $self->{shape_options_book}->GetSelection;
if ($page_idx == SHAPE_RECTANGULAR) {
my $rect_size = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_size');
my $rect_origin = $self->{optgroups}[SHAPE_RECTANGULAR]->get_value('rect_origin');
my ($x, $y) = @$rect_size;
return if !looks_like_number($x) || !looks_like_number($y); #Â empty strings or '-' or other things
return if !$x || !$y or $x == 0 or $y == 0;
my ($x0, $y0) = (0,0);
my ($x1, $y1) = ($x ,$y);
{
my ($dx, $dy) = @$rect_origin;
return if !looks_like_number($dx) || !looks_like_number($dy); #Â empty strings or '-' or other things
$x0 -= $dx;
$x1 -= $dx;
$y0 -= $dy;
$y1 -= $dy;
}
$self->{canvas}->bed_shape([
[$x0,$y0],
[$x1,$y0],
[$x1,$y1],
[$x0,$y1],
]);
} elsif ($page_idx == SHAPE_CIRCULAR) {
my $diameter = $self->{optgroups}[SHAPE_CIRCULAR]->get_value('diameter');
return if !$diameter or $diameter == 0;
my $r = $diameter/2;
my $twopi = 2*PI;
my $edges = 60;
my $polygon = Slic3r::Polygon->new_scale(
map [ $r * cos $_, $r * sin $_ ],
map { $twopi/$edges*$_ } 1..$edges
);
$self->{canvas}->bed_shape([
map [ unscale($_->x), unscale($_->y) ], @$polygon #))
]);
}
$self->{on_change}->();
$self->_update_preview;
}
sub _update_preview {
my ($self) = @_;
$self->{canvas}->Refresh if $self->{canvas};
$self->Refresh;
}
# Called from the constructor.
# Create a panel for a rectangular / circular / custom bed shape.
sub _init_shape_options_page {
my ($self, $title) = @_;
my $panel = Wx::Panel->new($self->{shape_options_book});
my $optgroup;
push @{$self->{optgroups}}, $optgroup = Slic3r::GUI::OptionsGroup->new(
parent => $panel,
title => 'Settings',
label_width => 100,
on_change => sub {
my ($opt_id) = @_;
#$self->{"_$opt_id"} = $optgroup->get_value($opt_id);
$self->_update_shape;
},
);
$panel->SetSizerAndFit($optgroup->sizer);
$self->{shape_options_book}->AddPage($panel, $title);
return $optgroup;
}
# Loads an stl file, projects it to the XY plane and calculates a polygon.
sub _load_stl {
my ($self) = @_;
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;
}
my $input_file = Slic3r::decode_path($dialog->GetPaths);
$dialog->Destroy;
my $model = Slic3r::Model->read_from_file($input_file);
my $mesh = $model->raw_mesh;
my $expolygons = $mesh->horizontal_projection;
if (@$expolygons == 0) {
Slic3r::GUI::show_error($self, "The selected file contains no geometry.");
return;
}
if (@$expolygons > 1) {
Slic3r::GUI::show_error($self, "The selected file contains several disjoint areas. This is not supported.");
return;
}
my $polygon = $expolygons->[0]->contour;
$self->{canvas}->bed_shape([ map [ unscale($_->x), unscale($_->y) ], @$polygon ]);
$self->_update_preview();
}
# Returns the resulting bed shape polygon. This value will be stored to the ini file.
sub GetValue {
my ($self) = @_;
return $self->{canvas}->bed_shape;
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/BonjourBrowser.pm 0000664 0000000 0000000 00000003007 13274424355 0020534 0 ustar 00root root 0000000 0000000 # A tiny dialog to select an OctoPrint device to print to.
package Slic3r::GUI::BonjourBrowser;
use strict;
use warnings;
use utf8;
use Wx qw(:dialog :id :misc :sizer :choicebook wxTAB_TRAVERSAL);
use Wx::Event qw(EVT_CLOSE);
use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent, $devices) = @_;
my $self = $class->SUPER::new($parent, -1, "Device Browser", wxDefaultPosition, [350,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
$self->{devices} = $devices;
# label
my $text = Wx::StaticText->new($self, -1, "Choose an OctoPrint device in your network:", wxDefaultPosition, wxDefaultSize);
# selector
$self->{choice} = my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize,
[ map $_->name, @{$self->{devices}} ]);
my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
$main_sizer->Add($text, 1, wxEXPAND | wxALL, 10);
$main_sizer->Add($choice, 1, wxEXPAND | wxALL, 10);
$main_sizer->Add($self->CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND);
$self->SetSizer($main_sizer);
$self->SetMinSize($self->GetSize);
$main_sizer->SetSizeHints($self);
# needed to actually free memory
EVT_CLOSE($self, sub {
$self->EndModal(wxID_OK);
$self->Destroy;
});
return $self;
}
sub GetValue {
my ($self) = @_;
return $self->{devices}[ $self->{choice}->GetSelection ]->address;
}
sub GetPort {
my ($self) = @_;
return $self->{devices}[ $self->{choice}->GetSelection ]->port;
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/ColorScheme.pm 0000664 0000000 0000000 00000014502 13274424355 0017757 0 ustar 00root root 0000000 0000000 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
Slic3r-1.3.0/lib/Slic3r/GUI/ConfigWizard.pm 0000664 0000000 0000000 00000027745 13274424355 0020157 0 ustar 00root root 0000000 0000000 # The config wizard is executed when the Slic3r is first started.
# The wizard helps the user to specify the 3D printer properties.
package Slic3r::GUI::ConfigWizard;
use strict;
use warnings;
use utf8;
use Wx;
use base 'Wx::Wizard';
use Slic3r::Geometry qw(unscale);
# adhere to various human interface guidelines
our $wizard = 'Wizard';
$wizard = 'Assistant' if &Wx::wxMAC || &Wx::wxGTK;
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, -1, "Configuration $wizard");
# initialize an empty repository
$self->{config} = Slic3r::Config->new;
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Welcome->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Firmware->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Bed->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Nozzle->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Filament->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Temperature->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::BedTemperature->new($self));
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Finished->new($self));
$_->build_index for @{$self->{pages}};
return $self;
}
sub add_page {
my $self = shift;
my ($page) = @_;
my $n = push @{$self->{pages}}, $page;
# add first page to the page area sizer
$self->GetPageAreaSizer->Add($page) if $n == 1;
# link pages
$self->{pages}[$n-2]->set_next_page($page) if $n >= 2;
$page->set_previous_page($self->{pages}[$n-2]) if $n >= 2;
}
sub run {
my $self = shift;
if (Wx::Wizard::RunWizard($self, $self->{pages}[0])) {
# it would be cleaner to have these defined inside each page class,
# in some event getting called before leaving the page
{
# set first_layer_height + layer_height based on nozzle_diameter
my $nozzle = $self->{config}->nozzle_diameter;
$self->{config}->set('first_layer_height', $nozzle->[0]);
$self->{config}->set('layer_height', $nozzle->[0] - 0.1);
# set first_layer_temperature to temperature + 5
$self->{config}->set('first_layer_temperature', [$self->{config}->temperature->[0] + 5]);
# set first_layer_bed_temperature to temperature + 5
$self->{config}->set('first_layer_bed_temperature',
($self->{config}->bed_temperature > 0) ? ($self->{config}->bed_temperature + 5) : 0);
}
$self->Destroy;
return $self->{config};
} else {
$self->Destroy;
return undef;
}
}
package Slic3r::GUI::ConfigWizard::Index;
use Wx qw(:bitmap :dc :font :misc :sizer :systemsettings :window);
use Wx::Event qw(EVT_ERASE_BACKGROUND EVT_PAINT);
use base 'Wx::Panel';
sub new {
my $class = shift;
my ($parent, $title) = @_;
my $self = $class->SUPER::new($parent);
push @{$self->{titles}}, $title;
$self->{own_index} = 0;
$self->{bullets}->{before} = Wx::Bitmap->new($Slic3r::var->("bullet_black.png"), wxBITMAP_TYPE_PNG);
$self->{bullets}->{own} = Wx::Bitmap->new($Slic3r::var->("bullet_blue.png"), wxBITMAP_TYPE_PNG);
$self->{bullets}->{after} = Wx::Bitmap->new($Slic3r::var->("bullet_white.png"), wxBITMAP_TYPE_PNG);
$self->{background} = Wx::Bitmap->new($Slic3r::var->("Slic3r_192px_transparent.png"), wxBITMAP_TYPE_PNG);
$self->SetMinSize(Wx::Size->new($self->{background}->GetWidth, $self->{background}->GetHeight));
EVT_PAINT($self, \&repaint);
return $self;
}
sub repaint {
my ($self, $event) = @_;
my $size = $self->GetClientSize;
my $gap = 5;
my $dc = Wx::PaintDC->new($self);
$dc->SetBackgroundMode(wxTRANSPARENT);
$dc->SetFont($self->GetFont);
$dc->SetTextForeground($self->GetForegroundColour);
my $background_h = $self->{background}->GetHeight;
my $background_w = $self->{background}->GetWidth;
$dc->DrawBitmap($self->{background}, ($size->GetWidth - $background_w) / 2, ($size->GetHeight - $background_h) / 2, 1);
my $label_h = $self->{bullets}->{own}->GetHeight;
$label_h = $dc->GetCharHeight if $dc->GetCharHeight > $label_h;
my $label_w = $size->GetWidth;
my $i = 0;
foreach (@{$self->{titles}}) {
my $bullet = $self->{bullets}->{own};
$bullet = $self->{bullets}->{before} if $i < $self->{own_index};
$bullet = $self->{bullets}->{after} if $i > $self->{own_index};
$dc->SetTextForeground(Wx::Colour->new(128, 128, 128)) if $i > $self->{own_index};
$dc->DrawLabel($_, $bullet, Wx::Rect->new(0, $i * ($label_h + $gap), $label_w, $label_h));
$i++;
}
$event->Skip;
}
sub prepend_title {
my $self = shift;
my ($title) = @_;
unshift @{$self->{titles}}, $title;
$self->{own_index}++;
$self->Refresh;
}
sub append_title {
my $self = shift;
my ($title) = @_;
push @{$self->{titles}}, $title;
$self->Refresh;
}
package Slic3r::GUI::ConfigWizard::Page;
use Wx qw(:font :misc :sizer :staticline :systemsettings);
use base 'Wx::WizardPage';
sub new {
my $class = shift;
my ($parent, $title, $short_title) = @_;
my $self = $class->SUPER::new($parent);
my $sizer = Wx::FlexGridSizer->new(0, 2, 10, 10);
$sizer->AddGrowableCol(1, 1);
$sizer->AddGrowableRow(1, 1);
$sizer->AddStretchSpacer(0);
$self->SetSizer($sizer);
# title
my $text = Wx::StaticText->new($self, -1, $title, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
my $bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$bold_font->SetWeight(wxFONTWEIGHT_BOLD);
$bold_font->SetPointSize(14);
$text->SetFont($bold_font);
$sizer->Add($text, 0, wxALIGN_LEFT, 0);
# index
$self->{short_title} = $short_title ? $short_title : $title;
$self->{index} = Slic3r::GUI::ConfigWizard::Index->new($self, $self->{short_title});
$sizer->Add($self->{index}, 1, wxEXPAND | wxTOP | wxRIGHT, 10);
# contents
$self->{width} = 430;
$self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($self->{vsizer}, 1);
return $self;
}
sub append_text {
my $self = shift;
my ($text) = @_;
my $para = Wx::StaticText->new($self, -1, $text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
$para->Wrap($self->{width});
$para->SetMinSize([$self->{width}, -1]);
$self->{vsizer}->Add($para, 0, wxALIGN_LEFT | wxTOP | wxBOTTOM, 10);
}
sub append_option {
my $self = shift;
my ($full_key) = @_;
# populate repository with the factory default
my ($opt_key, $opt_index) = split /#/, $full_key, 2;
$self->config->apply(Slic3r::Config->new_from_defaults($opt_key));
# draw the control
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
parent => $self,
title => '',
config => $self->config,
full_labels => 1,
);
$optgroup->append_single_option_line($opt_key, $opt_index);
$self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
}
sub append_panel {
my ($self, $panel) = @_;
$self->{vsizer}->Add($panel, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
}
sub set_previous_page {
my $self = shift;
my ($previous_page) = @_;
$self->{previous_page} = $previous_page;
}
sub GetPrev {
my $self = shift;
return $self->{previous_page};
}
sub set_next_page {
my $self = shift;
my ($next_page) = @_;
$self->{next_page} = $next_page;
}
sub GetNext {
my $self = shift;
return $self->{next_page};
}
sub get_short_title {
my $self = shift;
return $self->{short_title};
}
sub build_index {
my $self = shift;
my $page = $self;
$self->{index}->prepend_title($page->get_short_title) while ($page = $page->GetPrev);
$page = $self;
$self->{index}->append_title($page->get_short_title) while ($page = $page->GetNext);
}
sub config {
my ($self) = @_;
return $self->GetParent->{config};
}
package Slic3r::GUI::ConfigWizard::Page::Welcome;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, "Welcome to the Slic3r Configuration $wizard", 'Welcome');
$self->append_text('Hello, welcome to Slic3r! This '.lc($wizard).' helps you with the initial configuration; just a few settings and you will be ready to print.');
$self->append_text('To import an existing configuration instead, cancel this '.lc($wizard).' and use the Open Config menu item found in the File menu.');
$self->append_text('To continue, click Next.');
return $self;
}
package Slic3r::GUI::ConfigWizard::Page::Firmware;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Firmware Type');
$self->append_text('Choose the type of firmware used by your printer, then click Next.');
$self->append_option('gcode_flavor');
return $self;
}
package Slic3r::GUI::ConfigWizard::Page::Bed;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Bed Size');
$self->append_text('Set the shape of your printer\'s bed, then click Next.');
$self->config->apply(Slic3r::Config->new_from_defaults('bed_shape'));
$self->{bed_shape_panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $self->config->bed_shape);
$self->{bed_shape_panel}->on_change(sub {
$self->config->set('bed_shape', $self->{bed_shape_panel}->GetValue);
});
$self->append_panel($self->{bed_shape_panel});
return $self;
}
package Slic3r::GUI::ConfigWizard::Page::Nozzle;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Nozzle Diameter');
$self->append_text('Enter the diameter of your printer\'s hot end nozzle, then click Next.');
$self->append_option('nozzle_diameter#0');
return $self;
}
package Slic3r::GUI::ConfigWizard::Page::Filament;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Filament Diameter');
$self->append_text('Enter the diameter of your filament, then click Next.');
$self->append_text('Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.');
$self->append_option('filament_diameter#0');
return $self;
}
package Slic3r::GUI::ConfigWizard::Page::Temperature;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Extrusion Temperature');
$self->append_text('Enter the temperature needed for extruding your filament, then click Next.');
$self->append_text('A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.');
$self->append_option('temperature#0');
return $self;
}
package Slic3r::GUI::ConfigWizard::Page::BedTemperature;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Bed Temperature');
$self->append_text('Enter the bed temperature needed for getting your filament to stick to your heated bed, then click Next.');
$self->append_text('A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.');
$self->append_option('bed_temperature');
return $self;
}
package Slic3r::GUI::ConfigWizard::Page::Finished;
use base 'Slic3r::GUI::ConfigWizard::Page';
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, 'Congratulations!', 'Finish');
$self->append_text("You have successfully completed the Slic3r Configuration $wizard. " .
'Slic3r is now configured for your printer and filament.');
$self->append_text('To close this '.lc($wizard).' and apply the newly created configuration, click Finish.');
return $self;
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Controller.pm 0000664 0000000 0000000 00000015010 13274424355 0017672 0 ustar 00root root 0000000 0000000 # The "Controller" tab to control the printer using serial / USB.
# This feature is rarely used. Much more often, the firmware reads the G-codes from a SD card.
# May there be multiple subtabs per each printer connected?
package Slic3r::GUI::Controller;
use strict;
use warnings;
use utf8;
use List::Util qw(any);
use Wx qw(wxTheApp :frame :id :misc :sizer :bitmap :button :icon :dialog);
use Wx::Event qw(EVT_CLOSE EVT_LEFT_DOWN EVT_MENU);
use base qw(Wx::ScrolledWindow Class::Accessor);
__PACKAGE__->mk_accessors(qw(_selected_printer_preset));
our @ConfigOptions = qw(bed_shape serial_port serial_speed);
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, [600,350]);
$self->SetScrollbars(0, 1, 0, 1);
$self->{sizer} = my $sizer = Wx::BoxSizer->new(wxVERTICAL);
# warning to show when there are no printers configured
{
$self->{text_no_printers} = Wx::StaticText->new($self, -1,
"No printers were configured for USB/serial control.",
wxDefaultPosition, wxDefaultSize);
$self->{sizer}->Add($self->{text_no_printers}, 0, wxTOP | wxLEFT, 30);
}
# button for adding new printer panels
{
my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("add.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
$btn->SetToolTipString("Add printer…")
if $btn->can('SetToolTipString');
EVT_LEFT_DOWN($btn, sub {
my $menu = Wx::Menu->new;
my %presets = map { $_->name => $_ } @{wxTheApp->presets->{printer}};
# remove printers that already exist
my @panels = $self->print_panels;
delete $presets{$_} for map $_->printer_name, @panels;
foreach my $preset_name (sort keys %presets) {
my $preset = $presets{$preset_name};
next if !$preset->dirty_config->serial_port;
my $id = &Wx::NewId();
$menu->Append($id, $preset_name);
EVT_MENU($menu, $id, sub {
$self->add_printer($preset);
});
}
$self->PopupMenu($menu, $btn->GetPosition);
$menu->Destroy;
});
$self->{sizer}->Add($btn, 0, wxTOP | wxLEFT, 10);
}
$self->SetSizer($sizer);
$self->SetMinSize($self->GetSize);
#$sizer->SetSizeHints($self);
EVT_CLOSE($self, sub {
my (undef, $event) = @_;
if ($event->CanVeto) {
foreach my $panel ($self->print_panels) {
if ($panel->printing) {
my $confirm = Wx::MessageDialog->new(
$self, "Printer '" . $panel->printer_name . "' is printing.\n\nDo you want to stop printing?",
'Unfinished Print', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION,
);
if ($confirm->ShowModal == wxID_NO) {
$event->Veto;
return;
}
}
}
}
foreach my $panel ($self->print_panels) {
$panel->disconnect;
}
$event->Skip;
});
$self->Layout;
return $self;
}
sub OnActivate {
my ($self) = @_;
# get all available presets
my %presets = ();
foreach my $preset (@{wxTheApp->presets->{printer}}) {
$preset->load_config;
next if !$preset->dirty_config->serial_port;
$presets{$preset->name} = $preset;
}
# decide which ones we want to keep
my %active = ();
# keep the ones that are currently connected or have jobs in queue
$active{$_} = 1 for map $_->printer_name,
grep { $_->is_connected || @{$_->jobs} > 0 }
$self->print_panels;
if (%presets) {
# if there are no active panels, use sensible defaults
if (!%active && keys %presets <= 2) {
# if only one or two presets exist, load them
$active{$_} = 1 for keys %presets;
}
if (!%active) {
# enable printers whose port is available
my %ports = map { $_ => 1 } wxTheApp->scan_serial_ports;
$active{$_} = 1
for grep exists $ports{$presets{$_}->dirty_config->serial_port}, keys %presets;
}
if (!%active && $self->_selected_printer_preset) {
# enable currently selected printer if it is configured
$active{$self->_selected_printer_preset} = 1
if $presets{$self->_selected_printer_preset};
}
}
# apply changes
for my $panel ($self->print_panels) {
next if $active{$panel->printer_name};
$self->{sizer}->DetachWindow($panel);
$panel->Destroy;
}
$self->add_printer($presets{$_}) for sort keys %active;
# show/hide the warning about no printers
$self->{text_no_printers}->Show(!%presets);
# show/hide the Add button
$self->{btn_add}->Show(keys %presets != keys %active);
$self->Layout;
#Â we need this in order to trigger the OnSize event of wxScrolledWindow which
# recalculates the virtual size
Wx::GetTopLevelParent($self)->SendSizeEvent;
}
sub add_printer {
my ($self, $preset) = @_;
# check that printer doesn't exist already
foreach my $panel ($self->print_panels) {
if ($panel->printer_name eq $preset->name) {
return $panel;
}
}
my $printer_panel = Slic3r::GUI::Controller::PrinterPanel->new($self, $preset);
$self->{sizer}->Prepend($printer_panel, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
$self->Layout;
return $printer_panel;
}
sub print_panels {
my ($self) = @_;
return grep $_->isa('Slic3r::GUI::Controller::PrinterPanel'),
map $_->GetWindow, $self->{sizer}->GetChildren;
}
sub printing {
my ($self) = @_;
return any { $_->printing } $self->print_panels;
}
sub update_presets {
my $self = shift;
my ($group, $presets, $selected, $is_dirty) = @_;
# update configs of currently loaded print panels
foreach my $panel ($self->print_panels) {
foreach my $preset (@$presets) {
if ($panel->printer_name eq $preset->name) {
my $config = $preset->config(\@ConfigOptions);
$panel->config->apply($config);
}
}
}
$self->_selected_printer_preset($presets->[$selected]->name);
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Controller/ 0000775 0000000 0000000 00000000000 13274424355 0017337 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/lib/Slic3r/GUI/Controller/ManualControlDialog.pm 0000664 0000000 0000000 00000023767 13274424355 0023612 0 ustar 00root root 0000000 0000000 package Slic3r::GUI::Controller::ManualControlDialog;
use strict;
use warnings;
use utf8;
use Scalar::Util qw(looks_like_number);
use Slic3r::Geometry qw(PI X Y unscale);
use Wx qw(:dialog :id :misc :sizer :choicebook :button :bitmap :textctrl
wxBORDER_NONE wxTAB_TRAVERSAL);
use Wx::Event qw(EVT_CLOSE EVT_BUTTON EVT_TEXT_ENTER);
use base qw(Wx::Dialog Class::Accessor);
__PACKAGE__->mk_accessors(qw(sender writer config2 x_homed y_homed));
sub new {
my ($class, $parent, $config, $sender, $manual_control_config) = @_;
my $self = $class->SUPER::new($parent, -1, "Manual Control", wxDefaultPosition,
[500,400], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
$self->sender($sender);
$self->writer(Slic3r::GCode::Writer->new);
$self->writer->config->apply_dynamic($config);
$self->config2($manual_control_config);
my $bed_sizer = Wx::FlexGridSizer->new(2, 3, 1, 1);
$bed_sizer->AddGrowableCol(1, 1);
$bed_sizer->AddGrowableRow(0, 1);
my $move_button = sub {
my ($sizer, $label, $icon, $bold, $pos, $handler) = @_;
my $btn = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize,
wxBU_LEFT | wxBU_EXACTFIT);
$btn->SetFont($bold ? $Slic3r::GUI::small_bold_font : $Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("$icon.png"), wxBITMAP_TYPE_PNG));
$btn->SetBitmapPosition($pos);
}
EVT_BUTTON($self, $btn, $handler);
$sizer->Add($btn, 1, wxEXPAND | wxALL, 0);
};
# Y buttons
{
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
for my $d (qw(+10 +1 +0.1)) {
$move_button->($sizer, $d, 'arrow_up', 0, wxLEFT, sub { $self->rel_move('Y', $d) });
}
$move_button->($sizer, 'Y', 'house', 1, wxLEFT, sub { $self->home('Y') });
for my $d (qw(-0.1 -1 -10)) {
$move_button->($sizer, $d, 'arrow_down', 0, wxLEFT, sub { $self->rel_move('Y', $d) });
};
$bed_sizer->Add($sizer, 1, wxEXPAND, 0);
}
# Bed canvas
{
my $bed_shape = $config->bed_shape;
$self->{canvas} = my $canvas = Slic3r::GUI::2DBed->new($self, $bed_shape);
$canvas->interactive(1);
$canvas->on_move(sub {
my ($pos) = @_;
if (!($self->x_homed && $self->y_homed)) {
Slic3r::GUI::show_error($self, "Please home both X and Y before moving.");
return ;
}
# delete any pending commands to get a smoother movement
$self->sender->purge_queue(1);
$self->abs_xy_move($pos);
});
$bed_sizer->Add($canvas, 0, wxEXPAND | wxRIGHT, 3);
}
# Z buttons
{
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
for my $d (qw(+10 +1 +0.1)) {
$move_button->($sizer, $d, 'arrow_up', 0, wxLEFT, sub { $self->rel_move('Z', $d) });
}
$move_button->($sizer, 'Z', 'house', 1, wxLEFT, sub { $self->home('Z') });
for my $d (qw(-0.1 -1 -10)) {
$move_button->($sizer, $d, 'arrow_down', 0, wxLEFT, sub { $self->rel_move('Z', $d) });
};
$bed_sizer->Add($sizer, 1, wxEXPAND, 0);
}
# XYZ home button
$move_button->($bed_sizer, 'XYZ', 'house', 1, wxTOP, sub { $self->home(undef) });
# X buttons
{
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
for my $d (qw(-10 -1 -0.1)) {
$move_button->($sizer, $d, 'arrow_left', 0, wxTOP, sub { $self->rel_move('X', $d) });
}
$move_button->($sizer, 'X', 'house', 1, wxTOP, sub { $self->home('X') });
for my $d (qw(+0.1 +1 +10)) {
$move_button->($sizer, $d, 'arrow_right', 0, wxTOP, sub { $self->rel_move('X', $d) });
}
$bed_sizer->Add($sizer, 1, wxEXPAND, 0);
}
my $optgroup = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Manual motion settings',
on_change => sub {
my ($opt_id, $value) = @_;
$self->config2->{$opt_id} = $value;
},
);
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Speed (mm/s)',
);
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'xy_travel_speed',
type => 'f',
label => 'X/Y',
tooltip => '',
default => $self->config2->{xy_travel_speed},
));
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'z_travel_speed',
type => 'f',
label => 'Z',
tooltip => '',
default => $self->config2->{z_travel_speed},
));
$optgroup->append_line($line);
}
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
$left_sizer->Add($bed_sizer, 1, wxEXPAND | wxALL, 10);
$left_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10);
my $right_sizer = Wx::BoxSizer->new(wxVERTICAL);
{
my $optgroup = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Temperature',
on_change => sub {
my ($opt_id, $value) = @_;
$self->config2->{$opt_id} = $value;
},
);
$right_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10);
{
my $line = $optgroup->create_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'temperature',
type => 's',
label => 'Extruder',
default => '',
sidetext => '°C',
default => $self->config2->{temperature},
));
$line->append_button("Set", "tick.png", sub {
if (!looks_like_number($self->config2->{temperature})) {
Slic3r::GUI::show_error($self, "Invalid temperature.");
return;
}
my $cmd = $self->writer->set_temperature($self->config2->{temperature});
$self->GetParent->append_to_log(">> $cmd\n");
$self->sender->send($cmd, 1);
});
$optgroup->append_line($line);
}
{
my $line = $optgroup->create_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'bed_temperature',
type => 's',
label => 'Bed',
default => '',
sidetext => '°C',
default => $self->config2->{bed_temperature},
));
$line->append_button("Set", "tick.png", sub {
if (!looks_like_number($self->config2->{bed_temperature})) {
Slic3r::GUI::show_error($self, "Invalid bed temperature.");
return;
}
my $cmd = $self->writer->set_bed_temperature($self->config2->{bed_temperature});
$self->GetParent->append_to_log(">> $cmd\n");
$self->sender->send($cmd, 1);
});
$optgroup->append_line($line);
}
}
{
my $box = Wx::StaticBox->new($self, -1, "Send manual command");
my $sbsizer = Wx::StaticBoxSizer->new($box, wxVERTICAL);
$right_sizer->Add($sbsizer, 1, wxEXPAND | wxALL, 10);
my $cmd_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
my $cmd_textctrl = Wx::TextCtrl->new($self, -1, '', wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
$cmd_sizer->Add($cmd_textctrl, 1, wxEXPAND, 0);
my $btn = Wx::Button->new($self, -1,
"Send", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
$btn->SetFont($Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog_go.png"), wxBITMAP_TYPE_PNG));
}
$cmd_sizer->Add($btn, 0, wxEXPAND | wxLEFT, 5);
my $do_send = sub {
my $cmd = $cmd_textctrl->GetValue;
return if $cmd eq '';
$self->GetParent->append_to_log(">> $cmd\n");
$self->sender->send($cmd, 1);
$cmd_textctrl->SetValue('');
};
EVT_BUTTON($self, $btn, $do_send);
EVT_TEXT_ENTER($self, $cmd_textctrl, $do_send);
$sbsizer->Add($cmd_sizer, 0, wxEXPAND | wxTOP, 5);
}
my $main_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$main_sizer->Add($left_sizer, 1, wxEXPAND | wxRIGHT, 10);
$main_sizer->Add($right_sizer, 0, wxEXPAND, 0);
$self->SetSizer($main_sizer);
$self->SetMinSize($self->GetSize);
$main_sizer->SetSizeHints($self);
$self->Layout;
# needed to actually free memory
EVT_CLOSE($self, sub {
$self->EndModal(wxID_OK);
$self->Destroy;
});
return $self;
}
sub abs_xy_move {
my ($self, $pos) = @_;
$self->sender->send("G90", 1); #Â set absolute positioning
$self->sender->send(sprintf("G1 X%.1f Y%.1f F%d", @$pos, $self->config2->{xy_travel_speed}*60), 1);
$self->{canvas}->set_pos($pos);
}
sub rel_move {
my ($self, $axis, $distance) = @_;
my $speed = ($axis eq 'Z') ? $self->config2->{z_travel_speed} : $self->config2->{xy_travel_speed};
$self->sender->send("G91", 1); #Â set relative positioning
$self->sender->send(sprintf("G1 %s%.1f F%d", $axis, $distance, $speed*60), 1);
$self->sender->send("G90", 1); #Â set absolute positioning
if (my $pos = $self->{canvas}->pos) {
if ($axis eq 'X') {
$pos->translate($distance, 0);
} elsif ($axis eq 'Y') {
$pos->translate(0, $distance);
}
$self->{canvas}->set_pos($pos);
}
}
sub home {
my ($self, $axis) = @_;
$axis //= '';
$self->sender->send(sprintf("G28 %s", $axis), 1);
$self->{canvas}->set_pos(undef);
$self->x_homed(1) if $axis eq 'X';
$self->y_homed(1) if $axis eq 'Y';
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Controller/PrinterPanel.pm 0000664 0000000 0000000 00000066523 13274424355 0022314 0 ustar 00root root 0000000 0000000 package Slic3r::GUI::Controller::PrinterPanel;
use strict;
use warnings;
use utf8;
use List::Util qw(first);
use Wx qw(wxTheApp :panel :id :misc :sizer :button :bitmap :window :gauge :timer
:textctrl :font :systemsettings);
use Wx::Event qw(EVT_BUTTON EVT_MOUSEWHEEL EVT_TIMER EVT_SCROLLWIN);
use base qw(Wx::Panel Class::Accessor);
__PACKAGE__->mk_accessors(qw(printer_name config sender jobs
printing status_timer temp_timer manual_control_config));
use constant CONNECTION_TIMEOUT => 3; # seconds
use constant STATUS_TIMER_INTERVAL => 1000; # milliseconds
use constant TEMP_TIMER_INTERVAL => 5000; # milliseconds
sub new {
my ($class, $parent, $preset) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, [500, 250]);
$self->printer_name($preset->name);
$self->config($preset->dirty_config);
$self->manual_control_config({
xy_travel_speed => 130,
z_travel_speed => 10,
temperature => '',
bed_temperature => '',
});
$self->jobs([]);
# set up the timer that polls for updates
{
my $timer_id = &Wx::NewId();
$self->status_timer(Wx::Timer->new($self, $timer_id));
EVT_TIMER($self, $timer_id, sub {
my ($self, $event) = @_;
if ($self->printing) {
my $queue_size = $self->sender->queue_size;
$self->{gauge}->SetValue($self->{gauge}->GetRange - $queue_size);
if ($queue_size == 0) {
$self->print_completed;
}
}
$self->append_to_log("$_\n") for @{$self->sender->purge_log};
{
my $temp = $self->sender->getT;
if ($temp eq '') {
$self->{temp_panel}->Hide;
} else {
if (!$self->{temp_panel}->IsShown) {
$self->{temp_panel}->Show;
$self->Layout;
}
$self->{temp_text}->SetLabel($temp . "°C");
$temp = $self->sender->getB;
if ($temp eq '') {
$self->{bed_temp_text}->SetLabel('n.a.');
} else {
$self->{bed_temp_text}->SetLabel($temp . "°C");
}
}
}
});
}
# set up the timer that sends temperature requests
#Â (responses are handled by status_timer)
{
my $timer_id = &Wx::NewId();
$self->temp_timer(Wx::Timer->new($self, $timer_id));
EVT_TIMER($self, $timer_id, sub {
my ($self, $event) = @_;
$self->sender->send("M105", 1); # send it through priority queue
});
}
my $box = Wx::StaticBox->new($self, -1, "");
my $sizer = Wx::StaticBoxSizer->new($box, wxHORIZONTAL);
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
# printer name
{
my $text = Wx::StaticText->new($self, -1, $self->printer_name, wxDefaultPosition, [220,-1]);
my $font = $text->GetFont;
$font->SetPointSize(20);
$text->SetFont($font);
$left_sizer->Add($text, 0, wxEXPAND, 0);
}
# connection info
{
my $conn_sizer = Wx::FlexGridSizer->new(2, 2, 1, 0);
$conn_sizer->SetFlexibleDirection(wxHORIZONTAL);
$conn_sizer->AddGrowableCol(1, 1);
$left_sizer->Add($conn_sizer, 0, wxEXPAND | wxTOP, 5);
{
my $text = Wx::StaticText->new($self, -1, "Port:", wxDefaultPosition, wxDefaultSize);
$text->SetFont($Slic3r::GUI::small_font);
$conn_sizer->Add($text, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
}
my $serial_port_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
{
$self->{serial_port_combobox} = Wx::ComboBox->new($self, -1, $self->config->serial_port, wxDefaultPosition, wxDefaultSize, []);
$self->{serial_port_combobox}->SetFont($Slic3r::GUI::small_font);
$self->update_serial_ports;
$serial_port_sizer->Add($self->{serial_port_combobox}, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 1);
}
{
$self->{btn_rescan_serial} = my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE);
$btn->SetToolTipString("Rescan serial ports")
if $btn->can('SetToolTipString');
$serial_port_sizer->Add($btn, 0, wxALIGN_CENTER_VERTICAL, 0);
EVT_BUTTON($self, $btn, sub { $self->update_serial_ports });
}
$conn_sizer->Add($serial_port_sizer, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
{
my $text = Wx::StaticText->new($self, -1, "Speed:", wxDefaultPosition, wxDefaultSize);
$text->SetFont($Slic3r::GUI::small_font);
$conn_sizer->Add($text, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
}
my $serial_speed_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
{
$self->{serial_speed_combobox} = Wx::ComboBox->new($self, -1, $self->config->serial_speed, wxDefaultPosition, wxDefaultSize,
["57600", "115200", "250000"]);
$self->{serial_speed_combobox}->SetFont($Slic3r::GUI::small_font);
$serial_speed_sizer->Add($self->{serial_speed_combobox}, 0, wxALIGN_CENTER_VERTICAL, 0);
}
{
$self->{btn_disconnect} = my $btn = Wx::Button->new($self, -1, "Disconnect", wxDefaultPosition, wxDefaultSize);
$btn->SetFont($Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG));
}
$serial_speed_sizer->Add($btn, 0, wxLEFT, 5);
EVT_BUTTON($self, $btn, \&disconnect);
}
$conn_sizer->Add($serial_speed_sizer, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
}
# buttons
{
$self->{btn_connect} = my $btn = Wx::Button->new($self, -1, "Connect to printer", wxDefaultPosition, [-1, 40]);
my $font = $btn->GetFont;
$font->SetPointSize($font->GetPointSize + 2);
$btn->SetFont($font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("arrow_up.png"), wxBITMAP_TYPE_PNG));
}
$left_sizer->Add($btn, 0, wxTOP, 15);
EVT_BUTTON($self, $btn, \&connect);
}
# print progress bar
{
my $gauge = $self->{gauge} = Wx::Gauge->new($self, wxGA_HORIZONTAL, 100, wxDefaultPosition, wxDefaultSize);
$left_sizer->Add($self->{gauge}, 0, wxEXPAND | wxTOP, 15);
$gauge->Hide;
}
# status
$self->{status_text} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, [200,-1]);
$left_sizer->Add($self->{status_text}, 1, wxEXPAND | wxTOP, 15);
# manual control
{
$self->{btn_manual_control} = my $btn = Wx::Button->new($self, -1, "Manual control", wxDefaultPosition, wxDefaultSize);
$btn->SetFont($Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG));
}
$btn->Hide;
$left_sizer->Add($btn, 0, wxTOP, 15);
EVT_BUTTON($self, $btn, sub {
$self->{manual_control_dialog} = my $dlg = Slic3r::GUI::Controller::ManualControlDialog->new
($self, $self->config, $self->sender, $self->manual_control_config);
$dlg->ShowModal;
undef $self->{manual_control_dialog};
});
}
# temperature
{
my $temp_panel = $self->{temp_panel} = Wx::Panel->new($self, -1);
my $temp_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
my $temp_font = Wx::Font->new($Slic3r::GUI::small_font);
$temp_font->SetWeight(wxFONTWEIGHT_BOLD);
{
my $text = Wx::StaticText->new($temp_panel, -1, "Temperature:", wxDefaultPosition, wxDefaultSize);
$text->SetFont($Slic3r::GUI::small_font);
$temp_sizer->Add($text, 0, wxALIGN_CENTER_VERTICAL);
$self->{temp_text} = Wx::StaticText->new($temp_panel, -1, "", wxDefaultPosition, wxDefaultSize);
$self->{temp_text}->SetFont($temp_font);
$self->{temp_text}->SetForegroundColour(Wx::wxRED);
$temp_sizer->Add($self->{temp_text}, 1, wxALIGN_CENTER_VERTICAL);
}
{
my $text = Wx::StaticText->new($temp_panel, -1, "Bed:", wxDefaultPosition, wxDefaultSize);
$text->SetFont($Slic3r::GUI::small_font);
$temp_sizer->Add($text, 0, wxALIGN_CENTER_VERTICAL);
$self->{bed_temp_text} = Wx::StaticText->new($temp_panel, -1, "", wxDefaultPosition, wxDefaultSize);
$self->{bed_temp_text}->SetFont($temp_font);
$self->{bed_temp_text}->SetForegroundColour(Wx::wxRED);
$temp_sizer->Add($self->{bed_temp_text}, 1, wxALIGN_CENTER_VERTICAL);
}
$temp_panel->SetSizer($temp_sizer);
$temp_panel->Hide;
$left_sizer->Add($temp_panel, 0, wxEXPAND | wxTOP | wxBOTTOM, 4);
}
# print jobs panel
$self->{print_jobs_sizer} = my $print_jobs_sizer = Wx::BoxSizer->new(wxVERTICAL);
{
my $text = Wx::StaticText->new($self, -1, "Queue:", wxDefaultPosition, wxDefaultSize);
$text->SetFont($Slic3r::GUI::small_font);
$print_jobs_sizer->Add($text, 0, wxEXPAND, 0);
$self->{jobs_panel} = Wx::ScrolledWindow->new($self, -1, wxDefaultPosition, wxDefaultSize,
wxVSCROLL | wxBORDER_NONE);
$self->{jobs_panel}->SetScrollbars(0, 1, 0, 1);
$self->{jobs_panel_sizer} = Wx::BoxSizer->new(wxVERTICAL);
$self->{jobs_panel}->SetSizer($self->{jobs_panel_sizer});
$print_jobs_sizer->Add($self->{jobs_panel}, 1, wxEXPAND, 0);
# TODO: fix this. We're trying to pass the scroll event to the parent but it
# doesn't work.
EVT_SCROLLWIN($self->{jobs_panel}, sub {
my ($panel, $event) = @_;
my $controller = $self->GetParent;
my $new_event = Wx::ScrollWinEvent->new(
$event->GetEventType,
$event->GetPosition,
$event->GetOrientation,
);
$controller->ProcessEvent($new_event);
}) if 0;
}
my $log_sizer = Wx::BoxSizer->new(wxVERTICAL);
{
my $text = Wx::StaticText->new($self, -1, "Log:", wxDefaultPosition, wxDefaultSize);
$text->SetFont($Slic3r::GUI::small_font);
$log_sizer->Add($text, 0, wxEXPAND, 0);
my $log = $self->{log_textctrl} = Wx::TextCtrl->new($self, -1, "", wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE | wxBORDER_NONE);
$log->SetBackgroundColour($self->GetBackgroundColour);
$log->SetFont($Slic3r::GUI::small_font);
$log->SetEditable(0);
$log_sizer->Add($self->{log_textctrl}, 1, wxEXPAND, 0);
}
$sizer->Add($left_sizer, 0, wxEXPAND | wxALL, 0);
$sizer->Add($print_jobs_sizer, 2, wxEXPAND | wxALL, 0);
$sizer->Add($log_sizer, 1, wxEXPAND | wxLEFT, 15);
$self->SetSizer($sizer);
$self->SetMinSize($self->GetSize);
$self->_update_connection_controls;
return $self;
}
sub append_to_log {
my ($self, $text) = @_;
$self->{log_textctrl}->AppendText($text);
}
sub is_connected {
my ($self) = @_;
return $self->sender && $self->sender->is_connected;
}
sub _update_connection_controls {
my ($self) = @_;
$self->{btn_connect}->Show;
$self->{btn_disconnect}->Hide;
$self->{serial_port_combobox}->Enable;
$self->{serial_speed_combobox}->Enable;
$self->{btn_rescan_serial}->Enable;
$self->{btn_manual_control}->Hide;
$self->{btn_manual_control}->Disable;
if ($self->is_connected) {
$self->{btn_connect}->Hide;
$self->{btn_manual_control}->Show;
if (!$self->printing || $self->printing->paused) {
$self->{btn_disconnect}->Show;
$self->{btn_manual_control}->Enable;
}
$self->{serial_port_combobox}->Disable;
$self->{serial_speed_combobox}->Disable;
$self->{btn_rescan_serial}->Disable;
}
$self->Layout;
}
sub set_status {
my ($self, $status) = @_;
$self->{status_text}->SetLabel($status);
$self->{status_text}->Wrap($self->{status_text}->GetSize->GetWidth);
$self->{status_text}->Refresh;
$self->Layout;
}
sub connect {
my ($self) = @_;
return if $self->is_connected;
$self->set_status("Connecting...");
$self->sender(Slic3r::GCode::Sender->new);
my $res = $self->sender->connect(
$self->{serial_port_combobox}->GetValue,
$self->{serial_speed_combobox}->GetValue,
);
if (!$res) {
$self->set_status("Connection failed. Check serial port and speed.");
} else {
if ($self->sender->wait_connected) {
$self->set_status("Printer is online. You can now start printing from the queue on the right.");
$self->status_timer->Start(STATUS_TIMER_INTERVAL, wxTIMER_CONTINUOUS);
$self->temp_timer->Start(TEMP_TIMER_INTERVAL, wxTIMER_CONTINUOUS);
# request temperature now, without waiting for the timer
$self->sender->send("M105", 1);
# Update the printer preset with the new connection info
{
my $preset = first { $_->name eq $self->printer_name } @{wxTheApp->presets->{printer}};
if ($preset) {
$preset->load_config;
$preset->_dirty_config->set('serial_port', $self->{serial_port_combobox}->GetValue);
$preset->_dirty_config->set('serial_speed', $self->{serial_speed_combobox}->GetValue);
$preset->save([ 'serial_port', 'serial_speed' ]);
}
}
} else {
$self->set_status("Connection failed. Check serial port and speed.");
}
}
$self->_update_connection_controls;
$self->reload_jobs;
}
sub disconnect {
my ($self) = @_;
$self->status_timer->Stop;
$self->temp_timer->Stop;
return if !$self->is_connected;
$self->printing->printing(0) if $self->printing;
$self->printing(undef);
$self->{gauge}->Hide;
$self->{temp_panel}->Hide;
$self->sender->disconnect;
$self->set_status("");
$self->_update_connection_controls;
$self->reload_jobs;
}
sub update_serial_ports {
my ($self) = @_;
my $cb = $self->{serial_port_combobox};
my $current = $cb->GetValue;
$cb->Clear;
$cb->Append($_) for wxTheApp->scan_serial_ports;
$cb->SetValue($current);
}
sub load_print_job {
my ($self, $gcode_file, $filament_stats) = @_;
push @{$self->jobs}, my $job = Slic3r::GUI::Controller::PrinterPanel::PrintJob->new(
id => time() . $gcode_file . rand(1000),
gcode_file => $gcode_file,
filament_stats => $filament_stats,
);
$self->reload_jobs;
return $job;
}
sub delete_job {
my ($self, $job) = @_;
$self->jobs([ grep $_->id ne $job->id, @{$self->jobs} ]);
$self->reload_jobs;
}
sub print_job {
my ($self, $job) = @_;
$self->printing($job);
$job->printing(1);
$self->reload_jobs;
open my $fh, '<', $job->gcode_file;
my $line_count = 0;
while (my $row = <$fh>) {
$self->sender->send($row);
$line_count++;
}
close $fh;
$self->_update_connection_controls;
$self->{gauge}->SetRange($line_count);
$self->{gauge}->SetValue(0);
$self->{gauge}->Enable;
$self->{gauge}->Show;
$self->Layout;
$self->set_status('Printing...');
$self->append_to_log(sprintf "=====\n");
$self->append_to_log(sprintf "Printing %s\n", $job->name);
$self->append_to_log(sprintf "Print started at %s\n", $self->_timestamp);
}
sub print_completed {
my ($self) = @_;
my $job = $self->printing;
$self->printing(undef);
$job->printing(0);
$job->printed(1);
$self->_update_connection_controls;
$self->{gauge}->Hide;
$self->Layout;
$self->set_status('Print completed.');
$self->append_to_log(sprintf "Print completed at %s\n", $self->_timestamp);
$self->reload_jobs;
}
sub reload_jobs {
my ($self) = @_;
# reorder jobs
@{$self->jobs} = sort { ($a->printed <=> $b->printed) || ($a->timestamp <=> $b->timestamp) }
@{$self->jobs};
# remove all panels
foreach my $child ($self->{jobs_panel_sizer}->GetChildren) {
my $window = $child->GetWindow;
$self->{jobs_panel_sizer}->Detach($window);
# now $child does not exist anymore
$window->Destroy;
}
# re-add all panels
foreach my $job (@{$self->jobs}) {
my $panel = Slic3r::GUI::Controller::PrinterPanel::PrintJobPanel->new($self->{jobs_panel}, $job);
$self->{jobs_panel_sizer}->Add($panel, 0, wxEXPAND | wxBOTTOM, 5);
$panel->on_delete_job(sub {
my ($job) = @_;
$self->delete_job($job);
});
$panel->on_print_job(sub {
my ($job) = @_;
$self->print_job($job);
});
$panel->on_pause_print(sub {
my ($job) = @_;
$self->sender->pause_queue;
$job->paused(1);
$self->reload_jobs;
$self->_update_connection_controls;
$self->{gauge}->Disable;
$self->set_status('Print is paused. Click on Resume to continue.');
});
$panel->on_abort_print(sub {
my ($job) = @_;
$self->sender->purge_queue;
$self->printing(undef);
$job->printing(0);
$job->paused(0);
$self->reload_jobs;
$self->_update_connection_controls;
$self->{gauge}->Disable;
$self->{gauge}->Hide;
$self->set_status('Print was aborted.');
$self->append_to_log(sprintf "Print aborted at %s\n", $self->_timestamp);
});
$panel->on_resume_print(sub {
my ($job) = @_;
$self->sender->resume_queue;
$job->paused(0);
$self->reload_jobs;
$self->_update_connection_controls;
$self->{gauge}->Enable;
$self->set_status('Printing...');
});
$panel->enable_print if $self->is_connected && !$self->printing;
EVT_MOUSEWHEEL($panel, sub {
my (undef, $event) = @_;
Wx::PostEvent($self->{jobs_panel}, $event);
$event->Skip;
});
}
$self->{jobs_panel}->Layout;
$self->{print_jobs_sizer}->Layout;
}
sub _timestamp {
my ($self) = @_;
my @time = localtime(time);
return sprintf '%02d:%02d:%02d', @time[2,1,0];
}
package Slic3r::GUI::Controller::PrinterPanel::PrintJob;
use Moo;
use File::Basename qw(basename);
has 'id' => (is => 'ro', required => 1);
has 'timestamp' => (is => 'ro', default => sub { time });
has 'gcode_file' => (is => 'ro', required => 1);
has 'filament_stats' => (is => 'rw');
has 'printing' => (is => 'rw', default => sub { 0 });
has 'paused' => (is => 'rw', default => sub { 0 });
has 'printed' => (is => 'rw', default => sub { 0 });
sub name {
my ($self) = @_;
return basename($self->gcode_file);
}
package Slic3r::GUI::Controller::PrinterPanel::PrintJobPanel;
use strict;
use warnings;
use utf8;
use Wx qw(wxTheApp :panel :id :misc :sizer :button :bitmap :font :dialog :icon :timer
:colour :brush :pen);
use Wx::Event qw(EVT_BUTTON EVT_TIMER EVT_ERASE_BACKGROUND);
use base qw(Wx::Panel Class::Accessor);
__PACKAGE__->mk_accessors(qw(job on_delete_job on_print_job on_pause_print on_resume_print
on_abort_print blink_timer duration queued));
sub new {
my ($class, $parent, $job) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize);
# Estimate print duration
{
my $estimator = Slic3r::GCode::TimeEstimator->new;
$estimator->parse_file($job->gcode_file);
$self->duration($estimator->time);
}
$self->job($job);
$self->queued(scalar localtime);
$self->SetBackgroundColour(Wx::SystemSettings::GetColour(Wx::wxSYS_COLOUR_LISTBOX));
{
my $white_brush = Wx::Brush->new($self->GetBackgroundColour, wxSOLID);
my $pen = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
EVT_ERASE_BACKGROUND($self, sub {
my ($self, $event) = @_;
my $dc = $event->GetDC;
my $size = $self->GetSize;
$dc->SetBrush($white_brush);
$dc->SetPen($pen);
$dc->DrawRoundedRectangle(0, 0, $size->GetWidth,$size->GetHeight, 6);
});
}
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
{
$self->{job_name_textctrl} = my $text = Wx::StaticText->new($self, -1, $job->name, wxDefaultPosition, wxDefaultSize);
my $font = $text->GetFont;
$font->SetWeight(wxFONTWEIGHT_BOLD);
$text->SetFont($font);
if ($job->printed) {
$text->SetForegroundColour($Slic3r::GUI::grey);
}
$text->SetToolTipString("Queued on " . $self->queued)
if $text->can('SetToolTipString');
$left_sizer->Add($text, 0, wxEXPAND, 0);
}
{
my $stats = join "\n",
map "$_ (" . sprintf("%.2f", $job->filament_stats->{$_}/1000) . "m)",
sort keys %{$job->filament_stats};
$stats .= sprintf "\nEstimated time: %d hours and %d minutes",
int($self->duration/3600), int($self->duration/60) % 60;
my $text = Wx::StaticText->new($self, -1, $stats, wxDefaultPosition, wxDefaultSize);
$text->SetFont($Slic3r::GUI::small_font);
if ($job->printed && !$job->printing) {
$text->SetForegroundColour($Slic3r::GUI::grey);
}
$left_sizer->Add($text, 0, wxEXPAND | wxTOP, 6);
}
my $buttons_sizer = Wx::BoxSizer->new(wxVERTICAL);
my $button_style = Wx::wxBORDER_NONE | wxBU_EXACTFIT;
{
my $btn = $self->{btn_delete} = Wx::Button->new($self, -1, 'Delete',
wxDefaultPosition, wxDefaultSize, $button_style);
$btn->SetToolTipString("Delete this job from print queue")
if $btn->can('SetToolTipString');
$btn->SetFont($Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG));
}
if ($job->printing) {
$btn->Hide;
}
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
EVT_BUTTON($self, $btn, sub {
my $res = Wx::MessageDialog->new($self, "Are you sure you want to delete this print job?", 'Delete Job', wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION)->ShowModal;
return unless $res == wxID_YES;
wxTheApp->CallAfter(sub {
$self->on_delete_job->($job);
});
});
}
{
my $label = $job->printed ? 'Print Again' : 'Print This';
my $btn = $self->{btn_print} = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize,
$button_style);
$btn->SetFont($Slic3r::GUI::small_bold_font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_play.png"), wxBITMAP_TYPE_PNG));
$btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_play_blue.png"), wxBITMAP_TYPE_PNG));
#$btn->SetBitmapPosition(wxRIGHT);
}
$btn->Hide;
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
EVT_BUTTON($self, $btn, sub {
wxTheApp->CallAfter(sub {
$self->on_print_job->($job);
});
});
}
{
my $btn = $self->{btn_pause} = Wx::Button->new($self, -1, "Pause", wxDefaultPosition, wxDefaultSize,
$button_style);
$btn->SetFont($Slic3r::GUI::small_font);
if (!$job->printing || $job->paused) {
$btn->Hide;
}
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_pause.png"), wxBITMAP_TYPE_PNG));
$btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_pause_blue.png"), wxBITMAP_TYPE_PNG));
}
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
EVT_BUTTON($self, $btn, sub {
wxTheApp->CallAfter(sub {
$self->on_pause_print->($job);
});
});
}
{
my $btn = $self->{btn_resume} = Wx::Button->new($self, -1, "Resume", wxDefaultPosition, wxDefaultSize,
$button_style);
$btn->SetFont($Slic3r::GUI::small_font);
if (!$job->printing || !$job->paused) {
$btn->Hide;
}
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_play.png"), wxBITMAP_TYPE_PNG));
$btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_play_blue.png"), wxBITMAP_TYPE_PNG));
}
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
EVT_BUTTON($self, $btn, sub {
wxTheApp->CallAfter(sub {
$self->on_resume_print->($job);
});
});
}
{
my $btn = $self->{btn_abort} = Wx::Button->new($self, -1, "Abort", wxDefaultPosition, wxDefaultSize,
$button_style);
$btn->SetFont($Slic3r::GUI::small_font);
if (!$job->printing) {
$btn->Hide;
}
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_stop.png"), wxBITMAP_TYPE_PNG));
$btn->SetBitmapCurrent(Wx::Bitmap->new($Slic3r::var->("control_stop_blue.png"), wxBITMAP_TYPE_PNG));
}
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
EVT_BUTTON($self, $btn, sub {
wxTheApp->CallAfter(sub {
$self->on_abort_print->($job);
});
});
}
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($left_sizer, 1, wxEXPAND | wxALL, 6);
$sizer->Add($buttons_sizer, 0, wxEXPAND | wxALL, 6);
$self->SetSizer($sizer);
# set-up the timer that changes the job name color while printing
if ($self->job->printing && !$self->job->paused) {
my $timer_id = &Wx::NewId();
$self->blink_timer(Wx::Timer->new($self, $timer_id));
my $blink = 0; # closure
my $colour = Wx::Colour->new(0, 190, 0);
EVT_TIMER($self, $timer_id, sub {
my ($self, $event) = @_;
$self->{job_name_textctrl}->SetForegroundColour($blink ? Wx::wxBLACK : $colour);
$blink = !$blink;
});
$self->blink_timer->Start(1000, wxTIMER_CONTINUOUS);
}
return $self;
}
sub enable_print {
my ($self) = @_;
if (!$self->job->printing) {
$self->{btn_print}->Show;
}
$self->Layout;
}
sub Destroy {
my ($self) = @_;
# There's a gap between the time Perl destroys the wxPanel object and
# the blink_timer member, so the wxTimer might still fire an event which
# isn't handled properly, causing a crash. So we ensure that blink_timer
#Â is stopped before we destroy the wxPanel.
$self->blink_timer->Stop if $self->blink_timer;
return $self->SUPER::Destroy;
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/MainFrame.pm 0000664 0000000 0000000 00000074651 13274424355 0017426 0 ustar 00root root 0000000 0000000 # The main frame, the parent of all.
package Slic3r::GUI::MainFrame;
use strict;
use warnings;
use utf8;
use File::Basename qw(basename dirname);
use List::Util qw(min);
use Slic3r::Geometry qw(X Y Z);
use Wx qw(:frame :bitmap :id :misc :panel :sizer :menu :dialog :filedialog
:font :icon :aui wxTheApp);
use Wx::AUI;
use Wx::Event qw(EVT_CLOSE EVT_AUINOTEBOOK_PAGE_CHANGED EVT_AUINOTEBOOK_PAGE_CLOSE);
use base 'Wx::Frame';
our $qs_last_input_file;
our $qs_last_output_file;
our $last_config;
sub new {
my ($class) = @_;
my $self = $class->SUPER::new(undef, -1, 'Slic3r', wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE);
if ($^O eq 'MSWin32') {
$self->SetIcon(Wx::Icon->new($Slic3r::var->("Slic3r.ico"), wxBITMAP_TYPE_ICO));
} else {
$self->SetIcon(Wx::Icon->new($Slic3r::var->("Slic3r_128px.png"), wxBITMAP_TYPE_PNG));
}
$self->{loaded} = 0;
$self->{preset_editor_tabs} = {}; # group => panel
# initialize tabpanel and menubar
$self->_init_tabpanel;
$self->_init_menubar;
# set default tooltip timer in msec
# SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values
# (SetAutoPop is not available on GTK.)
eval { Wx::ToolTip::SetAutoPop(32767) };
# initialize status bar
$self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new($self, -1);
$self->{statusbar}->SetStatusText("Version $Slic3r::VERSION - Remember to check for updates at http://slic3r.org/");
$self->SetStatusBar($self->{statusbar});
$self->{loaded} = 1;
# initialize layout
{
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($self->{tabpanel}, 1, wxEXPAND);
$sizer->SetSizeHints($self);
$self->SetSizer($sizer);
$self->Fit;
$self->SetMinSize([760, 490]);
$self->SetSize($self->GetMinSize);
wxTheApp->restore_window_pos($self, "main_frame");
$self->Show;
$self->Layout;
}
# declare events
EVT_CLOSE($self, sub {
my (undef, $event) = @_;
if ($event->CanVeto) {
if (!$self->{plater}->prompt_unsaved_changes) {
$event->Veto;
return;
}
if ($self->{controller} && $self->{controller}->printing) {
my $confirm = Wx::MessageDialog->new($self, "You are currently printing. Do you want to stop printing and continue anyway?",
'Unfinished Print', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
if ($confirm->ShowModal == wxID_NO) {
$event->Veto;
return;
}
}
}
# save window size
wxTheApp->save_window_pos($self, "main_frame");
# propagate event
$event->Skip;
});
return $self;
}
sub _init_tabpanel {
my ($self) = @_;
$self->{tabpanel} = my $panel = Wx::AuiNotebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxAUI_NB_TOP);
EVT_AUINOTEBOOK_PAGE_CHANGED($self, $self->{tabpanel}, sub {
my $panel = $self->{tabpanel}->GetPage($self->{tabpanel}->GetSelection);
$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);
}
});
EVT_AUINOTEBOOK_PAGE_CLOSE($self, $self->{tabpanel}, sub {
my $panel = $self->{tabpanel}->GetPage($self->{tabpanel}->GetSelection);
if ($panel->isa('Slic3r::GUI::PresetEditor')) {
delete $self->{preset_editor_tabs}{$panel->name};
}
wxTheApp->CallAfter(sub {
$self->{tabpanel}->SetSelection(0);
});
});
$panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater");
$panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), "Controller")
if ($Slic3r::GUI::Settings->{_}{show_host});
}
sub _init_menubar {
my ($self) = @_;
# File menu
my $fileMenu = Wx::Menu->new;
{
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 {
$self->{plater}->add_tin if $self->{plater};
}, undef, 'map_add.png');
$fileMenu->AppendSeparator();
wxTheApp->append_menu_item($fileMenu, "&Load Config…\tCtrl+L", 'Load exported configuration file', sub {
$self->load_config_file;
}, undef, 'plugin_add.png');
wxTheApp->append_menu_item($fileMenu, "&Export Config…\tCtrl+E", 'Export current configuration to file', sub {
$self->export_config;
}, undef, 'plugin_go.png');
wxTheApp->append_menu_item($fileMenu, "&Load Config Bundle…", 'Load presets from a bundle', sub {
$self->load_configbundle;
}, undef, 'lorry_add.png');
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 {
wxTheApp->CallAfter(sub {
$self->quick_slice;
$repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file);
});
}, undef, 'cog_go.png');
wxTheApp->append_menu_item($fileMenu, "Quick Slice and Save &As…\tCtrl+Alt+U", 'Slice file and save as', sub {
wxTheApp->CallAfter(sub {
$self->quick_slice(save_as => 1);
$repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file);
});
}, undef, 'cog_go.png');
$repeat = wxTheApp->append_menu_item($fileMenu, "&Repeat Last Quick Slice\tCtrl+Shift+U", 'Repeat last quick slice', sub {
wxTheApp->CallAfter(sub {
$self->quick_slice(reslice => 1);
});
}, undef, 'cog_go.png');
$repeat->Enable(0);
$fileMenu->AppendSeparator();
wxTheApp->append_menu_item($fileMenu, "Slice to SV&G…\tCtrl+G", 'Slice file to SVG', sub {
$self->quick_slice(save_as => 1, export_svg => 1);
}, undef, 'shape_handles.png');
$fileMenu->AppendSeparator();
wxTheApp->append_menu_item($fileMenu, "Repair STL file…", 'Automatically repair an STL file', sub {
$self->repair_stl;
}, undef, 'wrench.png');
$fileMenu->AppendSeparator();
# Cmd+, is standard on OS X - what about other operating systems?
wxTheApp->append_menu_item($fileMenu, "Preferences…\tCtrl+,", 'Application preferences', sub {
Slic3r::GUI::Preferences->new($self)->ShowModal;
}, wxID_PREFERENCES);
$fileMenu->AppendSeparator();
wxTheApp->append_menu_item($fileMenu, "&Quit", 'Quit Slic3r', sub {
$self->Close(0);
}, wxID_EXIT);
}
# Plater menu
{
my $plater = $self->{plater};
$self->{plater_menu} = Wx::Menu->new;
{
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');
wxTheApp->append_menu_item($self->{plater_menu}, "Select Prev Object\tCtrl+Left", 'Select Previous Object in the plater', sub {
$plater->select_prev;
}, undef, 'arrow_left.png');
wxTheApp->append_menu_item($self->{plater_menu}, "Zoom In\tCtrl+up", 'Zoom In',
sub { $self->{plater}->zoom('in') }, undef, 'zoom_in.png');
wxTheApp->append_menu_item($self->{plater_menu}, "Zoom Out\tCtrl+down", 'Zoom Out',
sub { $self->{plater}->zoom('out') }, undef, 'zoom_out.png');
$self->{plater_menu}->AppendSeparator();
wxTheApp->append_menu_item($self->{plater_menu}, "Export G-code...", 'Export current plate as G-code', sub {
$plater->export_gcode;
}, undef, 'cog_go.png');
wxTheApp->append_menu_item($self->{plater_menu}, "Export plate as STL...", 'Export current plate as STL', sub {
$plater->export_stl;
}, undef, 'brick_go.png');
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);
}
# Settings menu
my $settingsMenu = Wx::Menu->new;
{
wxTheApp->append_menu_item($settingsMenu, "P&rint Settings…\tCtrl+1", 'Show the print settings editor', sub {
$self->{plater}->show_preset_editor('print');
}, undef, 'cog.png');
wxTheApp->append_menu_item($settingsMenu, "&Filament Settings…\tCtrl+2", 'Show the filament settings editor', sub {
$self->{plater}->show_preset_editor('filament');
}, undef, 'spool.png');
wxTheApp->append_menu_item($settingsMenu, "Print&er Settings…\tCtrl+3", 'Show the printer settings editor', sub {
$self->{plater}->show_preset_editor('printer');
}, undef, 'printer_empty.png');
}
# View menu
{
$self->{viewMenu} = Wx::Menu->new;
wxTheApp->append_menu_item($self->{viewMenu}, "Top\tCtrl+4" , 'Top View' , sub { $self->select_view('top' ); });
wxTheApp->append_menu_item($self->{viewMenu}, "Bottom\tCtrl+5" , 'Bottom View' , sub { $self->select_view('bottom' ); });
wxTheApp->append_menu_item($self->{viewMenu}, "Left\tCtrl+6" , 'Left View' , sub { $self->select_view('left' ); });
wxTheApp->append_menu_item($self->{viewMenu}, "Right\tCtrl+7" , 'Right View' , sub { $self->select_view('right' ); });
wxTheApp->append_menu_item($self->{viewMenu}, "Front\tCtrl+8" , 'Front View' , sub { $self->select_view('front' ); });
wxTheApp->append_menu_item($self->{viewMenu}, "Back\tCtrl+9" , 'Back View' , sub { $self->select_view('back' ); });
wxTheApp->append_menu_item($self->{viewMenu}, "Diagonal\tCtrl+0", 'Diagonal View', sub { $self->select_view('diagonal'); });
$self->{viewMenu}->AppendSeparator();
$self->{color_toolpaths_by_role} = wxTheApp->append_menu_item($self->{viewMenu},
"Color Toolpaths by Role",
'Color toolpaths according to perimeter/infill/support material',
sub {
$Slic3r::GUI::Settings->{_}{color_toolpaths_by} = 'role';
wxTheApp->save_settings;
$self->{plater}{preview3D}->reload_print;
},
undef, undef, wxITEM_RADIO
);
$self->{color_toolpaths_by_extruder} = wxTheApp->append_menu_item($self->{viewMenu},
"Color Toolpaths by Filament",
'Color toolpaths using the configured extruder/filament color',
sub {
$Slic3r::GUI::Settings->{_}{color_toolpaths_by} = 'extruder';
wxTheApp->save_settings;
$self->{plater}{preview3D}->reload_print;
},
undef, undef, wxITEM_RADIO
);
if ($Slic3r::GUI::Settings->{_}{color_toolpaths_by} eq 'role') {
$self->{color_toolpaths_by_role}->Check(1);
} else {
$self->{color_toolpaths_by_extruder}->Check(1);
}
}
# Window menu
my $windowMenu = Wx::Menu->new;
{
wxTheApp->append_menu_item($windowMenu, "&Plater\tCtrl+T", 'Show the plater', sub {
$self->select_tab(0);
}, undef, 'application_view_tile.png');
wxTheApp->append_menu_item($windowMenu, "&Controller\tCtrl+Y", 'Show the printer controller', sub {
$self->select_tab(1);
}, 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');
}
# Help menu
my $helpMenu = Wx::Menu->new;
{
wxTheApp->append_menu_item($helpMenu, "&Configuration $Slic3r::GUI::ConfigWizard::wizard…", "Run Configuration $Slic3r::GUI::ConfigWizard::wizard", sub {
$self->config_wizard;
});
$helpMenu->AppendSeparator();
wxTheApp->append_menu_item($helpMenu, "Slic3r &Website", 'Open the Slic3r website in your browser', sub {
Wx::LaunchDefaultBrowser('http://slic3r.org/');
});
my $versioncheck = wxTheApp->append_menu_item($helpMenu, "Check for &Updates...", 'Check for new Slic3r versions', sub {
wxTheApp->check_version(1);
});
$versioncheck->Enable(wxTheApp->have_version_check);
wxTheApp->append_menu_item($helpMenu, "Slic3r &Manual", 'Open the Slic3r manual in your browser', sub {
Wx::LaunchDefaultBrowser('http://manual.slic3r.org/');
});
$helpMenu->AppendSeparator();
wxTheApp->append_menu_item($helpMenu, "&About Slic3r", 'Show about dialog', sub {
wxTheApp->about;
});
}
# menubar
# assign menubar to frame after appending items, otherwise special items
# will not be handled correctly
{
my $menubar = Wx::MenuBar->new;
$menubar->Append($fileMenu, "&File");
$menubar->Append($self->{plater_menu}, "&Plater") if $self->{plater_menu};
$menubar->Append($self->{object_menu}, "&Object") if $self->{object_menu};
$menubar->Append($settingsMenu, "&Settings");
$menubar->Append($self->{viewMenu}, "&View") if $self->{viewMenu};
$menubar->Append($windowMenu, "&Window");
$menubar->Append($helpMenu, "&Help");
$self->SetMenuBar($menubar);
}
}
sub is_loaded {
my ($self) = @_;
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 {
my ($self, $have_selection) = @_;
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 {
my $self = shift;
my %params = @_;
my $progress_dialog;
eval {
# validate configuration
my $config = $self->{plater}->config;
$config->validate;
# select input file
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/3MF):', $dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy;
return;
}
$input_file = Slic3r::decode_path($dialog->GetPaths);
$dialog->Destroy;
$qs_last_input_file = $input_file unless $params{export_svg};
} else {
if (!defined $qs_last_input_file) {
Wx::MessageDialog->new($self, "No previously sliced file.",
'Error', wxICON_ERROR | wxOK)->ShowModal();
return;
}
if (! -e $qs_last_input_file) {
Wx::MessageDialog->new($self, "Previously sliced file ($qs_last_input_file) not found.",
'File Not Found', wxICON_ERROR | wxOK)->ShowModal();
return;
}
$input_file = $qs_last_input_file;
}
my $input_file_basename = basename($input_file);
$Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file);
wxTheApp->save_settings;
my $print_center;
{
my $bed_shape = Slic3r::Polygon->new_scale(@{$config->bed_shape});
$print_center = Slic3r::Pointf->new_unscale(@{$bed_shape->bounding_box->center});
}
my $sprint = Slic3r::Print::Simple->new(
print_center => $print_center,
status_cb => sub {
my ($percent, $message) = @_;
return if &Wx::wxVERSION_STRING !~ / 2\.(8\.|9\.[2-9])/;
$progress_dialog->Update($percent, "$message…");
},
);
# keep model around
my $model = Slic3r::Model->read_from_file($input_file);
$sprint->apply_config($config);
$sprint->set_model($model);
# FIXME: populate placeholders (preset names etc.)
# select output file
my $output_file;
if ($params{reslice}) {
$output_file = $qs_last_output_file if defined $qs_last_output_file;
} elsif ($params{save_as}) {
$output_file = $sprint->output_filepath;
$output_file =~ s/\.gcode$/.svg/i if $params{export_svg};
my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:',
wxTheApp->output_path(dirname($output_file)),
basename($output_file), $params{export_svg} ? &Slic3r::GUI::FILE_WILDCARDS->{svg} : &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
return;
}
$output_file = Slic3r::decode_path($dlg->GetPath);
$qs_last_output_file = $output_file unless $params{export_svg};
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($output_file);
wxTheApp->save_settings;
$dlg->Destroy;
}
# show processbar dialog
$progress_dialog = Wx::ProgressDialog->new('Slicing…', "Processing $input_file_basename…",
100, $self, 0);
$progress_dialog->Pulse;
{
my @warnings = ();
local $SIG{__WARN__} = sub { push @warnings, $_[0] };
$sprint->output_file($output_file);
if ($params{export_svg}) {
$sprint->export_svg;
} else {
$sprint->export_gcode;
}
$sprint->status_cb(undef);
Slic3r::GUI::warning_catcher($self)->($_) for @warnings;
}
$progress_dialog->Destroy;
undef $progress_dialog;
my $message = "$input_file_basename was successfully sliced.";
wxTheApp->notify($message);
Wx::MessageDialog->new($self, $message, 'Slicing Done!',
wxOK | wxICON_INFORMATION)->ShowModal;
};
Slic3r::GUI::catch_error($self, sub { $progress_dialog->Destroy if $progress_dialog });
}
sub repair_stl {
my $self = shift;
my $input_file;
{
my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || '';
my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', $dir, "", &Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if ($dialog->ShowModal != wxID_OK) {
$dialog->Destroy;
return;
}
$input_file = Slic3r::decode_path($dialog->GetPaths);
$dialog->Destroy;
}
my $output_file = $input_file;
{
$output_file =~ s/\.stl$/_fixed.obj/i;
my $dlg = Wx::FileDialog->new($self, "Save OBJ file (less prone to coordinate errors than STL) as:", dirname($output_file),
basename($output_file), &Slic3r::GUI::FILE_WILDCARDS->{obj}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
return undef;
}
$output_file = Slic3r::decode_path($dlg->GetPath);
$dlg->Destroy;
}
my $tmesh = Slic3r::TriangleMesh->new;
$tmesh->ReadSTLFile($input_file);
$tmesh->repair;
$tmesh->WriteOBJFile($output_file);
Slic3r::GUI::show_info($self, "Your file was repaired.", "Repair");
}
sub export_config {
my $self = shift;
my $config = $self->{plater}->config;
eval {
# validate configuration
$config->validate;
};
Slic3r::GUI::catch_error($self) and return;
my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || '';
my $filename = $last_config ? basename($last_config) : "config.ini";
my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $dir, $filename,
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal == wxID_OK) {
my $file = Slic3r::decode_path($dlg->GetPath);
$Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
wxTheApp->save_settings;
$last_config = $file;
$config->save($file);
}
$dlg->Destroy;
}
sub load_config_file {
my $self = shift;
my ($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 configuration to load:', $dir, "config.ini",
&Slic3r::GUI::FILE_WILDCARDS->{ini}, 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;
$last_config = $file;
my $name = wxTheApp->add_external_preset($file);
$self->{plater}->load_presets;
$self->{plater}->select_preset_by_name($name, $_) for qw(print filament printer);
}
sub import_fromGCode{ # import configuration from gcode file sliced with Slic3r
my ($self, $file) = @_;
if (!$file) {
my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || '';
my $dlg = Wx::FileDialog->new($self, 'Select GCode File to load config from:', $dir, &Slic3r::GUI::FILE_WILDCARDS->{gcode}, &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
return unless $dlg->ShowModal == wxID_OK;
$file = Slic3r::decode_path($dlg->GetPaths);
$dlg->Destroy;
}
$Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
wxTheApp->save_settings;
# open $file and read the first line to make sure it was sliced using Slic3r
open(FILEFIRSTLINE, '<', "$file")
or die( "Can't open file to import gcode: $!" );
my $firstLine = ;
close (FILEFIRSTLINE);
# Exit, if file was not sliced using Slic3r
if( index($firstLine, "generated by Slic3r") < 0){
return;
}
# if file sliced by Slic3r, read it
open(GFILE, "$file")
or die( "Can't open file to import gcode: $!" );
my @lines = reverse ; # read the file from the back
my $line;
my @settinglines="";
foreach $line (@lines) {
# if line is empty, we're done -> EOF
if (substr($line, 0, 1) eq ";"){
$line = substr($line,2, length($line)-2);
push @settinglines, $line;
} else {
last;
}
}
close (GFILE);
# (over-) write config to temp-file ->
my $tempfile = substr $file, 0, rindex( $file, q{.} );
$tempfile="$tempfile.ini";
open (TEMPFILESETTINGS, "> $tempfile");
print TEMPFILESETTINGS @settinglines;
close (TEMPFILESETTINGS);
$self->load_config_file($tempfile);
# todo: do we want to delete the file after load_config?
}
sub export_configbundle {
my $self = shift;
eval {
# validate current configuration in case it's dirty
$self->{plater}->config->validate;
};
Slic3r::GUI::catch_error($self) and return;
my $dir = $last_config ? dirname($last_config) : $Slic3r::GUI::Settings->{recent}{config_directory} || $Slic3r::GUI::Settings->{recent}{skein_directory} || '';
my $filename = "Slic3r_config_bundle.ini";
my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', $dir, $filename,
&Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal == wxID_OK) {
my $file = Slic3r::decode_path($dlg->GetPath);
$Slic3r::GUI::Settings->{recent}{config_directory} = dirname($file);
wxTheApp->save_settings;
# leave default category empty to prevent the bundle from being parsed as a normal config file
my $ini = { _ => {} };
$ini->{settings}{$_} = $Slic3r::GUI::Settings->{_}{$_} for qw(autocenter);
$ini->{presets} = $Slic3r::GUI::Settings->{presets};
foreach my $section (qw(print filament printer)) {
my @presets = @{wxTheApp->presets->{$section}};
foreach my $preset (@presets) {
next if $preset->default || $preset->external;
$ini->{"$section:" . $preset->name} = $preset->load_config->as_ini->{_};
}
}
Slic3r::Config->write_ini($file, $ini);
}
$dlg->Destroy;
}
sub load_configbundle {
my ($self, $file, $skip_no_id) = @_;
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 configuration to load:', $dir, "config.ini",
&Slic3r::GUI::FILE_WILDCARDS->{ini}, 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;
# load .ini file
my $ini = Slic3r::Config->read_ini($file);
if ($ini->{settings}) {
$Slic3r::GUI::Settings->{_}{$_} = $ini->{settings}{$_} for keys %{$ini->{settings}};
wxTheApp->save_settings;
}
if ($ini->{presets}) {
$Slic3r::GUI::Settings->{presets} = $ini->{presets};
wxTheApp->save_settings;
}
my $imported = 0;
INI_BLOCK: foreach my $ini_category (sort keys %$ini) {
next unless $ini_category =~ /^(print|filament|printer):(.+)$/;
my ($section, $preset_name) = ($1, $2);
my $config = Slic3r::Config->load_ini_hash($ini->{$ini_category});
next if $skip_no_id && !$config->get($section . "_settings_id");
{
my @current_presets = @{wxTheApp->presets->{$section}};
my %current_ids = map { $_ => 1 }
grep $_,
map $_->dirty_config->get($section . "_settings_id"),
@current_presets;
next INI_BLOCK if exists $current_ids{$config->get($section . "_settings_id")};
}
$config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $section, $preset_name);
Slic3r::debugf "Imported %s preset %s\n", $section, $preset_name;
$imported++;
}
$self->{plater}->load_presets;
return if !$imported;
my $message = sprintf "%d presets successfully imported.", $imported;
Slic3r::GUI::show_info($self, $message);
}
sub load_config {
my ($self, $config) = @_;
$self->{plater}->load_config($config);
}
sub config_wizard {
my $self = shift;
if (my $config = Slic3r::GUI::ConfigWizard->new($self)->run) {
foreach my $group (qw(print filament printer)) {
my $name = 'My Settings';
$config->save(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $group, $name);
$Slic3r::GUI::Settings->{presets}{$group} = "$name.ini";
$self->{plater}->load_presets;
$self->{plater}->select_preset_by_name($name, $group);
}
}
}
sub select_tab {
my ($self, $tab) = @_;
$self->{tabpanel}->SetSelection($tab);
}
# Set a camera direction, zoom to all objects.
sub select_view {
my ($self, $direction) = @_;
$self->{plater}->select_view($direction);
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Notifier.pm 0000664 0000000 0000000 00000002665 13274424355 0017342 0 ustar 00root root 0000000 0000000 # Notify about the end of slicing.
# The notifications are sent out using the Growl protocol if installed, and using DBus XWindow protocol.
package Slic3r::GUI::Notifier;
use Moo;
has 'growler' => (is => 'rw');
my $icon = $Slic3r::var->("Slic3r.png");
sub BUILD {
my ($self) = @_;
if (eval 'use Growl::GNTP; 1') {
# register with growl
eval {
$self->growler(Growl::GNTP->new(AppName => 'Slic3r', AppIcon => $icon));
$self->growler->register([{Name => 'SKEIN_DONE', DisplayName => 'Slicing Done'}]);
};
# if register() fails (for example because of a timeout), disable growler at all
$self->growler(undef) if $@;
}
}
sub notify {
my ($self, $message) = @_;
my $title = 'Slicing Done!';
eval {
$self->growler->notify(Event => 'SKEIN_DONE', Title => $title, Message => $message)
if $self->growler;
};
# Net::DBus is broken in multithreaded environment
if (0 && eval 'use Net::DBus; 1') {
eval {
my $session = Net::DBus->session;
my $serv = $session->get_service('org.freedesktop.Notifications');
my $notifier = $serv->get_object('/org/freedesktop/Notifications',
'org.freedesktop.Notifications');
$notifier->Notify('Slic3r', 0, $icon, $title, $message, [], {}, -1);
undef $Net::DBus::bus_session;
};
}
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/OptionsGroup.pm 0000664 0000000 0000000 00000040571 13274424355 0020231 0 ustar 00root root 0000000 0000000 # A dialog group object. Used by the PresetEditor, Preferences dialog, ManualControlDialog etc.
package Slic3r::GUI::OptionsGroup;
use Moo;
use List::Util qw(first);
use Wx qw(:combobox :font :misc :sizer :systemsettings :textctrl wxTheApp);
use Wx::Event qw(EVT_CHECKBOX EVT_COMBOBOX EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS EVT_SLIDER);
has 'parent' => (is => 'ro', required => 1);
has 'title' => (is => 'ro', required => 1);
has 'on_change' => (is => 'rw', default => sub { sub {} });
has 'staticbox' => (is => 'ro', default => sub { 1 });
has 'label_width' => (is => 'rw', default => sub { 180 });
has 'extra_column' => (is => 'rw', default => sub { undef });
has 'label_font' => (is => 'rw');
has 'sidetext_font' => (is => 'rw', default => sub { Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) });
has 'sizer' => (is => 'rw');
has '_disabled' => (is => 'rw', default => sub { 0 });
has '_grid_sizer' => (is => 'rw');
has '_options' => (is => 'ro', default => sub { {} });
has '_fields' => (is => 'ro', default => sub { {} });
sub BUILD {
my $self = shift;
if ($self->staticbox) {
my $box = Wx::StaticBox->new($self->parent, -1, $self->title);
$self->sizer(Wx::StaticBoxSizer->new($box, wxVERTICAL));
} else {
$self->sizer(Wx::BoxSizer->new(wxVERTICAL));
}
my $num_columns = 1;
++$num_columns if $self->label_width != 0;
++$num_columns if $self->extra_column;
$self->_grid_sizer(Wx::FlexGridSizer->new(0, $num_columns, 0, 0));
$self->_grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
$self->_grid_sizer->AddGrowableCol($self->label_width != 0);
# TODO: border size may be related to wxWidgets 2.8.x vs. 2.9.x instead of wxMAC specific
$self->sizer->Add($self->_grid_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 5);
}
# this method accepts a Slic3r::GUI::OptionsGroup::Line object
sub append_line {
my ($self, $line) = @_;
if ($line->sizer || ($line->widget && $line->full_width)) {
# full-width widgets are appended *after* the grid sizer, so after all the non-full-width lines
my $sizer = $line->sizer // $line->widget->($self->parent);
$self->sizer->Add($sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
return;
}
my $grid_sizer = $self->_grid_sizer;
# if we have an extra column, build it
if ($self->extra_column) {
if (defined (my $item = $self->extra_column->($line))) {
$grid_sizer->Add($item, 0, wxALIGN_CENTER_VERTICAL, 0);
} else {
# if the callback provides no sizer for the extra cell, put a spacer
$grid_sizer->AddSpacer(1);
}
}
# build label if we have it
my $label;
if ($self->label_width != 0) {
$label = Wx::StaticText->new($self->parent, -1, $line->label ? $line->label . ":" : "", wxDefaultPosition, [$self->label_width, -1]);
$label->SetFont($self->label_font) if $self->label_font;
$label->Wrap($self->label_width) ; # needed to avoid Linux/GTK bug
$grid_sizer->Add($label, 0, wxALIGN_CENTER_VERTICAL, 0);
$label->SetToolTipString($line->label_tooltip) if $line->label_tooltip;
}
# if we have a widget, add it to the sizer
if ($line->widget) {
my $widget_sizer = $line->widget->($self->parent);
$grid_sizer->Add($widget_sizer, 0, wxEXPAND | wxALL, &Wx::wxMAC ? 0 : 15);
return;
}
# if we have a single option with no sidetext just add it directly to the grid sizer
my @options = @{$line->get_options};
$self->_options->{$_->opt_id} = $_ for @options;
if (@options == 1 && !$options[0]->sidetext && !$options[0]->side_widget && !@{$line->get_extra_widgets}) {
my $option = $options[0];
my $field = $self->_build_field($option);
$grid_sizer->Add($field, 0, ($option->full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
return;
}
# if we're here, we have more than one option or a single option with sidetext
# so we need a horizontal sizer to arrange these things
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$grid_sizer->Add($sizer, 0, 0, 0);
foreach my $i (0..$#options) {
my $option = $options[$i];
# add label if any
if ($option->label) {
my $field_label = Wx::StaticText->new($self->parent, -1, $option->label . ":", wxDefaultPosition, wxDefaultSize);
$field_label->SetFont($self->sidetext_font);
$sizer->Add($field_label, 0, wxALIGN_CENTER_VERTICAL, 0);
}
# add field
my $field = $self->_build_field($option);
$sizer->Add($field, 0, wxALIGN_CENTER_VERTICAL, 0);
# add sidetext if any
if ($option->sidetext) {
my $sidetext = Wx::StaticText->new($self->parent, -1, $option->sidetext, wxDefaultPosition, wxDefaultSize);
$sidetext->SetFont($self->sidetext_font);
$sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
}
# add side widget if any
if ($option->side_widget) {
$sizer->Add($option->side_widget->($self->parent), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1);
}
if ($option != $#options) {
$sizer->AddSpacer(4);
}
}
# add extra sizers if any
foreach my $extra_widget (@{$line->get_extra_widgets}) {
$sizer->Add($extra_widget->($self->parent), 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4);
}
}
sub create_single_option_line {
my ($self, $option) = @_;
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => $option->label,
label_tooltip => $option->tooltip,
);
$option->label("");
$line->append_option($option);
return $line;
}
sub append_single_option_line {
my ($self, $option) = @_;
return $self->append_line($self->create_single_option_line($option));
}
sub _build_field {
my $self = shift;
my ($opt) = @_;
my $opt_id = $opt->opt_id;
my $on_change = sub {
my ($opt_id, $value) = @_;
$self->_on_change($opt_id, $value)
unless $self->_disabled;
};
my $on_kill_focus = sub {
my ($opt_id) = @_;
$self->_on_kill_focus($opt_id);
};
my $type = $opt->{gui_type} || $opt->{type};
my $field;
if ($type eq 'bool') {
$field = Slic3r::GUI::OptionsGroup::Field::Checkbox->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type eq 'i') {
$field = Slic3r::GUI::OptionsGroup::Field::SpinCtrl->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type eq 'color') {
$field = Slic3r::GUI::OptionsGroup::Field::ColourPicker->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type =~ /^(f|s|s@|percent)$/) {
$field = Slic3r::GUI::OptionsGroup::Field::TextCtrl->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type eq 'select' || $type eq 'select_open') {
$field = Slic3r::GUI::OptionsGroup::Field::Choice->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type eq 'f_enum_open' || $type eq 'i_enum_open' || $type eq 'i_enum_closed') {
$field = Slic3r::GUI::OptionsGroup::Field::NumericChoice->new(
parent => $self->parent,
option => $opt,
);
} elsif ($type eq 'point') {
$field = Slic3r::GUI::OptionsGroup::Field::Point->new(
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,
option => $opt,
);
}
return undef if !$field;
$field->on_change($on_change);
$field->on_kill_focus($on_kill_focus);
$self->_fields->{$opt_id} = $field;
return $field->isa('Slic3r::GUI::OptionsGroup::Field::wxWindow')
? $field->wxWindow
: $field->wxSizer;
}
sub get_option {
my ($self, $opt_id) = @_;
return undef if !exists $self->_options->{$opt_id};
return $self->_options->{$opt_id};
}
sub get_field {
my ($self, $opt_id) = @_;
return undef if !exists $self->_fields->{$opt_id};
return $self->_fields->{$opt_id};
}
sub get_value {
my ($self, $opt_id) = @_;
return if !exists $self->_fields->{$opt_id};
return $self->_fields->{$opt_id}->get_value;
}
sub set_value {
my ($self, $opt_id, $value) = @_;
return if !exists $self->_fields->{$opt_id};
$self->_fields->{$opt_id}->set_value($value);
}
sub _on_change {
my ($self, $opt_id, $value) = @_;
$self->on_change->($opt_id, $value);
}
sub enable {
my ($self) = @_;
$_->enable for values %{$self->_fields};
}
sub disable {
my ($self) = @_;
$_->disable for values %{$self->_fields};
}
sub _on_kill_focus {
my ($self, $opt_id) = @_;
# nothing
}
package Slic3r::GUI::OptionsGroup::Line;
use Moo;
has 'label' => (is => 'rw', default => sub { "" });
has 'full_width' => (is => 'rw', default => sub { 0 });
has 'label_tooltip' => (is => 'rw', default => sub { "" });
has 'sizer' => (is => 'rw');
has 'widget' => (is => 'rw');
has '_options' => (is => 'ro', default => sub { [] });
has '_extra_widgets' => (is => 'ro', default => sub { [] });
use Wx qw(:button :misc :bitmap);
use Wx::Event qw(EVT_BUTTON);
# this method accepts a Slic3r::GUI::OptionsGroup::Option object
sub append_option {
my ($self, $option) = @_;
push @{$self->_options}, $option;
}
sub append_widget {
my ($self, $widget) = @_;
push @{$self->_extra_widgets}, $widget;
}
sub append_button {
my ($self, $text, $icon, $cb, $ref, $disable) = @_;
$self->append_widget(sub {
my ($parent) = @_;
my $btn = Wx::Button->new($parent, -1,
$text, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
$btn->SetFont($Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icon), wxBITMAP_TYPE_PNG));
}
$btn->Disable if $disable;
$$ref = $btn if $ref;
EVT_BUTTON($parent, $btn, $cb);
return $btn;
});
}
sub get_options {
my ($self) = @_;
return [ @{$self->_options} ];
}
sub get_extra_widgets {
my ($self) = @_;
return [ @{$self->_extra_widgets} ];
}
package Slic3r::GUI::OptionsGroup::Option;
use Moo;
has 'opt_id' => (is => 'rw', required => 1);
has 'type' => (is => 'rw', required => 1);
has 'default' => (is => 'rw', required => 1);
has 'gui_type' => (is => 'rw', default => sub { undef });
has 'gui_flags' => (is => 'rw', default => sub { "" });
has 'label' => (is => 'rw', default => sub { "" });
has 'sidetext' => (is => 'rw', default => sub { "" });
has 'tooltip' => (is => 'rw', default => sub { "" });
has 'multiline' => (is => 'rw', default => sub { 0 });
has 'full_width' => (is => 'rw', default => sub { 0 });
has 'width' => (is => 'rw', default => sub { undef });
has 'height' => (is => 'rw', default => sub { undef });
has 'min' => (is => 'rw', default => sub { undef });
has 'max' => (is => 'rw', default => sub { undef });
has 'labels' => (is => 'rw', default => sub { [] });
has 'values' => (is => 'rw', default => sub { [] });
has 'readonly' => (is => 'rw', default => sub { 0 });
has 'side_widget' => (is => 'rw', default => sub { undef });
package Slic3r::GUI::ConfigOptionsGroup;
use Moo;
use List::Util qw(first);
extends 'Slic3r::GUI::OptionsGroup';
has 'config' => (is => 'ro', required => 1);
has 'full_labels' => (is => 'ro', default => sub { 0 });
has '_opt_map' => (is => 'ro', default => sub { {} });
sub get_option {
my ($self, $opt_key, $opt_index, %params) = @_;
$opt_index //= -1;
if (!$self->config->has($opt_key)) {
die "No $opt_key in ConfigOptionsGroup config";
}
my $opt_id = ($opt_index == -1 ? $opt_key : "${opt_key}#${opt_index}");
$self->_opt_map->{$opt_id} = [ $opt_key, $opt_index ];
my $optdef = $Slic3r::Config::Options->{$opt_key}; # we should access this from $self->config
my $default_value = $self->_get_config_value($opt_key, $opt_index, $optdef->{type} eq 's@');
return Slic3r::GUI::OptionsGroup::Option->new(
opt_id => $opt_id,
type => $optdef->{type},
default => $default_value,
gui_type => $optdef->{gui_type},
gui_flags => $optdef->{gui_flags},
label => ($self->full_labels && defined $optdef->{full_label}) ? $optdef->{full_label} : $optdef->{label},
sidetext => $optdef->{sidetext},
# calling serialize() ensures we get a stringified value
tooltip => $optdef->{tooltip} . " (default: " . $self->config->serialize($opt_key) . ")",
multiline => $optdef->{multiline},
width => $optdef->{width},
min => $optdef->{min},
max => $optdef->{max},
labels => $optdef->{labels},
values => $optdef->{values},
readonly => $optdef->{readonly},
%params,
);
}
sub create_single_option_line {
my ($self, $opt_key, $opt_index, %params) = @_;
my $option;
if (ref($opt_key)) {
$option = $opt_key;
} else {
$option = $self->get_option($opt_key, $opt_index, %params);
}
return $self->SUPER::create_single_option_line($option);
}
sub append_single_option_line {
my ($self, $option, $opt_index, %params) = @_;
return $self->append_line($self->create_single_option_line($option, $opt_index, %params));
}
sub reload_config {
my ($self) = @_;
foreach my $opt_id (keys %{ $self->_opt_map }) {
my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} };
my $option = $self->_options->{$opt_id};
$self->set_value($opt_id, $self->_get_config_value($opt_key, $opt_index, $option->type eq 's@'));
}
}
sub get_fieldc {
my ($self, $opt_key, $opt_index) = @_;
$opt_index //= -1;
my $opt_id = first { $self->_opt_map->{$_}[0] eq $opt_key && $self->_opt_map->{$_}[1] == $opt_index }
keys %{$self->_opt_map};
return defined($opt_id) ? $self->get_field($opt_id) : undef;
}
sub _get_config_value {
my ($self, $opt_key, $opt_index, $as_string) = @_;
if ($opt_index == -1) {
my $value = $self->config->get($opt_key);
if ($as_string && ref($value) eq 'ARRAY') {
return join "\n", @$value;
}
return $value;
} else {
return $self->config->get_at($opt_key, $opt_index);
}
}
sub _on_change {
my ($self, $opt_id, $value) = @_;
if (exists $self->_opt_map->{$opt_id}) {
my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} };
my $option = $self->_options->{$opt_id};
# get value
my $field_value = $self->get_value($opt_id);
if ($opt_index == -1) {
if ($option->type eq 's@' && ref($field_value) ne 'ARRAY') {
$field_value = [ split /(?config->set($opt_key, $field_value);
} else {
my $value = $self->config->get($opt_key);
$value->[$opt_index] = $field_value;
$self->config->set($opt_key, $value);
}
}
$self->SUPER::_on_change($opt_id, $value);
}
sub _on_kill_focus {
my ($self, $opt_id) = @_;
#Â when a field loses focus, reapply the config value to it
# (thus discarding any invalid input and reverting to the last
# accepted value)
$self->reload_config;
}
package Slic3r::GUI::OptionsGroup::StaticText;
use Wx qw(:misc :systemsettings);
use base 'Wx::StaticText';
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new($parent, -1, "", wxDefaultPosition, wxDefaultSize);
my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
$self->SetFont($font);
return $self;
}
sub SetText {
my ($self, $value) = @_;
$self->SetLabel($value);
$self->Wrap(400);
$self->GetParent->Layout;
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/OptionsGroup/ 0000775 0000000 0000000 00000000000 13274424355 0017664 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/lib/Slic3r/GUI/OptionsGroup/Field.pm 0000664 0000000 0000000 00000045367 13274424355 0021264 0 ustar 00root root 0000000 0000000 package Slic3r::GUI::OptionsGroup::Field;
use Moo;
# This is a base class for option fields.
has 'parent' => (is => 'ro', required => 1);
has 'option' => (is => 'ro', required => 1); # Slic3r::GUI::OptionsGroup::Option
has 'on_change' => (is => 'rw', default => sub { sub {} });
has 'on_kill_focus' => (is => 'rw', default => sub { sub {} });
has 'disable_change_event' => (is => 'rw', default => sub { 0 });
# This method should not fire the on_change event
sub set_value {
my ($self, $value) = @_;
die "Method not implemented";
}
sub get_value {
my ($self) = @_;
die "Method not implemented";
}
sub set_tooltip {
my ($self, $tooltip) = @_;
die "Method not implemented";
}
sub toggle {
my ($self, $enable) = @_;
$enable ? $self->enable : $self->disable;
}
sub _on_change {
my ($self, $opt_id) = @_;
$self->on_change->($opt_id, $self->get_value)
unless $self->disable_change_event;
}
sub _on_kill_focus {
my ($self, $opt_id, $s, $event) = @_;
# Without this, there will be nasty focus bugs on Windows.
# Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all
# non-command events to allow the default handling to take place."
$event->Skip(1);
$self->on_kill_focus->($opt_id);
}
package Slic3r::GUI::OptionsGroup::Field::wxWindow;
use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field';
has 'wxWindow' => (is => 'rw', trigger => 1); # wxWindow object
sub _default_size {
my ($self) = @_;
# default width on Windows is too large
return Wx::Size->new($self->option->width || 60, $self->option->height || -1);
}
sub _trigger_wxWindow {
my ($self) = @_;
$self->set_tooltip($self->option->tooltip);
}
sub set_tooltip {
my ($self, $tooltip) = @_;
$self->wxWindow->SetToolTipString($tooltip)
if $self->wxWindow->can('SetToolTipString');
}
sub set_value {
my ($self, $value) = @_;
$self->disable_change_event(1);
$self->wxWindow->SetValue($value);
$self->disable_change_event(0);
}
sub get_value {
my ($self) = @_;
return $self->wxWindow->GetValue;
}
sub enable {
my ($self) = @_;
$self->wxWindow->Enable;
$self->wxWindow->Refresh;
}
sub disable {
my ($self) = @_;
$self->wxWindow->Disable;
$self->wxWindow->Refresh;
}
package Slic3r::GUI::OptionsGroup::Field::Checkbox;
use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
use Wx qw(:misc);
use Wx::Event qw(EVT_CHECKBOX);
sub BUILD {
my ($self) = @_;
my $field = Wx::CheckBox->new($self->parent, -1, "");
$self->wxWindow($field);
$field->SetValue($self->option->default);
$field->Disable if $self->option->readonly;
EVT_CHECKBOX($self->parent, $field, sub {
$self->_on_change($self->option->opt_id);
});
}
package Slic3r::GUI::OptionsGroup::Field::SpinCtrl;
use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
use Wx qw(:misc);
use Wx::Event qw(EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS);
has 'tmp_value' => (is => 'rw');
sub BUILD {
my ($self) = @_;
my $field = Wx::SpinCtrl->new($self->parent, -1, $self->option->default, wxDefaultPosition, $self->_default_size,
0, $self->option->min || 0, $self->option->max || 2147483647, $self->option->default);
$self->wxWindow($field);
EVT_SPINCTRL($self->parent, $field, sub {
$self->tmp_value(undef);
$self->_on_change($self->option->opt_id);
});
EVT_TEXT($self->parent, $field, sub {
my ($s, $event) = @_;
# On OSX/Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
# when it was changed from the text control, so the on_change callback
# gets the old one, and on_kill_focus resets the control to the old value.
# As a workaround, we get the new value from $event->GetString and store
#Â here temporarily so that we can return it from $self->get_value
$self->tmp_value($event->GetString) if $event->GetString =~ /^\d+$/;
$self->_on_change($self->option->opt_id);
# We don't reset tmp_value here because _on_change might put callbacks
# in the CallAfter queue, and we want the tmp value to be available from
# them as well.
});
EVT_KILL_FOCUS($field, sub {
$self->tmp_value(undef);
$self->_on_kill_focus($self->option->opt_id, @_);
});
}
sub get_value {
my ($self) = @_;
return $self->tmp_value // $self->wxWindow->GetValue;
}
package Slic3r::GUI::OptionsGroup::Field::TextCtrl;
use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
use Wx qw(:misc :textctrl);
use Wx::Event qw(EVT_TEXT EVT_KILL_FOCUS);
sub BUILD {
my ($self) = @_;
my $style = 0;
$style = wxTE_MULTILINE if $self->option->multiline;
my $field = Wx::TextCtrl->new($self->parent, -1, $self->option->default, wxDefaultPosition,
$self->_default_size, $style);
$self->wxWindow($field);
# TODO: test loading a config that has empty string for multi-value options like 'wipe'
EVT_TEXT($self->parent, $field, sub {
$self->_on_change($self->option->opt_id);
});
EVT_KILL_FOCUS($field, sub {
$self->_on_kill_focus($self->option->opt_id, @_);
});
}
sub enable {
my ($self) = @_;
$self->wxWindow->Enable;
$self->wxWindow->SetEditable(1);
}
sub disable {
my ($self) = @_;
$self->wxWindow->Disable;
$self->wxWindow->SetEditable(0);
}
package Slic3r::GUI::OptionsGroup::Field::Choice;
use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
use List::Util qw(first);
use Wx qw(:misc :combobox);
use Wx::Event qw(EVT_COMBOBOX EVT_TEXT);
sub BUILD {
my ($self) = @_;
my $style = 0;
$style |= wxCB_READONLY if defined $self->option->gui_type && $self->option->gui_type ne 'select_open';
my $field = Wx::ComboBox->new($self->parent, -1, "", wxDefaultPosition, $self->_default_size,
$self->option->labels || $self->option->values || [], $style);
$self->wxWindow($field);
$self->set_value($self->option->default);
EVT_COMBOBOX($self->parent, $field, sub {
$self->_on_change($self->option->opt_id);
});
EVT_TEXT($self->parent, $field, sub {
$self->_on_change($self->option->opt_id);
});
}
sub set_value {
my ($self, $value) = @_;
$self->disable_change_event(1);
my $idx;
if ($self->option->values) {
$idx = first { $self->option->values->[$_] eq $value } 0..$#{$self->option->values};
# if value is not among indexes values we use SetValue()
}
if (defined $idx) {
$self->wxWindow->SetSelection($idx);
} else {
$self->wxWindow->SetValue($value);
}
$self->disable_change_event(0);
}
sub set_values {
my ($self, $values) = @_;
$self->disable_change_event(1);
#Â it looks that Clear() also clears the text field in recent wxWidgets versions,
# but we want to preserve it
my $ww = $self->wxWindow;
my $value = $ww->GetValue;
$ww->Clear;
$ww->Append($_) for @$values;
$ww->SetValue($value);
$self->disable_change_event(0);
}
sub get_value {
my ($self) = @_;
if ($self->option->values) {
my $idx = $self->wxWindow->GetSelection;
if ($idx != &Wx::wxNOT_FOUND) {
return $self->option->values->[$idx];
}
}
return $self->wxWindow->GetValue;
}
package Slic3r::GUI::OptionsGroup::Field::NumericChoice;
use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
use List::Util qw(first);
use Wx qw(wxTheApp :misc :combobox);
use Wx::Event qw(EVT_COMBOBOX EVT_TEXT);
# if option has no 'values', indices are values
# if option has no 'labels', values are labels
sub BUILD {
my ($self) = @_;
my $field = Wx::ComboBox->new($self->parent, -1, $self->option->default, wxDefaultPosition, $self->_default_size,
$self->option->labels || $self->option->values);
$self->wxWindow($field);
$self->set_value($self->option->default);
EVT_COMBOBOX($self->parent, $field, sub {
my $disable_change_event = $self->disable_change_event;
$self->disable_change_event(1);
my $idx = $field->GetSelection; # get index of selected value
my $label;
if ($self->option->labels && $idx <= $#{$self->option->labels}) {
$label = $self->option->labels->[$idx];
} elsif ($self->option->values && $idx <= $#{$self->option->values}) {
$label = $self->option->values->[$idx];
} else {
$label = $idx;
}
# The MSW implementation of wxComboBox will leave the field blank if we call
# SetValue() in the EVT_COMBOBOX event handler, so we postpone the call.
wxTheApp->CallAfter(sub {
my $dce = $self->disable_change_event;
$self->disable_change_event(1);
# ChangeValue() is not exported in wxPerl
$field->SetValue($label);
$self->disable_change_event($dce);
});
$self->disable_change_event($disable_change_event);
$self->_on_change($self->option->opt_id);
});
EVT_TEXT($self->parent, $field, sub {
$self->_on_change($self->option->opt_id);
});
}
sub set_value {
my ($self, $value) = @_;
$self->disable_change_event(1);
my $field = $self->wxWindow;
if ($self->option->gui_flags =~ /\bshow_value\b/) {
$field->SetValue($value);
} else {
if ($self->option->values) {
# check whether we have a value index
my $value_idx = first { $self->option->values->[$_] eq $value } 0..$#{$self->option->values};
if (defined $value_idx) {
$field->SetSelection($value_idx);
$self->disable_change_event(0);
return;
}
} elsif ($self->option->labels && $value <= $#{$self->option->labels}) {
# if we have no values, we expect value to be an index
$field->SetValue($self->option->labels->[$value]);
$self->disable_change_event(0);
return;
}
$field->SetValue($value);
}
$self->disable_change_event(0);
}
sub get_value {
my ($self) = @_;
my $label = $self->wxWindow->GetValue;
if ($self->option->labels) {
my $value_idx = first { $self->option->labels->[$_] eq $label } 0..$#{$self->option->labels};
if (defined $value_idx) {
if ($self->option->values) {
return $self->option->values->[$value_idx];
}
return $value_idx;
}
}
return $label;
}
package Slic3r::GUI::OptionsGroup::Field::ColourPicker;
use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
use Wx qw(:misc :colour);
use Wx::Event qw(EVT_COLOURPICKER_CHANGED);
sub BUILD {
my ($self) = @_;
my $field = Wx::ColourPickerCtrl->new($self->parent, -1,
$self->_string_to_colour($self->option->default), wxDefaultPosition,
$self->_default_size);
$self->wxWindow($field);
EVT_COLOURPICKER_CHANGED($self->parent, $field, sub {
$self->_on_change($self->option->opt_id);
});
}
sub set_value {
my ($self, $value) = @_;
$self->disable_change_event(1);
$self->wxWindow->SetColour($self->_string_to_colour($value));
$self->disable_change_event(0);
}
sub get_value {
my ($self) = @_;
return $self->wxWindow->GetColour->GetAsString(wxC2S_HTML_SYNTAX);
}
sub _string_to_colour {
my ($self, $string) = @_;
$string =~ s/^#//;
return Wx::Colour->new(unpack 'C*', pack 'H*', $string);
}
package Slic3r::GUI::OptionsGroup::Field::wxSizer;
use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field';
has 'wxSizer' => (is => 'rw'); # wxSizer object
package Slic3r::GUI::OptionsGroup::Field::Point;
use Moo;
extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer';
has 'x_textctrl' => (is => 'rw');
has 'y_textctrl' => (is => 'rw');
use Slic3r::Geometry qw(X Y);
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));
my @items = (
Wx::StaticText->new($self->parent, -1, "x:"),
$self->x_textctrl,
Wx::StaticText->new($self->parent, -1, " y:"),
$self->y_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;
}
sub set_value {
my ($self, $value) = @_;
$self->disable_change_event(1);
$self->x_textctrl->SetValue($value->[X]);
$self->y_textctrl->SetValue($value->[Y]);
$self->disable_change_event(0);
}
sub get_value {
my ($self) = @_;
return [
$self->x_textctrl->GetValue,
$self->y_textctrl->GetValue,
];
}
sub enable {
my ($self) = @_;
$self->x_textctrl->Enable;
$self->y_textctrl->Enable;
}
sub disable {
my ($self) = @_;
$self->x_textctrl->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;
extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer';
has 'scale' => (is => 'rw', default => sub { 10 });
has 'slider' => (is => 'rw');
has 'textctrl' => (is => 'rw');
use Slic3r::Geometry qw(X Y);
use Wx qw(:misc :sizer);
use Wx::Event qw(EVT_SLIDER EVT_TEXT EVT_KILL_FOCUS);
sub BUILD {
my ($self) = @_;
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$self->wxSizer($sizer);
my $slider = Wx::Slider->new(
$self->parent, -1,
($self->option->default // $self->option->min) * $self->scale,
($self->option->min // 0) * $self->scale,
($self->option->max // 100) * $self->scale,
wxDefaultPosition,
[ $self->option->width // -1, $self->option->height // -1 ],
);
$self->slider($slider);
my $textctrl = Wx::TextCtrl->new($self->parent, -1, $slider->GetValue/$self->scale,
wxDefaultPosition, [50,-1]);
$self->textctrl($textctrl);
$sizer->Add($slider, 1, wxALIGN_CENTER_VERTICAL, 0);
$sizer->Add($textctrl, 0, wxALIGN_CENTER_VERTICAL, 0);
EVT_SLIDER($self->parent, $slider, sub {
$self->_update_textctrl;
$self->_on_change($self->option->opt_id);
});
EVT_TEXT($self->parent, $textctrl, sub {
my $value = $textctrl->GetValue;
if ($value =~ /^-?\d+(\.\d*)?$/) {
# Update the slider without re-updating the text field being modified.
$self->disable_change_event(1);
$self->slider->SetValue($value*$self->scale);
$self->disable_change_event(0);
$self->_on_change($self->option->opt_id);
}
});
EVT_KILL_FOCUS($textctrl, sub {
$self->_update_textctrl;
$self->_on_kill_focus($self->option->opt_id, @_);
});
}
sub set_value {
my ($self, $value) = @_;
$self->disable_change_event(1);
$self->slider->SetValue($value*$self->scale);
$self->_update_textctrl;
$self->disable_change_event(0);
}
sub get_value {
my ($self) = @_;
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) = @_;
$self->textctrl->ChangeValue($self->get_value);
$self->textctrl->SetInsertionPointEnd;
}
sub enable {
my ($self) = @_;
$self->slider->Enable;
$self->textctrl->Enable;
$self->textctrl->SetEditable(1);
}
sub disable {
my ($self) = @_;
$self->slider->Disable;
$self->textctrl->Disable;
$self->textctrl->SetEditable(0);
}
sub set_range {
my ($self, $min, $max) = @_;
$self->slider->SetRange($min * $self->scale, $max * $self->scale);
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Plater.pm 0000664 0000000 0000000 00000377102 13274424355 0017013 0 ustar 00root root 0000000 0000000 # The "Plater" tab. It contains the "3D", "2D", "Preview" and "Layers" subtabs.
package Slic3r::GUI::Plater::UndoOperation;
use strict;
use warnings;
sub new{
my $class = shift;
my $self = {
type => shift,
object_identifier => shift,
attributes => shift,
};
bless ($self, $class);
return $self;
}
package Slic3r::GUI::Plater;
use strict;
use warnings;
use utf8;
use File::Basename qw(basename dirname);
use List::Util qw(sum first max none any);
use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad rad2deg);
use LWP::UserAgent;
use threads::shared qw(shared_clone);
use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :misc
:panel :sizer :toolbar :window wxTheApp :notebook :combobox);
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL
EVT_CHOICE EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED EVT_LEFT_UP EVT_CLOSE);
use base qw(Wx::Panel Class::Accessor);
__PACKAGE__->mk_accessors(qw(presets));
use constant TB_ADD => &Wx::NewId;
use constant TB_REMOVE => &Wx::NewId;
use constant TB_RESET => &Wx::NewId;
use constant TB_ARRANGE => &Wx::NewId;
use constant TB_EXPORT_GCODE => &Wx::NewId;
use constant TB_EXPORT_STL => &Wx::NewId;
use constant TB_MORE => &Wx::NewId;
use constant TB_FEWER => &Wx::NewId;
use constant TB_45CW => &Wx::NewId;
use constant TB_45CCW => &Wx::NewId;
use constant TB_SCALE => &Wx::NewId;
use constant TB_SPLIT => &Wx::NewId;
use constant TB_CUT => &Wx::NewId;
use constant TB_LAYERS => &Wx::NewId;
use constant TB_SETTINGS => &Wx::NewId;
# package variables to avoid passing lexicals to threads
our $THUMBNAIL_DONE_EVENT : shared = Wx::NewEventType;
our $PROGRESS_BAR_EVENT : shared = Wx::NewEventType;
our $ERROR_EVENT : shared = Wx::NewEventType;
our $EXPORT_COMPLETED_EVENT : shared = Wx::NewEventType;
our $PROCESS_COMPLETED_EVENT : shared = Wx::NewEventType;
use constant FILAMENT_CHOOSERS_SPACING => 0;
use constant PROCESS_DELAY => 0.5 * 1000; # milliseconds
sub new {
my $class = shift;
my ($parent) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
$self->{config} = Slic3r::Config->new_from_defaults(qw(
bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width
serial_port serial_speed host_type print_host octoprint_apikey shortcuts filament_colour
));
$self->{model} = Slic3r::Model->new;
$self->{print} = Slic3r::Print->new;
$self->{processed} = 0;
# List of Perl objects Slic3r::GUI::Plater::Object, representing a 2D preview of the platter.
$self->{objects} = [];
# Objects identifier used for undo/redo operations. It's a one time id assigned to each newly created object.
$self->{object_identifier} = 0;
# Stack of undo operations.
$self->{undo_stack} = [];
# Stack of redo operations.
$self->{redo_stack} = [];
$self->{print}->set_status_cb(sub {
my ($percent, $message) = @_;
if ($Slic3r::have_threads) {
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROGRESS_BAR_EVENT, shared_clone([$percent, $message])));
} else {
$self->on_progress_event($percent, $message);
}
});
# Initialize preview notebook
$self->{preview_notebook} = Wx::Notebook->new($self, -1, wxDefaultPosition, [335,335], wxNB_BOTTOM);
# Initialize handlers for canvases
my $on_select_object = sub {
my ($obj_idx) = @_;
$self->select_object($obj_idx);
};
my $on_double_click = sub {
$self->object_settings_dialog if $self->selected_object;
};
my $on_right_click = sub {
my ($canvas, $click_pos) = @_;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
my $menu = $self->object_menu;
$canvas->PopupMenu($menu, $click_pos);
$menu->Destroy;
};
my $on_instances_moved = sub {
$self->on_model_change;
};
# Initialize 3D plater
if ($Slic3r::GUI::have_OpenGL) {
$self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{config});
$self->{preview_notebook}->AddPage($self->{canvas3D}, '3D');
$self->{canvas3D}->set_on_select_object($on_select_object);
$self->{canvas3D}->set_on_double_click($on_double_click);
$self->{canvas3D}->set_on_right_click(sub { $on_right_click->($self->{canvas3D}, @_); });
$self->{canvas3D}->set_on_instances_moved($on_instances_moved);
$self->{canvas3D}->on_viewport_changed(sub {
$self->{preview3D}->canvas->set_viewport_from_scene($self->{canvas3D});
});
}
# Initialize 2D preview canvas
$self->{canvas} = Slic3r::GUI::Plater::2D->new($self->{preview_notebook}, wxDefaultSize, $self->{objects}, $self->{model}, $self->{config});
$self->{preview_notebook}->AddPage($self->{canvas}, '2D');
$self->{canvas}->on_select_object($on_select_object);
$self->{canvas}->on_double_click($on_double_click);
$self->{canvas}->on_right_click(sub { $on_right_click->($self->{canvas}, @_); });
$self->{canvas}->on_instances_moved($on_instances_moved);
# Initialize 3D toolpaths preview
$self->{preview3D_page_idx} = -1;
if ($Slic3r::GUI::have_OpenGL) {
$self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print});
$self->{preview3D}->canvas->on_viewport_changed(sub {
$self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas);
});
$self->{preview_notebook}->AddPage($self->{preview3D}, 'Preview');
$self->{preview3D_page_idx} = $self->{preview_notebook}->GetPageCount-1;
}
# Initialize toolpaths preview
$self->{toolpaths2D_page_idx} = -1;
if ($Slic3r::GUI::have_OpenGL) {
$self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print});
$self->{preview_notebook}->AddPage($self->{toolpaths2D}, 'Layers');
$self->{toolpaths2D_page_idx} = $self->{preview_notebook}->GetPageCount-1;
}
EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub {
wxTheApp->CallAfter(sub {
my $sel = $self->{preview_notebook}->GetSelection;
if ($sel == $self->{preview3D_page_idx} || $sel == $self->{toolpaths2D_page_idx}) {
if (!$Slic3r::GUI::Settings->{_}{background_processing} && !$self->{processed}) {
$self->statusbar->SetCancelCallback(sub {
$self->stop_background_process;
$self->statusbar->SetStatusText("Slicing cancelled");
$self->{preview_notebook}->SetSelection(0);
});
$self->start_background_process;
} else {
$self->{preview3D}->load_print
if $sel == $self->{preview3D_page_idx};
}
}
});
});
# toolbar for object manipulation
if (!&Wx::wxMSW) {
Wx::ToolTip::Enable(1);
$self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL);
$self->{htoolbar}->AddTool(TB_ADD, "Add…", Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_REMOVE, "Delete", Wx::Bitmap->new($Slic3r::var->("brick_delete.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_RESET, "Delete All", Wx::Bitmap->new($Slic3r::var->("cross.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_ARRANGE, "Arrange", Wx::Bitmap->new($Slic3r::var->("bricks.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new($Slic3r::var->("add.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_FEWER, "Fewer", Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new($Slic3r::var->("arrow_rotate_anticlockwise.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new($Slic3r::var->("arrow_out.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new($Slic3r::var->("shape_ungroup.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_LAYERS, "Layer heights…", Wx::Bitmap->new($Slic3r::var->("variable_layer_height.png"), wxBITMAP_TYPE_PNG), '');
} else {
my %tbar_buttons = (
add => "Add…",
remove => "Delete",
reset => "Delete All",
arrange => "Arrange",
increase => "",
decrease => "",
rotate45ccw => "",
rotate45cw => "",
changescale => "Scale…",
split => "Split",
cut => "Cut…",
layers => "Layer heights…",
settings => "Settings…",
);
$self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL);
for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut layers settings)) {
$self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
$self->{btoolbar}->Add($self->{"btn_$_"});
}
}
# right pane buttons
$self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_print} = Wx::Button->new($self, -1, "Print…", wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_send_gcode} = Wx::Button->new($self, -1, "Send to printer", wxDefaultPosition, [-1, 30], wxBU_LEFT);
$self->{btn_export_stl} = Wx::Button->new($self, -1, "Export STL…", wxDefaultPosition, [-1, 30], wxBU_LEFT);
#$self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font);
#$self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font);
$self->{btn_print}->Hide;
$self->{btn_send_gcode}->Hide;
if ($Slic3r::GUI::have_button_icons) {
my %icons = qw(
add brick_add.png
remove brick_delete.png
reset cross.png
arrange bricks.png
export_gcode cog_go.png
print arrow_up.png
send_gcode arrow_up.png
export_stl brick_go.png
increase add.png
decrease delete.png
rotate45cw arrow_rotate_clockwise.png
rotate45ccw arrow_rotate_anticlockwise.png
changescale arrow_out.png
split shape_ungroup.png
cut package.png
layers variable_layer_height.png
settings cog.png
);
for (grep $self->{"btn_$_"}, keys %icons) {
$self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new($Slic3r::var->($icons{$_}), wxBITMAP_TYPE_PNG));
}
}
$self->selection_changed(0);
$self->object_list_changed;
EVT_BUTTON($self, $self->{btn_export_gcode}, sub {
$self->export_gcode;
});
EVT_BUTTON($self, $self->{btn_print}, sub {
$self->{print_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir());
});
EVT_LEFT_UP($self->{btn_send_gcode}, sub {
my (undef, $e) = @_;
my $alt = $e->ShiftDown;
wxTheApp->CallAfter(sub {
$self->prepare_send($alt);
});
});
EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl);
if ($self->{htoolbar}) {
EVT_TOOL($self, TB_ADD, sub { $self->add; });
EVT_TOOL($self, TB_REMOVE, sub { $self->remove() }); # explicitly pass no argument to remove
EVT_TOOL($self, TB_RESET, sub { $self->reset; });
EVT_TOOL($self, TB_ARRANGE, sub { $self->arrange; });
EVT_TOOL($self, TB_MORE, sub { $self->increase; });
EVT_TOOL($self, TB_FEWER, sub { $self->decrease; });
EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45) });
EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45) });
EVT_TOOL($self, TB_SCALE, sub { $self->changescale(undef); });
EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; });
EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog });
EVT_TOOL($self, TB_LAYERS, sub { $_[0]->object_layers_dialog });
EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog });
} else {
EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; });
EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove
EVT_BUTTON($self, $self->{btn_reset}, sub { $self->reset; });
EVT_BUTTON($self, $self->{btn_arrange}, sub { $self->arrange; });
EVT_BUTTON($self, $self->{btn_increase}, sub { $self->increase; });
EVT_BUTTON($self, $self->{btn_decrease}, sub { $self->decrease; });
EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45) });
EVT_BUTTON($self, $self->{btn_rotate45ccw}, sub { $_[0]->rotate(45) });
EVT_BUTTON($self, $self->{btn_changescale}, sub { $self->changescale(undef); });
EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; });
EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog });
EVT_BUTTON($self, $self->{btn_layers}, sub { $_[0]->object_layers_dialog });
EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog });
}
$_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self))
for grep defined($_),
$self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D};
EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub {
my ($self, $event) = @_;
my ($obj_idx) = @{$event->GetData};
return if !$self->{objects}[$obj_idx]; # object was deleted before thumbnail generation completed
$self->on_thumbnail_made($obj_idx);
});
EVT_COMMAND($self, -1, $PROGRESS_BAR_EVENT, sub {
my ($self, $event) = @_;
my ($percent, $message) = @{$event->GetData};
$self->on_progress_event($percent, $message);
});
EVT_COMMAND($self, -1, $ERROR_EVENT, sub {
my ($self, $event) = @_;
Slic3r::GUI::show_error($self, @{$event->GetData});
});
EVT_COMMAND($self, -1, $EXPORT_COMPLETED_EVENT, sub {
my ($self, $event) = @_;
$self->on_export_completed($event->GetData);
});
EVT_COMMAND($self, -1, $PROCESS_COMPLETED_EVENT, sub {
my ($self, $event) = @_;
$self->on_process_completed($event->GetData);
});
if ($Slic3r::have_threads) {
my $timer_id = Wx::NewId();
$self->{apply_config_timer} = Wx::Timer->new($self, $timer_id);
EVT_TIMER($self, $timer_id, sub {
my ($self, $event) = @_;
$self->async_apply_config;
});
}
$self->{canvas}->update_bed_size;
if ($self->{canvas3D}) {
$self->{canvas3D}->update_bed_size;
$self->{canvas3D}->zoom_to_bed;
}
if ($self->{preview3D}) {
$self->{preview3D}->set_bed_shape($self->{config}->bed_shape);
}
{
my $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 3, 1, 2);
$presets->AddGrowableCol(1, 1);
$presets->SetFlexibleDirection(wxHORIZONTAL);
my %group_labels = (
print => 'Print settings',
filament => 'Filament',
printer => 'Printer',
);
$self->{preset_choosers} = {};
$self->{preset_choosers_names} = {}; # wxChoice* => []
for my $group (qw(print filament printer)) {
# label
my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT);
$text->SetFont($Slic3r::GUI::small_font);
# dropdown control
my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY);
$self->{preset_choosers}{$group} = [$choice];
#Â setup the listener
EVT_COMBOBOX($choice, $choice, sub {
my ($choice) = @_;
wxTheApp->CallAfter(sub {
$self->_on_change_combobox($group, $choice);
});
});
# settings button
my $settings_btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
EVT_BUTTON($self, $settings_btn, sub {
$self->show_preset_editor($group, 0);
});
$presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4);
$presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0);
$presets->Add($settings_btn, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxLEFT, 3);
}
{
my $o = $self->{settings_override_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self,
on_change => sub {
my ($opt_key) = @_;
my ($preset) = $self->selected_presets('print');
$preset->load_config;
# If this option is not in the override panel it means it was manually deleted,
# so let's restore the profile value.
if (!$self->{settings_override_config}->has($opt_key)) {
$preset->_dirty_config->set($opt_key, $preset->_config->get($opt_key));
} else {
# Apply the overrides to the current Print preset, potentially making it dirty
$preset->_dirty_config->apply($self->{settings_override_config});
# If this is a configured shortcut (and not just a dirty option),
# save it now.
if (any { $_ eq $opt_key } @{$preset->dirty_config->shortcuts}) {
$preset->save([$opt_key]);
}
}
$self->load_presets;
$self->config_changed;
# Reload the open tab if any
if (my $print_tab = $self->GetFrame->{preset_editor_tabs}{print}) {
$print_tab->load_presets;
$print_tab->reload_preset;
}
});
$o->can_add(0);
$o->can_delete(1);
$o->set_opt_keys([ Slic3r::GUI::PresetEditor::Print->options ]);
$self->{settings_override_config} = Slic3r::Config->new;
$o->set_default_config($self->{settings_override_config});
$o->set_config($self->{settings_override_config});
}
my $object_info_sizer;
{
my $box = Wx::StaticBox->new($self, -1, "Info");
$object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL);
$object_info_sizer->SetMinSize([350,-1]);
{
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$object_info_sizer->Add($sizer, 0, wxEXPAND | wxBOTTOM, 5);
my $text = Wx::StaticText->new($self, -1, "Object:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
$text->SetFont($Slic3r::GUI::small_font);
$sizer->Add($text, 0, wxALIGN_CENTER_VERTICAL);
# We supply a bogus width to wxChoice (sizer will override it and stretch
# the control anyway), because if we leave the default (-1) it will stretch
# too much according to the contents, and this is bad with long file names.
$self->{object_info_choice} = Wx::Choice->new($self, -1, wxDefaultPosition, [100,-1], []);
$self->{object_info_choice}->SetFont($Slic3r::GUI::small_font);
$sizer->Add($self->{object_info_choice}, 1, wxALIGN_CENTER_VERTICAL);
EVT_CHOICE($self, $self->{object_info_choice}, sub {
$self->select_object($self->{object_info_choice}->GetSelection);
$self->refresh_canvases;
});
}
my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5);
$grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
$grid_sizer->AddGrowableCol(1, 1);
$grid_sizer->AddGrowableCol(3, 1);
$object_info_sizer->Add($grid_sizer, 0, wxEXPAND);
my @info = (
copies => "Copies",
size => "Size",
volume => "Volume",
facets => "Facets",
materials => "Materials",
manifold => "Manifold",
);
while (my $field = shift @info) {
my $label = shift @info;
my $text = Wx::StaticText->new($self, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
$text->SetFont($Slic3r::GUI::small_font);
$grid_sizer->Add($text, 0);
$self->{"object_info_$field"} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
$self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font);
if ($field eq 'manifold') {
$self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self, -1, Wx::Bitmap->new($Slic3r::var->("error.png"), wxBITMAP_TYPE_PNG));
$self->{object_info_manifold_warning_icon}->Hide;
my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$h_sizer->Add($self->{object_info_manifold_warning_icon}, 0);
$h_sizer->Add($self->{"object_info_$field"}, 0);
$grid_sizer->Add($h_sizer, 0, wxEXPAND);
} else {
$grid_sizer->Add($self->{"object_info_$field"}, 0);
}
}
}
my $print_info_sizer;
{
my $box = Wx::StaticBox->new($self, -1, "Print Summary");
$print_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL);
$print_info_sizer->SetMinSize([350,-1]);
my $grid_sizer = Wx::FlexGridSizer->new(2, 2, 5, 5);
$grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
$grid_sizer->AddGrowableCol(1, 1);
$grid_sizer->AddGrowableCol(3, 1);
$print_info_sizer->Add($grid_sizer, 0, wxEXPAND);
my @info = (
fil => "Used Filament",
cost => "Cost",
);
while (my $field = shift @info) {
my $label = shift @info;
my $text = Wx::StaticText->new($self, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT);
$text->SetFont($Slic3r::GUI::small_font);
$grid_sizer->Add($text, 0);
$self->{"print_info_$field"} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
$self->{"print_info_$field"}->SetFont($Slic3r::GUI::small_font);
$grid_sizer->Add($self->{"print_info_$field"}, 0);
}
$self->{sliced_info_box} = $print_info_sizer;
}
my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$buttons_sizer->AddStretchSpacer(1);
$buttons_sizer->Add($self->{btn_export_stl}, 0, wxALIGN_RIGHT, 0);
$buttons_sizer->Add($self->{btn_print}, 0, wxALIGN_RIGHT, 0);
$buttons_sizer->Add($self->{btn_send_gcode}, 0, wxALIGN_RIGHT, 0);
$buttons_sizer->Add($self->{btn_export_gcode}, 0, wxALIGN_RIGHT, 0);
$self->{right_sizer} = my $right_sizer = Wx::BoxSizer->new(wxVERTICAL);
$right_sizer->Add($presets, 0, wxEXPAND | wxTOP, 10) if defined $presets;
$right_sizer->Add($buttons_sizer, 0, wxEXPAND | wxBOTTOM, 5);
$right_sizer->Add($self->{settings_override_panel}, 1, wxEXPAND, 5);
$right_sizer->Add($object_info_sizer, 0, wxEXPAND, 0);
$right_sizer->Add($print_info_sizer, 0, wxEXPAND, 0);
$right_sizer->Hide($print_info_sizer);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
$hsizer->Add($self->{preview_notebook}, 1, wxEXPAND | wxTOP, 1);
$hsizer->Add($right_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, 3);
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar};
$sizer->Add($self->{btoolbar}, 0, wxEXPAND, 0) if $self->{btoolbar};
$sizer->Add($hsizer, 1, wxEXPAND, 0);
$sizer->SetSizeHints($self);
$self->SetSizer($sizer);
}
$self->load_presets;
$self->_on_select_preset($_) for qw(printer filament print);
return $self;
}
sub prompt_unsaved_changes {
my ($self) = @_;
foreach my $group (qw(printer filament print)) {
foreach my $choice (@{$self->{preset_choosers}{$group}}) {
my $pp = $self->{preset_choosers_names}{$choice};
for my $i (0..$#$pp) {
my $preset = first { $_->name eq $pp->[$i] } @{wxTheApp->presets->{$group}};
if (!$preset->prompt_unsaved_changes($self)) {
# Restore the previous one
$choice->SetSelection($i);
return 0;
}
}
}
}
return 1;
}
sub _on_change_combobox {
my ($self, $group, $choice) = @_;
if (0) {
# This code is disabled because wxPerl doesn't provide GetCurrentSelection
my $current_name = $self->{preset_choosers_names}{$choice}[$choice->GetCurrentSelection];
my $current = first { $_->name eq $current_name } @{wxTheApp->presets->{$group}};
if (!$current->prompt_unsaved_changes($self)) {
# Restore the previous one
$choice->SetSelection($choice->GetCurrentSelection);
return;
}
} else {
return 0 if !$self->prompt_unsaved_changes;
}
wxTheApp->CallAfter(sub {
# Close the preset editor tab if any
if (exists $self->GetFrame->{preset_editor_tabs}{$group}) {
my $tabpanel = $self->GetFrame->{tabpanel};
$tabpanel->DeletePage($tabpanel->GetPageIndex($self->GetFrame->{preset_editor_tabs}{$group}));
delete $self->GetFrame->{preset_editor_tabs}{$group};
$tabpanel->SetSelection(0); # without this, a newly created tab will not be selected by wx
}
$self->_on_select_preset($group);
# This will remove the "(modified)" mark from any dirty preset handled here.
$self->load_presets;
});
}
sub _on_select_preset {
my ($self, $group) = @_;
my @presets = $self->selected_presets($group);
my $s_presets = $Slic3r::GUI::Settings->{presets};
my $changed = !$s_presets->{$group} || $s_presets->{$group} ne $presets[0]->name;
$s_presets->{$group} = $presets[0]->name;
$s_presets->{"${group}_${_}"} = $presets[$_]->name for 1..$#presets;
wxTheApp->save_settings;
# Ignore overrides in the plater, we only care about the preset configs.
my $config = $self->config(1);
$self->on_extruders_change(scalar @{$config->get('nozzle_diameter')});
if ($group eq 'print') {
my $o_config = $self->{settings_override_config};
my $o_panel = $self->{settings_override_panel};
my $shortcuts = $config->get('shortcuts');
# Re-populate the override panel with the configured shortcuts
# and the dirty options.
$o_config->clear;
foreach my $opt_key (@$shortcuts, $presets[0]->dirty_options) {
# Don't add shortcut for shortcuts!
next if $opt_key eq 'shortcuts';
$o_config->set($opt_key, $config->get($opt_key));
}
$o_panel->set_default_config($config);
$o_panel->set_fixed_options(\@$shortcuts);
$o_panel->update_optgroup;
} elsif ($group eq 'printer') {
# reload print and filament settings to honor their compatible_printer options
$self->load_presets;
}
$self->config_changed;
}
sub load_config {
my ($self, $config) = @_;
# This method is called with the CLI options.
# We add them to the visible overrides.
$self->{settings_override_config}->apply($config);
$self->{settings_override_panel}->update_optgroup;
$self->config_changed;
}
sub GetFrame {
my ($self) = @_;
return &Wx::GetTopLevelParent($self);
}
sub load_presets {
my ($self) = @_;
my $selected_printer_name;
foreach my $group (qw(printer filament print)) {
my @presets = @{wxTheApp->presets->{$group}};
# Skip presets not compatible with the selected printer, if they
#Â have other compatible printers configured (and at least one of them exists).
if ($group eq 'filament' || $group eq 'print') {
my %printer_names = map { $_->name => 1 } @{ wxTheApp->presets->{printer} };
for (my $i = 0; $i <= $#presets; ++$i) {
my $config = $presets[$i]->dirty_config;
next if !$config->has('compatible_printers');
my @compat = @{$config->compatible_printers};
if (@compat
&& (none { $_ eq $selected_printer_name } @compat)
&& (any { $printer_names{$_} } @compat)) {
splice @presets, $i, 1;
--$i;
}
}
}
# Only show the default presets if we have no other presets.
if (@presets > 1) {
@presets = grep { !$_->default } @presets;
}
# get the wxChoice objects for this group
my @choosers = @{ $self->{preset_choosers}{$group} };
# find the currently selected one(s) according to the saved file
my @sel = ();
if (my $current = $Slic3r::GUI::Settings->{presets}{$group}) {
push @sel, grep defined, first { $presets[$_]->name eq $current } 0..$#presets;
}
for my $i (1..(@choosers-1)) {
if (my $current = $Slic3r::GUI::Settings->{presets}{"${group}_$i"}) {
push @sel, grep defined, first { $presets[$_]->name eq $current } 0..$#presets;
}
}
@sel = (0) if !@sel;
# populate the wxChoice objects
my @preset_names = ();
foreach my $choice (@choosers) {
$choice->Clear;
$self->{preset_choosers_names}{$choice} = [];
foreach my $preset (@presets) {
# load/generate the proper icon
my $bitmap;
if ($group eq 'filament') {
my $config = $preset->dirty_config;
if ($preset->default || !$config->has('filament_colour')) {
$bitmap = Wx::Bitmap->new($Slic3r::var->("spool.png"), wxBITMAP_TYPE_PNG);
} else {
my $rgb_hex = $config->filament_colour->[0];
$rgb_hex =~ s/^#//;
my @rgb = unpack 'C*', pack 'H*', $rgb_hex;
my $image = Wx::Image->new(16,16);
$image->SetRGB(Wx::Rect->new(0,0,16,16), @rgb);
$bitmap = Wx::Bitmap->new($image);
}
} elsif ($group eq 'print') {
$bitmap = Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG);
} elsif ($group eq 'printer') {
$bitmap = Wx::Bitmap->new($Slic3r::var->("printer_empty.png"), wxBITMAP_TYPE_PNG);
}
$choice->AppendString($preset->dropdown_name, $bitmap);
push @{$self->{preset_choosers_names}{$choice}}, $preset->name;
}
my $selected = shift @sel;
if (defined $selected && $selected <= $#presets) {
# call SetSelection() only after SetString() otherwise the new string
# won't be picked up as the visible string
$choice->SetSelection($selected);
my $preset_name = $self->{preset_choosers_names}{$choice}[$selected];
push @preset_names, $preset_name;
# TODO: populate other filament preset placeholders
$selected_printer_name = $preset_name if $group eq 'printer';
}
}
$self->{print}->placeholder_parser->set_multiple("${group}_preset", [ @preset_names ]);
}
}
sub select_preset_by_name {
my ($self, $name, $group, $n) = @_;
# $n is optional
my $presets = wxTheApp->presets->{$group};
my $choosers = $self->{preset_choosers}{$group};
my $names = $self->{preset_choosers_names}{$choosers->[0]};
my $i = first { $names->[$_] eq $name } 0..$#$names;
return if !defined $i;
if (defined $n && $n <= $#$choosers) {
$choosers->[$n]->SetSelection($i);
} else {
$_->SetSelection($i) for @$choosers;
}
$self->_on_select_preset($group);
}
sub selected_presets {
my ($self, $group) = @_;
my %presets = ();
foreach my $group (qw(printer filament print)) {
$presets{$group} = [];
foreach my $choice (@{$self->{preset_choosers}{$group}}) {
my $sel = $choice->GetSelection;
$sel = 0 if $sel == -1;
push @{ $presets{$group} },
grep { $_->name eq $self->{preset_choosers_names}{$choice}[$sel] }
@{wxTheApp->presets->{$group}};
}
}
return $group ? @{$presets{$group}} : %presets;
}
sub show_preset_editor {
my ($self, $group, $i) = @_;
wxTheApp->CallAfter(sub {
my @presets = $self->selected_presets($group);
my $preset_editor;
my $dlg;
my $mainframe = $self->GetFrame;
my $tabpanel = $mainframe->{tabpanel};
if (exists $mainframe->{preset_editor_tabs}{$group}) {
# we already have an open editor
$tabpanel->SetSelection($tabpanel->GetPageIndex($mainframe->{preset_editor_tabs}{$group}));
return;
} elsif ($Slic3r::GUI::Settings->{_}{tabbed_preset_editors}) {
my $class = "Slic3r::GUI::PresetEditor::" . ucfirst($group);
$mainframe->{preset_editor_tabs}{$group} = $preset_editor = $class->new($self->GetFrame);
$tabpanel->AddPage($preset_editor, ucfirst($group) . " Settings", 1);
} else {
my $class = "Slic3r::GUI::PresetEditorDialog::" . ucfirst($group);
$dlg = $class->new($self);
$preset_editor = $dlg->preset_editor;
}
$preset_editor->select_preset_by_name($presets[$i // 0]->name);
$preset_editor->on_value_change(sub {
# Re-load the presets in order to toggle the (modified) suffix
$self->load_presets;
# Update shortcuts
$self->_on_select_preset($group);
# Use the new config wherever we actually use its contents
$self->config_changed;
});
my $cb = sub {
my ($group, $preset) = @_;
# Re-load the presets as they might have changed.
$self->load_presets;
# Select the preset in plater too
$self->select_preset_by_name($preset->name, $group, $i, 1);
};
$preset_editor->on_select_preset($cb);
$preset_editor->on_save_preset($cb);
if ($dlg) {
$dlg->Show;
}
});
}
# Returns the current config by merging the selected presets and the overrides.
sub config {
my ($self, $ignore_overrides) = @_;
# use a DynamicConfig because FullPrintConfig is not enough
my $config = Slic3r::Config->new_from_defaults;
# get defaults also for the values tracked by the Plater's config
# (for example 'shortcuts')
$config->apply(Slic3r::Config->new_from_defaults(@{$self->{config}->get_keys}));
my %classes = map { $_ => "Slic3r::GUI::PresetEditor::".ucfirst($_) }
qw(print filament printer);
my %presets = $self->selected_presets;
$config->apply($_->dirty_config) for @{ $presets{printer} };
if (@{ $presets{filament} }) {
my $filament_config = $presets{filament}[0]->dirty_config;
for my $i (1..$#{ $presets{filament} }) {
my $preset = $presets{filament}[$i];
my $config = $preset->dirty_config;
foreach my $opt_key (@{$config->get_keys}) {
if ($filament_config->has($opt_key)) {
my $value = $filament_config->get($opt_key);
next unless ref $value eq 'ARRAY';
$value->[$i] = $config->get($opt_key)->[0];
$filament_config->set($opt_key, $value);
}
}
}
$config->apply($filament_config);
}
$config->apply($_->dirty_config) for @{ $presets{print} };
$config->apply($self->{settings_override_config})
unless $ignore_overrides;
return $config;
}
sub get_object_index {
my $self = shift;
my ($object_indentifier) = @_;
return undef if !defined $object_indentifier;
for (my $i = 0; $i <= $#{$self->{objects}}; $i++){
if ($self->{objects}->[$i]->identifier eq $object_indentifier) {
return $i;
}
}
return undef;
}
sub add_undo_operation {
my $self = shift;
my @parameters = @_;
my $type = $parameters[0];
my $object_identifier = $parameters[1];
my @attributes = @parameters[2..$#parameters]; # operation values.
my $new_undo_operation = new Slic3r::GUI::Plater::UndoOperation($type, $object_identifier, \@attributes);
push @{$self->{undo_stack}}, $new_undo_operation;
$self->{redo_stack} = [];
$self->limit_undo_operations(8); # Current limit of undo/redo operations.
$self->GetFrame->on_undo_redo_stacks_changed;
return $new_undo_operation;
}
sub limit_undo_operations {
my ($self, $limit)= @_;
return if !defined $limit;
# Delete undo operations succeeded by 4 operations or more to save memory.
while ($#{$self->{undo_stack}} + 1 > $limit) {
print "Removing an old operation.\n";
splice @{$self->{undo_stack}}, 0, 1;
}
}
sub undo {
my $self = shift;
my $operation = pop @{$self->{undo_stack}};
return if !defined $operation;
push @{$self->{redo_stack}}, $operation;
my $type = $operation->{type};
if ($type eq "ROTATE") {
my $object_id = $operation->{object_identifier};
my $obj_idx = $self->get_object_index($object_id);
$self->select_object($obj_idx);
my $angle = $operation->{attributes}->[0];
my $axis = $operation->{attributes}->[1];
$self->rotate(-1 * $angle, $axis, 'true'); # Apply inverse transformation.
} elsif ($type eq "INCREASE") {
my $object_id = $operation->{object_identifier};
my $obj_idx = $self->get_object_index($object_id);
$self->select_object($obj_idx);
my $copies = $operation->{attributes}->[0];
$self->decrease($copies, 'true');
} elsif ($type eq "DECREASE") {
my $object_id = $operation->{object_identifier};
my $obj_idx = $self->get_object_index($object_id);
$self->select_object($obj_idx);
my $copies = $operation->{attributes}->[0];
$self->increase($copies, 'true');
} elsif ($type eq "MIRROR") {
my $object_id = $operation->{object_identifier};
my $obj_idx = $self->get_object_index($object_id);
$self->select_object($obj_idx);
my $axis = $operation->{attributes}->[0];
$self->mirror($axis, 'true');
} elsif ($type eq "REMOVE") {
my $_model = $operation->{attributes}->[0];
$self->load_model_objects(@{$_model->objects});
$self->{object_identifier}--; # Decrement the identifier as we will change the object identifier with the saved one.
$self->{objects}->[-1]->identifier($operation->{object_identifier});
} elsif ($type eq "CUT" || $type eq "SPLIT") {
# Delete the produced objects.
my $obj_identifiers_start = $operation->{attributes}->[2];
for (my $i_object = 0; $i_object < $#{$operation->{attributes}->[1]->objects} + 1; $i_object++) {
$self->remove($self->get_object_index($obj_identifiers_start++), 'true');
}
# Add the original object.
$self->load_model_objects(@{$operation->{attributes}->[0]->objects});
$self->{object_identifier}--;
$self->{objects}->[-1]->identifier($operation->{object_identifier}); # Add the original assigned identifier.
} elsif ($type eq "CHANGE_SCALE") {
my $object_id = $operation->{object_identifier};
my $obj_idx = $self->get_object_index($object_id);
$self->select_object($obj_idx);
my $axis = $operation->{attributes}->[0];
my $tosize = $operation->{attributes}->[1];
my $saved_scale = $operation->{attributes}->[3];
$self->changescale($axis, $tosize, $saved_scale, 'true');
} elsif ($type eq "RESET") {
# Revert changes to the plater object identifier. It's modified when adding new objects only not when undo/redo is executed.
my $current_objects_identifier = $self->{object_identifier};
my $_model = $operation->{attributes}->[0];
$self->load_model_objects(@{$_model->objects});
$self->{object_identifier} = $current_objects_identifier;
# don't forget the identifiers.
my $objects_count = $#{$operation->{attributes}->[0]->objects} + 1;
foreach my $identifier (@{$operation->{attributes}->[1]})
{
$self->{objects}->[-$objects_count]->identifier($identifier);
$objects_count--;
}
} elsif ($type eq "ADD") {
my $objects_count = $#{$operation->{attributes}->[0]->objects} + 1;
my $identifier_start = $operation->{attributes}->[1];
for (my $identifier = $identifier_start; $identifier < $objects_count + $identifier_start; $identifier++) {
my $obj_idx = $self->get_object_index($identifier);
$self->remove($obj_idx, 'true');
}
}
}
sub redo {
my $self = shift;
my $operation = pop @{$self->{redo_stack}};
return if !defined $operation;
push @{$self->{undo_stack}}, $operation;
my $type = $operation->{type};
if ($type eq "ROTATE") {
my $object_id = $operation->{object_identifier};
my $obj_idx = $self->get_object_index($object_id);
$self->select_object($obj_idx);
my $angle = $operation->{attributes}->[0];
my $axis = $operation->{attributes}->[1];
$self->rotate($angle, $axis, 'true');
} elsif ($type eq "INCREASE") {
my $object_id = $operation->{object_identifier};
my $obj_idx = $self->get_object_index($object_id);
$self->select_object($obj_idx);
my $copies = $operation->{attributes}->[0];
$self->increase($copies, 'true');
} elsif ($type eq "DECREASE") {
my $object_id = $operation->{object_identifier};
my $obj_idx = $self->get_object_index($object_id);
$self->select_object($obj_idx);
my $copies = $operation->{attributes}->[0];
$self->decrease($copies, 'true');
} elsif ($type eq "MIRROR") {
my $object_id = $operation->{object_identifier};
my $obj_idx = $self->get_object_index($object_id);
$self->select_object($obj_idx);
my $axis = $operation->{attributes}->[0];
$self->mirror($axis, 'true');
} elsif ($type eq "REMOVE") {
my $object_id = $operation->{object_identifier};
my $obj_idx = $self->get_object_index($object_id);
$self->select_object($obj_idx);
$self->remove(undef, 'true');
} elsif ($type eq "CUT" || $type eq "SPLIT") {
# Delete the org objects.
$self->remove($self->get_object_index($operation->{object_identifier}), 'true');
# Add the new objects and revert changes to the plater object identifier.
my $current_objects_identifier = $self->{object_identifier};
$self->load_model_objects(@{$operation->{attributes}->[1]->objects});
$self->{object_identifier} = $current_objects_identifier;
# Add their identifiers.
my $obj_identifiers_start = $operation->{attributes}->[2];
my $obj_count = $#{$operation->{attributes}->[1]->objects} + 1;
for (my $i_object = 0; $i_object <= $#{$operation->{attributes}->[1]->objects}; $i_object++){
$self->{objects}->[-$obj_count]->identifier($obj_identifiers_start++);
$obj_count--;
}
} elsif ($type eq "CHANGE_SCALE") {
my $object_id = $operation->{object_identifier};
my $obj_idx = $self->get_object_index($object_id);
$self->select_object($obj_idx);
my $axis = $operation->{attributes}->[0];
my $tosize = $operation->{attributes}->[1];
my $old_scale = $operation->{attributes}->[2];
$self->changescale($axis, $tosize, $old_scale, 'true');
} elsif ($type eq "RESET") {
$self->reset('true');
} elsif ($type eq "ADD") {
# Revert changes to the plater object identifier. It's modified when adding new objects only not when undo/redo is executed.
my $current_objects_identifier = $self->{object_identifier};
$self->load_model_objects(@{$operation->{attributes}->[0]->objects});
$self->{object_identifier} = $current_objects_identifier;
my $objects_count = $#{$operation->{attributes}->[0]->objects} + 1;
my $start_identifier = $operation->{attributes}->[1];
foreach my $object (@{$operation->{attributes}->[0]->objects})
{
$self->{objects}->[-$objects_count]->identifier($start_identifier++);
$objects_count--;
}
}
}
sub add {
my $self = shift;
# Save the current object identifier to track added objects.
my $start_object_id = $self->{object_identifier};
my @input_files = wxTheApp->open_model($self);
$self->load_file($_) for @input_files;
# Check if no objects are added.
if ($start_object_id == $self->{object_identifier}) {
return;
}
# Save the added objects.
my $new_model = $self->{model}->new;
# Get newly added objects count.
my $new_objects_count = $self->{object_identifier} - $start_object_id;
for (my $i_object = $start_object_id; $i_object < $new_objects_count + $start_object_id; $i_object++){
my $object_index = $self->get_object_index($i_object);
$new_model->add_object($self->{model}->get_object($object_index));
}
$self->add_undo_operation("ADD", undef, $new_model, $start_object_id);
}
sub add_tin {
my $self = shift;
my @input_files = wxTheApp->open_model($self);
return if !@input_files;
my $offset = Wx::GetNumberFromUser("", "Enter the minimum thickness in mm (i.e. the offset from the lowest point):", "2.5D TIN",
5, 0, 1000000, $self);
return if $offset < 0;
foreach my $input_file (@input_files) {
my $model = eval { Slic3r::Model->read_from_file($input_file) };
Slic3r::GUI::show_error($self, $@) if $@;
next if !$model;
if ($model->looks_like_multipart_object) {
Slic3r::GUI::show_error($self, "Multi-part models cannot be opened as 2.5D TIN files. Please load a single continuous mesh.");
next;
}
my $model_object = $model->get_object(0);
eval {
$model_object->get_volume(0)->extrude_tin($offset);
};
Slic3r::GUI::show_error($self, $@) if $@;
$self->load_model_objects($model_object);
}
}
sub load_file {
my $self = shift;
my ($input_file, $obj_idx_to_load) = @_;
$Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file);
wxTheApp->save_settings;
my $process_dialog = Wx::ProgressDialog->new('Loading…', "Processing input file…", 100, $self, 0);
$process_dialog->Pulse;
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
my $model = eval { Slic3r::Model->read_from_file($input_file) };
Slic3r::GUI::show_error($self, $@) if $@;
my @obj_idx = ();
if (defined $model) {
if ($model->looks_like_multipart_object) {
my $dialog = Wx::MessageDialog->new($self,
"This file contains several objects positioned at multiple heights. "
. "Instead of considering them as multiple objects, should I consider\n"
. "this file as a single object having multiple parts?\n",
'Multi-part object detected', wxICON_WARNING | wxYES | wxNO);
if ($dialog->ShowModal() == wxID_YES) {
$model->convert_multipart_object;
}
}
for my $obj_idx (0..($model->objects_count-1)) {
my $object = $model->objects->[$obj_idx];
$object->set_input_file($input_file);
for my $vol_idx (0..($object->volumes_count-1)) {
my $volume = $object->get_volume($vol_idx);
$volume->set_input_file($input_file);
$volume->set_input_file_obj_idx($obj_idx);
$volume->set_input_file_obj_idx($vol_idx);
}
}
my $i = 0;
if (defined $obj_idx_to_load) {
return () if $obj_idx_to_load >= $model->objects_count;
@obj_idx = $self->load_model_objects($model->get_object($obj_idx_to_load));
$i = $obj_idx_to_load;
} else {
@obj_idx = $self->load_model_objects(@{$model->objects});
}
foreach my $obj_idx (@obj_idx) {
$self->{objects}[$obj_idx]->input_file($input_file);
$self->{objects}[$obj_idx]->input_file_obj_idx($i++);
}
$self->statusbar->SetStatusText("Loaded " . basename($input_file));
if($self->{scaled_down}) {
$self->statusbar->SetStatusText('Your object appears to be too large, so it was automatically scaled down to fit your print bed.');
}
if($self->{outside_bounds}) {
$self->statusbar->SetStatusText('Some of your object(s) appear to be outside the print bed. Use the arrange button to correct this.');
}
}
$process_dialog->Destroy;
# Empty the redo stack
$self->{redo_stack} = [];
return @obj_idx;
}
sub load_model_objects {
my ($self, @model_objects) = @_;
# Always restart background process when adding new objects.
# This prevents lack of processing in some circumstances when background process is
# running but adding a new object does not invalidate anything.
$self->stop_background_process;
my $bed_centerf = $self->bed_centerf;
my $bed_shape = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape});
my $bed_size = $bed_shape->bounding_box->size;
my $need_arrange = 0;
my @obj_idx = ();
foreach my $model_object (@model_objects) {
my $o = $self->{model}->add_object($model_object);
$o->repair;
push @{ $self->{objects} }, Slic3r::GUI::Plater::Object->new(
name => $model_object->name || basename($model_object->input_file), identifier =>
$self->{object_identifier}++
);
push @obj_idx, $#{ $self->{objects} };
if ($model_object->instances_count == 0) {
if ($Slic3r::GUI::Settings->{_}{autocenter}) {
# if object has no defined position(s) we need to rearrange everything after loading
$need_arrange = 1;
# add a default instance and center object around origin
$o->center_around_origin; # also aligns object to Z = 0
$o->add_instance(offset => $bed_centerf);
} else {
# if user turned autocentering off, automatic arranging would disappoint them
$need_arrange = 0;
if ($Slic3r::GUI::Settings->{_}{autoalignz}) {
$o->align_to_ground; # aligns object to Z = 0
}
$o->add_instance();
}
} else {
if ($Slic3r::GUI::Settings->{_}{autoalignz}) {
# if object has defined positions we still need to ensure it's aligned to Z = 0
$o->align_to_ground;
}
}
{
# if the object is too large (more than 5 times the bed), scale it down
my $size = $o->bounding_box->size;
my $ratio = max(@$size[X,Y]) / unscale(max(@$bed_size[X,Y]));
if ($ratio > 5) {
$_->set_scaling_factor(1/$ratio) for @{$o->instances};
$self->{scaled_down} = 1;
}
}
{
# if after scaling the object does not fit on the bed provide a warning
my $bed_bounds = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape);
my $o_bounds = $o->bounding_box;
my $min = Slic3r::Pointf->new($o_bounds->x_min, $o_bounds->y_min);
my $max = Slic3r::Pointf->new($o_bounds->x_max, $o_bounds->y_max);
if (!$bed_bounds->contains_point($min) || !$bed_bounds->contains_point($max))
{
$self->{outside_bounds} = 1;
}
}
$self->{print}->auto_assign_extruders($o);
$self->{print}->add_model_object($o);
}
$self->make_thumbnail($_) for @obj_idx;
$self->arrange if $need_arrange;
$self->on_model_change;
# zoom to objects
$self->{canvas3D}->zoom_to_volumes
if $self->{canvas3D};
$self->object_list_changed;
return @obj_idx;
}
sub bed_centerf {
my ($self) = @_;
my $bed_shape = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape});
my $bed_center = $bed_shape->bounding_box->center;
return Slic3r::Pointf->new(unscale($bed_center->x), unscale($bed_center->y)); #)
}
sub remove {
my $self = shift;
my ($obj_idx, $dont_push) = @_;
$self->stop_background_process;
# Prevent toolpaths preview from rendering while we modify the Print object
$self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D};
$self->{preview3D}->enabled(0) if $self->{preview3D};
# if no object index is supplied, remove the selected one
if (!defined $obj_idx) {
($obj_idx, undef) = $self->selected_object;
}
# Save the object identifier and copy the object for undo/redo operations.
my $object_id = $self->{objects}->[$obj_idx]->identifier;
my $new_model = Slic3r::Model->new; # store this before calling get_object()
$new_model->add_object($self->{model}->get_object($obj_idx));
splice @{$self->{objects}}, $obj_idx, 1;
$self->{model}->delete_object($obj_idx);
$self->{print}->delete_object($obj_idx);
$self->object_list_changed;
$self->select_object(undef);
$self->on_model_change;
if (!defined $dont_push) {
$self->add_undo_operation("REMOVE", $object_id, $new_model);
}
}
sub reset {
my ($self, $dont_push) = @_;
$self->stop_background_process;
# Prevent toolpaths preview from rendering while we modify the Print object
$self->{toolpaths2D}->enabled(0) if $self->{toolpaths2D};
$self->{preview3D}->enabled(0) if $self->{preview3D};
# Save the current model.
my $current_model = $self->{model}->clone;
if (!defined $dont_push) {
# Get the identifiers of the curent model objects.
my $objects_identifiers = [];
for (my $i = 0; $i <= $#{$self->{objects}}; $i++){
push @{$objects_identifiers}, $self->{objects}->[$i]->identifier;
}
$self->add_undo_operation("RESET", undef, $current_model, $objects_identifiers);
}
@{$self->{objects}} = ();
$self->{model}->clear_objects;
$self->{print}->clear_objects;
$self->object_list_changed;
$self->select_object(undef);
$self->on_model_change;
}
sub increase {
my ($self, $copies, $dont_push) = @_;
$copies //= 1;
my ($obj_idx, $object) = $self->selected_object;
my $model_object = $self->{model}->objects->[$obj_idx];
my $instance = $model_object->instances->[-1];
for my $i (1..$copies) {
$instance = $model_object->add_instance(
offset => Slic3r::Pointf->new(map 10+$_, @{$instance->offset}),
z_translation => $instance->z_translation,
scaling_factor => $instance->scaling_factor,
scaling_vector => $instance->scaling_vector,
rotation => $instance->rotation,
x_rotation => $instance->x_rotation,
y_rotation => $instance->y_rotation,
);
$self->{print}->objects->[$obj_idx]->add_copy($instance->offset);
}
if (!defined $dont_push) {
$self->add_undo_operation("INCREASE", $object->identifier , $copies);
}
# only autoarrange if user has autocentering enabled
$self->stop_background_process;
if ($Slic3r::GUI::Settings->{_}{autocenter}) {
$self->arrange;
} else {
$self->on_model_change;
}
}
sub decrease {
my ($self, $copies, $dont_push) = @_;
$copies //= 1;
$self->stop_background_process;
my ($obj_idx, $object) = $self->selected_object;
my $model_object = $self->{model}->objects->[$obj_idx];
if ($model_object->instances_count > $copies) {
for my $i (1..$copies) {
$model_object->delete_last_instance;
$self->{print}->objects->[$obj_idx]->delete_last_copy;
}
if (!defined $dont_push) {
$self->add_undo_operation("DECREASE", $object->identifier, $copies);
}
} else {
$self->remove;
}
$self->on_model_change;
}
sub set_number_of_copies {
my ($self) = @_;
$self->pause_background_process;
# get current number of copies
my ($obj_idx, $object) = $self->selected_object;
my $model_object = $self->{model}->objects->[$obj_idx];
# prompt user
my $copies = Wx::GetNumberFromUser("", "Enter the number of copies of the selected object:", "Copies", $model_object->instances_count, 0, 1000, $self);
return if $copies == -1;
my $diff = $copies - $model_object->instances_count;
if ($diff == 0) {
#Â no variation
$self->resume_background_process;
} elsif ($diff > 0) {
$self->increase($diff);
} elsif ($diff < 0) {
$self->decrease(-$diff);
}
}
sub center_selected_object_on_bed {
my ($self) = @_;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
my $model_object = $self->{model}->objects->[$obj_idx];
my $bb = $model_object->bounding_box;
my $size = $bb->size;
my $vector = Slic3r::Pointf->new(
$self->bed_centerf->x - $bb->x_min - $size->x/2,
$self->bed_centerf->y - $bb->y_min - $size->y/2, #//
);
$_->offset->translate(@$vector) for @{$model_object->instances};
$self->refresh_canvases;
}
sub rotate {
my $self = shift;
my ($angle, $axis, $dont_push) = @_;
# angle is in degrees
$axis //= Z;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
my $model_object = $self->{model}->objects->[$obj_idx];
my $model_instance = $model_object->instances->[0];
# we need thumbnail to be computed before allowing rotation
return if !$object->thumbnail;
if (!defined $angle) {
my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z';
my $default = $axis == Z ? rad2deg($model_instance->rotation) : 0;
# Wx::GetNumberFromUser() does not support decimal numbers
$angle = Wx::GetTextFromUser("Enter the rotation angle:", "Rotate around $axis_name axis",
$default, $self);
return if !$angle || $angle !~ /^-?\d*(?:\.\d*)?$/ || $angle == -1;
}
$self->stop_background_process;
if ($axis == Z) {
my $new_angle = deg2rad($angle);
$_->set_rotation($_->rotation + $new_angle) for @{ $model_object->instances };
$object->transform_thumbnail($self->{model}, $obj_idx);
} else {
# rotation around X and Y needs to be performed on mesh
# so we first apply any Z rotation
$model_object->transform_by_instance($model_instance, 1);
$model_object->rotate(deg2rad($angle), $axis);
# realign object to Z = 0
$model_object->center_around_origin;
$self->make_thumbnail($obj_idx);
}
$model_object->update_bounding_box;
#Â update print and start background processing
$self->{print}->add_model_object($model_object, $obj_idx);
if (!defined $dont_push) {
$self->add_undo_operation("ROTATE", $object->identifier, $angle, $axis);
}
$self->selection_changed; # refresh info (size etc.)
$self->on_model_change;
}
sub mirror {
my ($self, $axis, $dont_push) = @_;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
my $model_object = $self->{model}->objects->[$obj_idx];
my $model_instance = $model_object->instances->[0];
# apply Z rotation before mirroring
$model_object->transform_by_instance($model_instance, 1);
$model_object->mirror($axis);
$model_object->update_bounding_box;
# realign object to Z = 0
$model_object->center_around_origin;
$self->make_thumbnail($obj_idx);
#Â update print and start background processing
$self->stop_background_process;
$self->{print}->add_model_object($model_object, $obj_idx);
if (!defined $dont_push) {
$self->add_undo_operation("MIRROR", $object->identifier, $axis);
}
$self->selection_changed; # refresh info (size etc.)
$self->on_model_change;
}
sub changescale {
my ($self, $axis, $tosize, $saved_scale, $dont_push) = @_;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
my $model_object = $self->{model}->objects->[$obj_idx];
my $model_instance = $model_object->instances->[0];
# we need thumbnail to be computed before allowing scaling
return if !$object->thumbnail;
my $object_size = $model_object->bounding_box->size;
my $bed_size = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape})->bounding_box->size;
my $old_scale;
my $scale;
if (defined $axis) {
my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z';
if (!defined $saved_scale) {
if ($tosize) {
my $cursize = $object_size->[$axis];
# Wx::GetNumberFromUser() does not support decimal numbers
my $newsize = Wx::GetTextFromUser(
sprintf("Enter the new size for the selected object (print bed: %smm):", $bed_size->[$axis]),
"Scale along $axis_name",
$cursize, $self);
return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
$scale = $newsize / $cursize * 100;
$old_scale = $cursize / $newsize * 100;
} else {
# Wx::GetNumberFromUser() does not support decimal numbers
$scale = Wx::GetTextFromUser("Enter the scale % for the selected object:",
"Scale along $axis_name", 100, $self);
$scale =~ s/%$//;
return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
$old_scale = 100 * 100 / $scale;
}
}
else {
$scale = $saved_scale;
}
# apply Z rotation before scaling
$model_object->transform_by_instance($model_instance, 1);
my $versor = [1,1,1];
$versor->[$axis] = $scale/100;
$model_object->scale_xyz(Slic3r::Pointf3->new(@$versor));
# object was already aligned to Z = 0, so no need to realign it
$self->make_thumbnail($obj_idx);
} else {
if (!defined $saved_scale) {
if ($tosize) {
my $cursize = max(@$object_size);
# Wx::GetNumberFromUser() does not support decimal numbers
my $newsize = Wx::GetTextFromUser("Enter the new max size for the selected object:",
"Scale", $cursize, $self);
return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
$scale = $model_instance->scaling_factor * $newsize / $cursize * 100;
$old_scale = $model_instance->scaling_factor * 100;
} else {
# max scale factor should be above 2540 to allow importing files exported in inches
# Wx::GetNumberFromUser() does not support decimal numbers
$scale = Wx::GetTextFromUser("Enter the scale % for the selected object:", 'Scale',
$model_instance->scaling_factor * 100, $self);
return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
$old_scale = $model_instance->scaling_factor * 100;
}
return if !$scale || $scale < 0;
} else {
$scale = $saved_scale;
}
$scale /= 100; # turn percent into factor
my $variation = $scale / $model_instance->scaling_factor;
foreach my $range (@{ $model_object->layer_height_ranges }) {
$range->[0] *= $variation;
$range->[1] *= $variation;
}
$_->set_scaling_factor($scale) for @{ $model_object->instances };
$object->transform_thumbnail($self->{model}, $obj_idx);
$scale *= 100;
}
# Add the new undo operation.
if (!defined $dont_push) {
$self->add_undo_operation("CHANGE_SCALE", $object->identifier, $axis, $tosize, $scale, $old_scale);
}
$model_object->update_bounding_box;
#Â update print and start background processing
$self->stop_background_process;
$self->{print}->add_model_object($model_object, $obj_idx);
$self->selection_changed(1); # refresh info (size, volume etc.)
$self->on_model_change;
}
sub arrange {
my $self = shift;
$self->pause_background_process;
my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($self->{config}->bed_shape);
my $success = $self->{model}->arrange_objects($self->config->min_object_distance, $bb);
# ignore arrange failures on purpose: user has visual feedback and we don't need to warn him
#Â when parts don't fit in print bed
$self->statusbar->SetStatusText('Objects were arranged.');
$self->on_model_change(1);
}
sub split_object {
my ($self, $dont_push) = @_;
my ($obj_idx, $current_object) = $self->selected_object;
# we clone model object because split_object() adds the split volumes
# into the same model object, thus causing duplicates when we call load_model_objects()
my $new_model = $self->{model}->clone; # store this before calling get_object()
my $current_model_object = $new_model->get_object($obj_idx);
if ($current_model_object->volumes_count > 1) {
Slic3r::GUI::warning_catcher($self)->("The selected object can't be split because it contains more than one volume/material.");
return;
}
$self->pause_background_process;
# Save the curent model object for undo/redo operataions.
my $org_object_model = Slic3r::Model->new;
$org_object_model->add_object($current_model_object);
# Save the org object identifier.
my $object_id = $self->{objects}->[$obj_idx]->identifier;
my @model_objects = @{$current_model_object->split_object};
if (@model_objects == 1) {
$self->resume_background_process;
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it contains only one part.");
$self->resume_background_process;
return;
}
foreach my $object (@model_objects) {
$object->instances->[$_]->offset->translate($_ * 10, $_ * 10)
for 1..$#{ $object->instances };
# we need to center this single object around origin
$object->center_around_origin;
}
# remove the original object before spawning the object_loaded event, otherwise
# we'll pass the wrong $obj_idx to it (which won't be recognized after the
# thumbnail thread returns)
$self->remove($obj_idx, 'true'); # Don't push to the undo stack it's considered a split opeation not a remove one.
$current_object = $obj_idx = undef;
# Save the object identifiers used in undo/redo operations.
my $new_objects_id_start = $self->{object_identifier};
print "The new object identifier start for split is " .$new_objects_id_start . "\n";
# load all model objects at once, otherwise the plate would be rearranged after each one
# causing original positions not to be kept
$self->load_model_objects(@model_objects);
# Create two models to save the current object and the resulted objects.
my $new_objects_model = Slic3r::Model->new;
foreach my $new_object (@model_objects) {
$new_objects_model->add_object($new_object);
}
$self->add_undo_operation("SPLIT", $object_id, $org_object_model, $new_objects_model, $new_objects_id_start);
}
sub toggle_print_stats {
my ($self, $show) = @_;
return if !$self->GetFrame->is_loaded;
if ($show) {
$self->{right_sizer}->Show($self->{sliced_info_box});
} else {
$self->{right_sizer}->Hide($self->{sliced_info_box});
}
$self->{right_sizer}->Layout;
}
sub config_changed {
my $self = shift;
my $config = $self->config;
if ($Slic3r::GUI::autosave) {
$config->save($Slic3r::GUI::autosave);
}
# Apply changes to the plater-specific config options.
foreach my $opt_key (@{$self->{config}->diff($config)}) {
# Ignore overrides. No need to set them in our config; we'll use them directly below.
next if $opt_key eq 'overrides';
$self->{config}->set($opt_key, $config->get($opt_key));
if ($opt_key eq 'bed_shape') {
$self->{canvas}->update_bed_size;
$self->{canvas3D}->update_bed_size if $self->{canvas3D};
$self->{preview3D}->set_bed_shape($self->{config}->bed_shape)
if $self->{preview3D};
$self->on_model_change;
} elsif ($opt_key eq 'serial_port') {
if ($config->get('serial_port')) {
$self->{btn_print}->Show;
} else {
$self->{btn_print}->Hide;
}
$self->Layout;
} elsif ($opt_key eq 'print_host') {
if ($config->get('print_host')) {
$self->{btn_send_gcode}->Show;
} else {
$self->{btn_send_gcode}->Hide;
}
$self->Layout;
}
}
return if !$self->GetFrame->is_loaded;
$self->toggle_print_stats(0);
if ($Slic3r::GUI::Settings->{_}{background_processing}) {
# (re)start timer
$self->schedule_background_process;
} else {
$self->async_apply_config;
}
}
sub schedule_background_process {
my ($self) = @_;
warn 'schedule_background_process() is not supposed to be called when background processing is disabled'
if !$Slic3r::GUI::Settings->{_}{background_processing};
$self->{processed} = 0;
if (defined $self->{apply_config_timer}) {
$self->{apply_config_timer}->Start(PROCESS_DELAY, 1); # 1 = one shot
}
}
# Executed asynchronously by a timer every PROCESS_DELAY (0.5 second).
# The timer is started by schedule_background_process(),
sub async_apply_config {
my ($self) = @_;
# pause process thread before applying new config
#Â since we don't want to touch data that is being used by the threads
$self->pause_background_process;
# apply new config
my $invalidated = $self->{print}->apply_config($self->config);
# reset preview canvases (invalidated contents will be hidden)
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
$self->{preview3D}->reload_print if $self->{preview3D};
$self->{AdaptiveLayersDialog}->reload_preview if $self->{AdaptiveLayersDialog};
if (!$Slic3r::GUI::Settings->{_}{background_processing}) {
$self->hide_preview if $invalidated;
return;
}
if ($invalidated) {
# kill current thread if any
$self->stop_background_process;
# remove the sliced statistics box because something changed.
$self->toggle_print_stats(0);
} else {
$self->resume_background_process;
}
# schedule a new process thread in case it wasn't running
$self->start_background_process;
}
sub start_background_process {
my ($self) = @_;
return if !$Slic3r::have_threads;
return if $self->{process_thread};
if (!@{$self->{objects}}) {
$self->on_process_completed;
return;
}
# It looks like declaring a local $SIG{__WARN__} prevents the ugly
# "Attempt to free unreferenced scalar" warning...
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
# don't start process thread if config is not valid
eval {
# this will throw errors if config is not valid
$self->config->validate;
$self->{print}->validate;
};
if ($@) {
$self->statusbar->SetStatusText($@);
return;
}
if ($Slic3r::GUI::Settings->{_}{threads}) {
$self->{print}->config->set('threads', $Slic3r::GUI::Settings->{_}{threads});
}
# start thread
@_ = ();
$self->{process_thread} = Slic3r::spawn_thread(sub {
eval {
$self->{print}->process;
};
if ($@) {
Slic3r::debugf "Background process error: $@\n";
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, $@));
} else {
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROCESS_COMPLETED_EVENT, undef));
}
Slic3r::thread_cleanup();
});
Slic3r::debugf "Background processing started.\n";
}
sub stop_background_process {
my ($self) = @_;
$self->{apply_config_timer}->Stop if defined $self->{apply_config_timer};
$self->statusbar->SetCancelCallback(undef);
$self->statusbar->StopBusy;
$self->statusbar->SetStatusText("");
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
$self->{preview3D}->reload_print if $self->{preview3D};
$self->{AdaptiveLayersDialog}->reload_preview if $self->{AdaptiveLayersDialog};
if ($self->{process_thread}) {
Slic3r::debugf "Killing background process.\n";
Slic3r::kill_all_threads();
$self->{process_thread} = undef;
} else {
Slic3r::debugf "No background process running.\n";
}
# if there's an export process, kill that one as well
if ($self->{export_thread}) {
Slic3r::debugf "Killing background export process.\n";
Slic3r::kill_all_threads();
$self->{export_thread} = undef;
}
}
sub pause_background_process {
my ($self) = @_;
if ($self->{process_thread} || $self->{export_thread}) {
Slic3r::pause_all_threads();
return 1;
} elsif (defined $self->{apply_config_timer} && $self->{apply_config_timer}->IsRunning) {
$self->{apply_config_timer}->Stop;
return 0; # we didn't actually pause any running thread; need to reschedule
}
return 0;
}
sub resume_background_process {
my ($self) = @_;
if ($self->{process_thread} || $self->{export_thread}) {
Slic3r::resume_all_threads();
}
}
sub export_gcode {
my ($self, $output_file) = @_;
return if !@{$self->{objects}};
if ($self->{export_gcode_output_file}) {
Wx::MessageDialog->new($self, "Another export job is currently running.", 'Error', wxOK | wxICON_ERROR)->ShowModal;
return;
}
# if process is not running, validate config
# (we assume that if it is running, config is valid)
eval {
# this will throw errors if config is not valid
$self->config->validate;
$self->{print}->validate;
};
Slic3r::GUI::catch_error($self) and return;
# apply config and validate print
my $config = $self->config;
eval {
# this will throw errors if config is not valid
$config->validate;
$self->{print}->apply_config($config);
$self->{print}->validate;
};
if (!$Slic3r::have_threads) {
Slic3r::GUI::catch_error($self) and return;
}
# select output file
if ($output_file) {
$self->{export_gcode_output_file} = $self->{print}->output_filepath($output_file);
} else {
my $default_output_file = $self->{print}->output_filepath($main::opt{output} // '');
my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', wxTheApp->output_path(dirname($default_output_file)),
basename($default_output_file), &Slic3r::GUI::FILE_WILDCARDS->{gcode}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
return;
}
my $path = Slic3r::decode_path($dlg->GetPath);
$Slic3r::GUI::Settings->{_}{last_output_path} = dirname($path);
wxTheApp->save_settings;
$self->{export_gcode_output_file} = $path;
$dlg->Destroy;
}
$self->statusbar->StartBusy;
if ($Slic3r::have_threads) {
$self->statusbar->SetCancelCallback(sub {
$self->stop_background_process;
$self->statusbar->SetStatusText("Export cancelled");
$self->{export_gcode_output_file} = undef;
$self->{send_gcode_file} = undef;
# this updates buttons status
$self->object_list_changed;
});
# start background process, whose completion event handler
# will detect $self->{export_gcode_output_file} and proceed with export
$self->start_background_process;
} else {
eval {
$self->{print}->process;
$self->{print}->export_gcode(output_file => $self->{export_gcode_output_file});
};
my $result = !Slic3r::GUI::catch_error($self);
$self->on_export_completed($result);
}
# this updates buttons status
$self->object_list_changed;
$self->toggle_print_stats(1);
return $self->{export_gcode_output_file};
}
# This gets called only if we have threads.
sub on_process_completed {
my ($self, $error) = @_;
$self->statusbar->SetCancelCallback(undef);
$self->statusbar->StopBusy;
$self->statusbar->SetStatusText($error // "");
Slic3r::debugf "Background processing completed.\n";
$self->{process_thread}->detach if $self->{process_thread};
$self->{process_thread} = undef;
$self->{processed} = 1;
# if we're supposed to perform an explicit export let's display the error in a dialog
if ($error && $self->{export_gcode_output_file}) {
$self->{export_gcode_output_file} = undef;
Slic3r::GUI::show_error($self, $error);
}
return if $error;
$self->{toolpaths2D}->reload_print if $self->{toolpaths2D};
$self->{preview3D}->reload_print if $self->{preview3D};
$self->{AdaptiveLayersDialog}->reload_preview if $self->{AdaptiveLayersDialog};
# if we have an export filename, start a new thread for exporting G-code
if ($self->{export_gcode_output_file}) {
@_ = ();
# workaround for "Attempt to free un referenced scalar..."
our $_thread_self = $self;
$self->{export_thread} = Slic3r::spawn_thread(sub {
eval {
$_thread_self->{print}->export_gcode(output_file => $_thread_self->{export_gcode_output_file});
};
if ($@) {
Wx::PostEvent($_thread_self, Wx::PlThreadEvent->new(-1, $ERROR_EVENT, shared_clone([ $@ ])));
Wx::PostEvent($_thread_self, Wx::PlThreadEvent->new(-1, $EXPORT_COMPLETED_EVENT, 0));
} else {
Wx::PostEvent($_thread_self, Wx::PlThreadEvent->new(-1, $EXPORT_COMPLETED_EVENT, 1));
}
Slic3r::thread_cleanup();
});
Slic3r::debugf "Background G-code export started.\n";
}
}
# This gets called also if we have no threads.
sub on_progress_event {
my ($self, $percent, $message) = @_;
$self->statusbar->SetProgress($percent);
$self->statusbar->SetStatusText("$message…");
}
# This gets called also if we don't have threads.
sub on_export_completed {
my ($self, $result) = @_;
$self->statusbar->SetCancelCallback(undef);
$self->statusbar->StopBusy;
$self->statusbar->SetStatusText("");
Slic3r::debugf "Background export process completed.\n";
$self->{export_thread}->detach if $self->{export_thread};
$self->{export_thread} = undef;
my $message;
my $send_gcode = 0;
my $do_print = 0;
if ($result) {
if ($self->{print_file}) {
$message = "File added to print queue";
$do_print = 1;
} elsif ($self->{send_gcode_file}) {
$message = "Sending G-code file to the " . $self->{config}->host_type . " server...";
$send_gcode = 1;
} else {
$message = "G-code file exported to " . $self->{export_gcode_output_file};
}
} else {
$message = "Export failed";
}
$self->{export_gcode_output_file} = undef;
$self->statusbar->SetStatusText($message);
wxTheApp->notify($message);
$self->do_print if $do_print;
$self->send_gcode if $send_gcode;
$self->{print_file} = undef;
$self->{send_gcode_file} = undef;
{
my $fil = sprintf(
'%.2fcm (%.2fcm³%s)',
$self->{print}->total_used_filament / 10,
$self->{print}->total_extruded_volume / 1000,
$self->{print}->total_weight
? sprintf(', %.2fg', $self->{print}->total_weight)
: '',
);
my $cost = $self->{print}->total_cost
? sprintf("%.2f" , $self->{print}->total_cost)
: 'n.a.';
$self->{print_info_fil}->SetLabel($fil);
$self->{print_info_cost}->SetLabel($cost);
}
# this updates buttons status
$self->object_list_changed;
}
sub do_print {
my ($self) = @_;
my $controller = $self->GetFrame->{controller} or return;
my %current_presets = $self->selected_presets;
my $printer_panel = $controller->add_printer($current_presets{printer}->[0], $self->config);
my $filament_stats = $self->{print}->filament_stats;
$filament_stats = { map { $current_presets{filament}[$_]->name => $filament_stats->{$_} } keys %$filament_stats };
$printer_panel->load_print_job($self->{print_file}, $filament_stats);
$self->GetFrame->select_tab(1);
}
sub prepare_send {
my ($self, $skip_dialog) = @_;
return if !$self->{btn_send_gcode}->IsEnabled;
my $filename = basename($self->{print}->output_filepath($main::opt{output} // ''));
if (!$skip_dialog) {
# When the alt key is pressed, bypass the dialog.
my $dlg = Slic3r::GUI::Plater::OctoPrintSpoolDialog->new($self, $filename);
return unless $dlg->ShowModal == wxID_OK;
$filename = $dlg->{filename};
}
if (!$Slic3r::GUI::Settings->{octoprint}{overwrite}) {
my $progress = Wx::ProgressDialog->new('Querying OctoPrint…',
"Checking whether file already exists…", 100, $self, 0);
$progress->Pulse;
my $ua = LWP::UserAgent->new;
$ua->timeout(5);
my $res;
if ($self->{config}->print_host) {
if($self->{config}->host_type eq 'octoprint'){
$res = $ua->get(
"http://" . $self->{config}->print_host . "/api/files/local",
'X-Api-Key' => $self->{config}->octoprint_apikey,
);
}else {
$res = $ua->get(
"http://" . $self->{config}->print_host . "/rr_files",
);
}
}
$progress->Destroy;
if ($res->is_success) {
my $searchterm = ($self->{config}->host_type eq 'octoprint') ? '/"name":\s*"\Q$filename\E"/' : '"'.$filename.'"';
if ($res->decoded_content =~ $searchterm) {
my $dialog = Wx::MessageDialog->new($self,
"It looks like a file with the same name already exists in the server. "
. "Shall I overwrite it?",
$self->{config}->host_type, wxICON_WARNING | wxYES | wxNO);
if ($dialog->ShowModal() == wxID_NO) {
return;
}
}
} else {
my $message = "Error while connecting to the " . $self->{config}->host_type . " server: " . $res->status_line;
Slic3r::GUI::show_error($self, $message);
return;
}
}
$self->{send_gcode_file_print} = $Slic3r::GUI::Settings->{octoprint}{start};
$self->{send_gcode_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir() . "/" . $filename);
}
sub send_gcode {
my ($self) = @_;
$self->statusbar->StartBusy;
my $ua = LWP::UserAgent->new;
$ua->timeout(180);
my $path = Slic3r::encode_path($self->{send_gcode_file});
my $filename = basename($self->{print}->output_filepath($main::opt{output} // ''));
my $res;
if($self->{config}->print_host){
if($self->{config}->host_type eq 'octoprint'){
$res = $ua->post(
"http://" . $self->{config}->print_host . "/api/files/local",
Content_Type => 'form-data',
'X-Api-Key' => $self->{config}->octoprint_apikey,
Content => [
# OctoPrint doesn't like Windows paths so we use basename()
# Also, since we need to read from filesystem we process it through encode_path()
file => [ $path, basename($path) ],
print => $self->{send_gcode_file_print} ? 1 : 0,
],
);
}else{
# slurp the file we would send into a string - should be someplace to reference this but could not find it?
local $/=undef;
open (my $gch,$path);
my $gcode=<$gch>;
close($gch);
# get the time string
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
my $t = sprintf("%4d-%02d-%02dT%02d:%02d:%02d",$year+1900,$mon+1,$mday,$hour,$min,$sec);
my $req = HTTP::Request->new(POST => "http://" . $self->{config}->print_host . "/rr_upload?name=0:/gcodes/" . basename($path) . "&time=$t",);
$req->content( $gcode );
$res = $ua->request($req);
if ($res->is_success) {
if ($self->{send_gcode_file_print}) {
$res = $ua->get(
"http://" . $self->{config}->print_host . "/rr_gcode?gcode=M32%20" . basename($path),
);
}
}
}
}
$self->statusbar->StopBusy;
if ($res->is_success) {
$self->statusbar->SetStatusText("G-code file successfully uploaded to the " . $self->{config}->host_type . " server");
} else {
my $message = "Error while uploading to the " . $self->{config}->host_type . " server: " . $res->status_line;
Slic3r::GUI::show_error($self, $message);
$self->statusbar->SetStatusText($message);
}
}
sub export_stl {
my $self = shift;
return if !@{$self->{objects}};
my $output_file = $self->_get_export_file('STL') or return;
$self->{model}->write_stl($output_file, 1);
$self->statusbar->SetStatusText("STL file exported to $output_file");
}
sub reload_from_disk {
my ($self) = @_;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
if (!$object->input_file) {
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be reloaded because it isn't referenced to its input file any more. This is the case after performing operations like cut or split.");
return;
}
if (!-e $object->input_file) {
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be reloaded because the file doesn't exist anymore on the disk.");
return;
}
# Only reload the selected object and not all objects from the input file.
my @new_obj_idx = $self->load_file($object->input_file, $object->input_file_obj_idx);
if (!@new_obj_idx) {
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be reloaded because the new file doesn't contain the object.");
return;
}
my $org_obj = $self->{model}->objects->[$obj_idx];
# check if the object is dependant of more than one file
my $org_obj_has_modifiers=0;
for my $i (0..($org_obj->volumes_count-1)) {
if ($org_obj->input_file ne $org_obj->get_volume($i)->input_file) {
$org_obj_has_modifiers=1;
last;
}
}
my $reload_behavior = $Slic3r::GUI::Settings->{_}{reload_behavior};
# ask the user how to proceed, if option is selected in preferences
if ($org_obj_has_modifiers && !$Slic3r::GUI::Settings->{_}{reload_hide_dialog}) {
my $dlg = Slic3r::GUI::ReloadDialog->new(undef,$reload_behavior);
my $res = $dlg->ShowModal;
if ($res==wxID_CANCEL) {
$self->remove($_) for @new_obj_idx;
$dlg->Destroy;
return;
}
$reload_behavior = $dlg->GetSelection;
my $save = 0;
if ($reload_behavior != $Slic3r::GUI::Settings->{_}{reload_behavior}) {
$Slic3r::GUI::Settings->{_}{reload_behavior} = $reload_behavior;
$save = 1;
}
if ($dlg->GetHideOnNext) {
$Slic3r::GUI::Settings->{_}{reload_hide_dialog} = 1;
$save = 1;
}
Slic3r::GUI->save_settings if $save;
$dlg->Destroy;
}
my $volume_unmatched=0;
foreach my $new_obj_idx (@new_obj_idx) {
my $new_obj = $self->{model}->objects->[$new_obj_idx];
$new_obj->clear_instances;
$new_obj->add_instance($_) for @{$org_obj->instances};
$new_obj->config->apply($org_obj->config);
my $new_vol_idx = 0;
my $org_vol_idx = 0;
my $new_vol_count=$new_obj->volumes_count;
my $org_vol_count=$org_obj->volumes_count;
while ($new_vol_idx<=$new_vol_count-1) {
if (($org_vol_idx<=$org_vol_count-1) && ($org_obj->get_volume($org_vol_idx)->input_file eq $new_obj->input_file)) {
# apply config from the matching volumes
$new_obj->get_volume($new_vol_idx++)->config->apply($org_obj->get_volume($org_vol_idx++)->config);
} else {
# reload has more volumes than original (first file), apply config from the first volume
$new_obj->get_volume($new_vol_idx++)->config->apply($org_obj->get_volume(0)->config);
$volume_unmatched=1;
}
}
$org_vol_idx=$org_vol_count if $reload_behavior==2; # Reload behavior: discard
while (($org_vol_idx<=$org_vol_count-1) && ($org_obj->get_volume($org_vol_idx)->input_file eq $new_obj->input_file)) {
# original has more volumes (first file), skip those
$org_vol_idx++;
$volume_unmatched=1;
}
while ($org_vol_idx<=$org_vol_count-1) {
if ($reload_behavior==1) { # Reload behavior: copy
my $new_volume = $new_obj->add_volume($org_obj->get_volume($org_vol_idx));
$new_volume->mesh->translate(@{$org_obj->origin_translation->negative});
$new_volume->mesh->translate(@{$new_obj->origin_translation});
if ($new_volume->name =~ m/link to path\z/) {
my $new_name = $new_volume->name;
$new_name =~ s/ - no link to path$/ - copied/;
$new_volume->set_name($new_name);
}elsif(!($new_volume->name =~ m/copied\z/)) {
$new_volume->set_name($new_volume->name . " - copied");
}
}else{ # Reload behavior: Reload all, also fallback solution if ini was manually edited to a wrong value
if ($org_obj->get_volume($org_vol_idx)->input_file) {
my $model = eval { Slic3r::Model->read_from_file($org_obj->get_volume($org_vol_idx)->input_file) };
if ($@) {
$org_obj->get_volume($org_vol_idx)->set_input_file("");
}elsif ($org_obj->get_volume($org_vol_idx)->input_file_obj_idx > ($model->objects_count-1)) {
# Object Index for that part / modifier not found in current version of the file
$org_obj->get_volume($org_vol_idx)->set_input_file("");
}else{
my $prt_mod_obj = $model->objects->[$org_obj->get_volume($org_vol_idx)->input_file_obj_idx];
if ($org_obj->get_volume($org_vol_idx)->input_file_vol_idx > ($prt_mod_obj->volumes_count-1)) {
# Volume Index for that part / modifier not found in current version of the file
$org_obj->get_volume($org_vol_idx)->set_input_file("");
}else{
# all checks passed, load new mesh and copy metadata
my $new_volume = $new_obj->add_volume($prt_mod_obj->get_volume($org_obj->get_volume($org_vol_idx)->input_file_vol_idx));
$new_volume->set_input_file($org_obj->get_volume($org_vol_idx)->input_file);
$new_volume->set_input_file_obj_idx($org_obj->get_volume($org_vol_idx)->input_file_obj_idx);
$new_volume->set_input_file_vol_idx($org_obj->get_volume($org_vol_idx)->input_file_vol_idx);
$new_volume->config->apply($org_obj->get_volume($org_vol_idx)->config);
$new_volume->set_modifier($org_obj->get_volume($org_vol_idx)->modifier);
$new_volume->mesh->translate(@{$new_obj->origin_translation});
}
}
}
if (!$org_obj->get_volume($org_vol_idx)->input_file) {
my $new_volume = $new_obj->add_volume($org_obj->get_volume($org_vol_idx)); # error -> copy old mesh
$new_volume->mesh->translate(@{$org_obj->origin_translation->negative});
$new_volume->mesh->translate(@{$new_obj->origin_translation});
if ($new_volume->name =~ m/copied\z/) {
my $new_name = $new_volume->name;
$new_name =~ s/ - copied$/ - no link to path/;
$new_volume->set_name($new_name);
}elsif(!($new_volume->name =~ m/link to path\z/)) {
$new_volume->set_name($new_volume->name . " - no link to path");
}
$volume_unmatched=1;
}
}
$org_vol_idx++;
}
}
$self->remove($obj_idx);
# TODO: refresh object list which contains wrong count and scale
# Trigger thumbnail generation again, because the remove() method altered
#Â object indexes before background thumbnail generation called its completion
# event, so the on_thumbnail_made callback is called with the wrong $obj_idx.
# When porting to C++ we'll probably have cleaner ways to do this.
$self->make_thumbnail($_-1) for @new_obj_idx;
#Â update print
$self->stop_background_process;
$self->{print}->reload_object($_-1) for @new_obj_idx;
$self->on_model_change;
# Empty the redo stack
$self->{redo_stack} = [];
if ($volume_unmatched) {
Slic3r::GUI::warning_catcher($self)->("At least 1 volume couldn't be matched between the original object and the reloaded one.");
}
}
sub export_object_stl {
my $self = shift;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
my $model_object = $self->{model}->objects->[$obj_idx];
my $output_file = $self->_get_export_file('STL') or return;
$model_object->mesh->write_binary($output_file);
$self->statusbar->SetStatusText("STL file exported to $output_file");
}
# Export function for a single AMF output
sub export_object_amf {
my $self = shift;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
my $local_model = Slic3r::Model->new;
my $model_object = $self->{model}->objects->[$obj_idx];
# copy model_object -> local_model
$local_model->add_object($model_object);
my $output_file = $self->_get_export_file('AMF') or return;
$local_model->write_amf($output_file);
$self->statusbar->SetStatusText("AMF file exported to $output_file");
}
# Export function for a single 3MF output
sub export_object_tmf {
my $self = shift;
my ($obj_idx, $object) = $self->selected_object;
return if !defined $obj_idx;
my $local_model = Slic3r::Model->new;
my $model_object = $self->{model}->objects->[$obj_idx];
# copy model_object -> local_model
$local_model->add_object($model_object);
my $output_file = $self->_get_export_file('TMF') or return;
$local_model->write_tmf($output_file);
$self->statusbar->SetStatusText("3MF file exported to $output_file");
}
sub export_amf {
my $self = shift;
return if !@{$self->{objects}};
my $output_file = $self->_get_export_file('AMF') or return;
$self->{model}->write_amf($output_file);
$self->statusbar->SetStatusText("AMF file exported to $output_file");
}
sub export_tmf {
my $self = shift;
return if !@{$self->{objects}};
my $output_file = $self->_get_export_file('TMF') or return;
$self->{model}->write_tmf($output_file);
$self->statusbar->SetStatusText("3MF file exported to $output_file");
}
sub _get_export_file {
my $self = shift;
my ($format) = @_;
my $suffix = $format eq 'STL' ? '.stl' : ( $format eq 'AMF' ? '.amf' : '.3mf');
my $output_file = $main::opt{output};
{
$output_file = $self->{print}->output_filepath($output_file // '');
$output_file =~ s/\.gcode$/$suffix/i;
my $dlg;
$dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file),
basename($output_file), &Slic3r::GUI::STL_MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT)
if $format eq 'STL';
$dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file),
basename($output_file), &Slic3r::GUI::AMF_MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT)
if $format eq 'AMF';
$dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file),
basename($output_file), &Slic3r::GUI::TMF_MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT)
if $format eq 'TMF';
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
return undef;
}
$output_file = Slic3r::decode_path($dlg->GetPath);
$dlg->Destroy;
}
return $output_file;
}
sub make_thumbnail {
my $self = shift;
my ($obj_idx) = @_;
my $plater_object = $self->{objects}[$obj_idx];
$plater_object->thumbnail(Slic3r::ExPolygon::Collection->new);
my $cb = sub {
$plater_object->make_thumbnail($self->{model}, $obj_idx);
if ($Slic3r::have_threads) {
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $THUMBNAIL_DONE_EVENT, shared_clone([ $obj_idx ])));
Slic3r::thread_cleanup();
threads->exit;
} else {
$self->on_thumbnail_made($obj_idx);
}
};
@_ = ();
$Slic3r::have_threads
? threads->create(sub { $cb->(); Slic3r::thread_cleanup(); })->detach
: $cb->();
}
sub on_thumbnail_made {
my $self = shift;
my ($obj_idx) = @_;
$self->{objects}[$obj_idx]->transform_thumbnail($self->{model}, $obj_idx);
$self->refresh_canvases;
}
# this method gets called whenever print center is changed or the objects' bounding box changes
# (i.e. when an object is added/removed/moved/rotated/scaled)
sub on_model_change {
my ($self, $force_autocenter) = @_;
# reload the select submenu (if already initialized)
if (my $menu = $self->GetFrame->{plater_select_menu}) {
$menu->DeleteItem($_) for $menu->GetMenuItems;
for my $i (0..$#{$self->{objects}}) {
my $name = $self->{objects}->[$i]->name;
my $count = $self->{model}->get_object($i)->instances_count;
if ($count > 1) {
$name .= " (${count}x)";
}
my $item = wxTheApp->append_menu_item($menu, $name, 'Select object', sub {
$self->select_object($i);
$self->refresh_canvases;
}, undef, undef, wxITEM_CHECK);
$item->Check(1) if $self->{objects}->[$i]->selected;
}
}
# reload the objects info choice
if (my $choice = $self->{object_info_choice}) {
$choice->Clear;
for my $i (0..$#{$self->{objects}}) {
my $name = $self->{objects}->[$i]->name;
my $count = $self->{model}->get_object($i)->instances_count;
if ($count > 1) {
$name .= " (${count}x)";
}
$choice->Append($name);
}
my ($obj_idx, $object) = $self->selected_object;
$choice->SetSelection($obj_idx // -1);
}
my $running = $self->pause_background_process;
if ($Slic3r::GUI::Settings->{_}{autocenter} || $force_autocenter) {
$self->{model}->center_instances_around_point($self->bed_centerf);
}
$self->refresh_canvases;
my $invalidated = $self->{print}->reload_model_instances();
if ($Slic3r::GUI::Settings->{_}{background_processing}) {
if ($invalidated || !$running) {
# The mere fact that no steps were invalidated when reloading model instances
# doesn't mean that all steps were done: for example, validation might have
# failed upon previous instance move, so we have no running thread and no steps
#Â are invalidated on this move, thus we need to schedule a new run.
$self->schedule_background_process;
$self->toggle_print_stats(0);
} else {
$self->resume_background_process;
}
} else {
$self->hide_preview;
}
}
sub hide_preview {
my ($self) = @_;
my $sel = $self->{preview_notebook}->GetSelection;
if ($sel == $self->{preview3D_page_idx} || $sel == $self->{toolpaths2D_page_idx}) {
$self->{preview_notebook}->SetSelection(0);
}
$self->{processed} = 0;
}
sub on_extruders_change {
my ($self, $num_extruders) = @_;
my $choices = $self->{preset_choosers}{filament};
while (@$choices < $num_extruders) {
# copy strings from first choice
my @presets = $choices->[0]->GetStrings;
# initialize new choice
my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [@presets], wxCB_READONLY);
push @$choices, $choice;
# copy icons from first choice
$choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
# settings button
my $settings_btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
# insert new row into sizer
$self->{presets_sizer}->Insert(6 + ($#$choices-1)*3, 0, 0);
$self->{presets_sizer}->Insert(7 + ($#$choices-1)*3, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING);
$self->{presets_sizer}->Insert(8 + ($#$choices-1)*3, $settings_btn, 0, wxEXPAND | wxLEFT, 4);
#Â setup the listeners
EVT_COMBOBOX($choice, $choice, sub {
my ($choice) = @_;
wxTheApp->CallAfter(sub {
$self->_on_change_combobox('filament', $choice);
});
});
EVT_BUTTON($self, $settings_btn, sub {
$self->show_preset_editor('filament', $#$choices);
});
# initialize selection
my $i = first { $choice->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets;
$choice->SetSelection($i || 0);
}
# remove unused choices if any
while (@$choices > $num_extruders) {
my $i = 6 + ($#$choices-1)*3;
$self->{presets_sizer}->Remove($i); # label
$self->{presets_sizer}->Remove($i); # wxChoice
my $settings_btn = $self->{presets_sizer}->GetItem($i)->GetWindow;
$self->{presets_sizer}->Remove($i); # settings btn
$settings_btn->Destroy;
$choices->[-1]->Destroy;
pop @$choices;
}
$self->Layout;
}
sub object_cut_dialog {
my $self = shift;
my ($obj_idx) = @_;
if (!defined $obj_idx) {
($obj_idx, undef) = $self->selected_object;
}
if (!$Slic3r::GUI::have_OpenGL) {
Slic3r::GUI::show_error($self, "Please install the OpenGL modules to use this feature (see build instructions).");
return;
}
my $dlg = Slic3r::GUI::Plater::ObjectCutDialog->new($self,
object => $self->{objects}[$obj_idx],
model_object => $self->{model}->objects->[$obj_idx],
);
return unless $dlg->ShowModal == wxID_OK;
if (my @new_objects = $dlg->NewModelObjects) {
my $process_dialog = Wx::ProgressDialog->new('Loading…', "Loading new objects…", 100, $self, 0);
$process_dialog->Pulse;
# Create two models to save the current object and the resulted objects.
my $new_objects_model = Slic3r::Model->new;
foreach my $new_object (@new_objects) {
$new_objects_model->add_object($new_object);
}
my $org_object_model = Slic3r::Model->new;
$org_object_model->add_object($self->{model}->get_object($obj_idx));
# Save the object identifiers used in undo/redo operations.
my $object_id = $self->{objects}->[$obj_idx]->identifier;
my $new_objects_id_start = $self->{object_identifier};
$self->add_undo_operation("CUT", $object_id, $org_object_model, $new_objects_model, $new_objects_id_start);
$self->remove($obj_idx, 'true');
$self->load_model_objects(grep defined($_), @new_objects);
$self->arrange if @new_objects <= 2; # don't arrange for grid cuts
$process_dialog->Destroy;
}
}
sub object_layers_dialog {
my $self = shift;
my ($obj_idx) = @_;
$self->object_settings_dialog($obj_idx, adaptive_layers => 1);
}
sub object_settings_dialog {
my $self = shift;
my ($obj_idx, %params) = @_;
if (!defined $obj_idx) {
($obj_idx, undef) = $self->selected_object;
}
my $model_object = $self->{model}->objects->[$obj_idx];
# validate config before opening the settings dialog because
# that dialog can't be closed if validation fails, but user
# can't fix any error which is outside that dialog
return unless $self->validate_config;
my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog->new($self,
object => $self->{objects}[$obj_idx],
model_object => $model_object,
obj_idx => $obj_idx,
);
# store pointer to the adaptive layer tab to push preview updates
$self->{AdaptiveLayersDialog} = $dlg->{adaptive_layers};
# and jump directly to the tab if called by "promo-button"
$dlg->{tabpanel}->SetSelection(1) if $params{adaptive_layers};
$self->pause_background_process;
$dlg->ShowModal;
$self->{AdaptiveLayersDialog} = undef;
# update thumbnail since parts may have changed
if ($dlg->PartsChanged) {
# recenter and re-align to Z = 0
$model_object->center_around_origin;
$self->make_thumbnail($obj_idx);
}
#Â update print
if ($dlg->PartsChanged || $dlg->PartSettingsChanged) {
$self->stop_background_process;
$self->{print}->reload_object($obj_idx);
$self->on_model_change;
} else {
$self->resume_background_process;
}
}
sub object_list_changed {
my $self = shift;
my $have_objects = @{$self->{objects}} ? 1 : 0;
my $method = $have_objects ? 'Enable' : 'Disable';
$self->{"btn_$_"}->$method
for grep $self->{"btn_$_"}, qw(reset arrange export_gcode export_stl print send_gcode);
if ($self->{export_gcode_output_file} || $self->{send_gcode_file}) {
$self->{btn_export_gcode}->Disable;
$self->{btn_print}->Disable;
$self->{btn_send_gcode}->Disable;
}
if ($self->{htoolbar}) {
$self->{htoolbar}->EnableTool($_, $have_objects)
for (TB_RESET, TB_ARRANGE);
}
# prepagate the event to the frame (a custom Wx event would be cleaner)
$self->GetFrame->on_plater_object_list_changed($have_objects);
}
sub selection_changed {
my $self = shift;
my ($obj_idx, $object) = $self->selected_object;
my $have_sel = defined $obj_idx;
# Remove selection in 2d Plater.
$self->{canvas}->{selected_instance} = undef;
if (my $menu = $self->GetFrame->{plater_select_menu}) {
$_->Check(0) for $menu->GetMenuItems;
if ($have_sel) {
$menu->FindItemByPosition($obj_idx)->Check(1);
}
}
my $method = $have_sel ? 'Enable' : 'Disable';
$self->{"btn_$_"}->$method
for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut layers settings);
if ($self->{htoolbar}) {
$self->{htoolbar}->EnableTool($_, $have_sel)
for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_LAYERS, TB_SETTINGS);
}
if ($self->{object_info_size}) { # have we already loaded the info pane?
if ($have_sel) {
my $model_object = $self->{model}->objects->[$obj_idx];
$self->{object_info_choice}->SetSelection($obj_idx);
$self->{object_info_copies}->SetLabel($model_object->instances_count);
my $model_instance = $model_object->instances->[0];
{
my $size_string = sprintf "%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size};
if ($model_instance->scaling_factor != 1) {
$size_string .= sprintf " (%s%%)", $model_instance->scaling_factor * 100;
}
$self->{object_info_size}->SetLabel($size_string);
}
$self->{object_info_materials}->SetLabel($model_object->materials_count);
my $raw_mesh = $model_object->raw_mesh;
$raw_mesh->repair; # this calculates number_of_parts
if (my $stats = $raw_mesh->stats) {
$self->{object_info_volume}->SetLabel(sprintf('%.2f', $raw_mesh->volume * ($model_instance->scaling_factor**3)));
$self->{object_info_facets}->SetLabel(sprintf('%d (%d shells)', $model_object->facets_count, $stats->{number_of_parts}));
if (my $errors = sum(@$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)})) {
$self->{object_info_manifold}->SetLabel(sprintf("Auto-repaired (%d errors)", $errors));
$self->{object_info_manifold_warning_icon}->Show;
# we don't show normals_fixed because we never provide normals
# to admesh, so it generates normals for all facets
my $message = sprintf '%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges',
@$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)};
$self->{object_info_manifold}->SetToolTipString($message);
$self->{object_info_manifold_warning_icon}->SetToolTipString($message);
} else {
$self->{object_info_manifold}->SetLabel("Yes");
}
} else {
$self->{object_info_facets}->SetLabel($object->facets);
}
} else {
$self->{object_info_choice}->SetSelection(-1);
$self->{"object_info_$_"}->SetLabel("") for qw(copies size volume facets materials manifold);
$self->{object_info_manifold_warning_icon}->Hide;
$self->{object_info_manifold}->SetToolTipString("");
}
$self->Layout;
}
# prepagate the event to the frame (a custom Wx event would be cleaner)
$self->GetFrame->on_plater_selection_changed($have_sel);
}
sub select_object {
my ($self, $obj_idx) = @_;
$_->selected(0) for @{ $self->{objects} };
$_->selected_instance(-1) for @{ $self->{objects} };
if (defined $obj_idx) {
$self->{objects}->[$obj_idx]->selected(1);
$self->{objects}->[$obj_idx]->selected_instance(0);
}
$self->selection_changed(1);
}
sub select_next {
my ($self) = @_;
return if !@{$self->{objects}};
my ($obj_idx, $object) = $self->selected_object;
if (!defined $obj_idx || $obj_idx == $#{$self->{objects}}) {
$obj_idx = 0;
} else {
$obj_idx++;
}
$self->select_object($obj_idx);
$self->refresh_canvases;
}
sub select_prev {
my ($self) = @_;
return if !@{$self->{objects}};
my ($obj_idx, $object) = $self->selected_object;
if (!defined $obj_idx || $obj_idx == 0) {
$obj_idx = $#{$self->{objects}};
} else {
$obj_idx--;
}
$self->select_object($obj_idx);
$self->refresh_canvases;
}
sub selected_object {
my $self = shift;
my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} };
return undef if !defined $obj_idx;
return ($obj_idx, $self->{objects}[$obj_idx]),
}
sub refresh_canvases {
my ($self) = @_;
$self->{canvas}->Refresh;
$self->{canvas3D}->update if $self->{canvas3D};
$self->{preview3D}->reload_print if $self->{preview3D};
}
sub validate_config {
my $self = shift;
eval {
$self->config->validate;
};
return 0 if Slic3r::GUI::catch_error($self);
return 1;
}
sub statusbar {
my $self = shift;
return $self->GetFrame->{statusbar};
}
sub object_menu {
my ($self) = @_;
my $frame = $self->GetFrame;
my $menu = Wx::Menu->new;
wxTheApp->append_menu_item($menu, "Delete\tCtrl+Del", 'Remove the selected object', sub {
$self->remove;
}, undef, 'brick_delete.png');
wxTheApp->append_menu_item($menu, "Increase copies\tCtrl++", 'Place one more copy of the selected object', sub {
$self->increase;
}, undef, 'add.png');
wxTheApp->append_menu_item($menu, "Decrease copies\tCtrl+-", 'Remove one copy of the selected object', sub {
$self->decrease;
}, undef, 'delete.png');
wxTheApp->append_menu_item($menu, "Set number of copies…", 'Change the number of copies of the selected object', sub {
$self->set_number_of_copies;
}, undef, 'textfield.png');
$menu->AppendSeparator();
wxTheApp->append_menu_item($menu, "Move to bed center", 'Center object around bed center', sub {
$self->center_selected_object_on_bed;
}, undef, 'arrow_in.png');
wxTheApp->append_menu_item($menu, "Rotate 45° clockwise", 'Rotate the selected object by 45° clockwise', sub {
$self->rotate(-45);
}, undef, 'arrow_rotate_clockwise.png');
wxTheApp->append_menu_item($menu, "Rotate 45° counter-clockwise", 'Rotate the selected object by 45° counter-clockwise', sub {
$self->rotate(+45);
}, undef, 'arrow_rotate_anticlockwise.png');
{
my $rotateMenu = Wx::Menu->new;
wxTheApp->append_menu_item($rotateMenu, "Around X axis…", 'Rotate the selected object by an arbitrary angle around X axis', sub {
$self->rotate(undef, X);
}, undef, 'bullet_red.png');
wxTheApp->append_menu_item($rotateMenu, "Around Y axis…", 'Rotate the selected object by an arbitrary angle around Y axis', sub {
$self->rotate(undef, Y);
}, undef, 'bullet_green.png');
wxTheApp->append_menu_item($rotateMenu, "Around Z axis…", 'Rotate the selected object by an arbitrary angle around Z axis', sub {
$self->rotate(undef, Z);
}, undef, 'bullet_blue.png');
wxTheApp->append_submenu($menu, "Rotate", 'Rotate the selected object by an arbitrary angle', $rotateMenu, undef, 'textfield.png');
}
{
my $mirrorMenu = Wx::Menu->new;
wxTheApp->append_menu_item($mirrorMenu, "Along X axis…", 'Mirror the selected object along the X axis', sub {
$self->mirror(X);
}, undef, 'bullet_red.png');
wxTheApp->append_menu_item($mirrorMenu, "Along Y axis…", 'Mirror the selected object along the Y axis', sub {
$self->mirror(Y);
}, undef, 'bullet_green.png');
wxTheApp->append_menu_item($mirrorMenu, "Along Z axis…", 'Mirror the selected object along the Z axis', sub {
$self->mirror(Z);
}, undef, 'bullet_blue.png');
wxTheApp->append_submenu($menu, "Mirror", 'Mirror the selected object', $mirrorMenu, undef, 'shape_flip_horizontal.png');
}
{
my $scaleMenu = Wx::Menu->new;
wxTheApp->append_menu_item($scaleMenu, "Uniformly…", 'Scale the selected object along the XYZ axes', sub {
$self->changescale(undef);
});
wxTheApp->append_menu_item($scaleMenu, "Along X axis…", 'Scale the selected object along the X axis', sub {
$self->changescale(X);
}, undef, 'bullet_red.png');
wxTheApp->append_menu_item($scaleMenu, "Along Y axis…", 'Scale the selected object along the Y axis', sub {
$self->changescale(Y);
}, undef, 'bullet_green.png');
wxTheApp->append_menu_item($scaleMenu, "Along Z axis…", 'Scale the selected object along the Z axis', sub {
$self->changescale(Z);
}, undef, 'bullet_blue.png');
wxTheApp->append_submenu($menu, "Scale", 'Scale the selected object by a given factor', $scaleMenu, undef, 'arrow_out.png');
}
{
my $scaleToSizeMenu = Wx::Menu->new;
wxTheApp->append_menu_item($scaleToSizeMenu, "Uniformly…", 'Scale the selected object along the XYZ axes', sub {
$self->changescale(undef, 1);
});
wxTheApp->append_menu_item($scaleToSizeMenu, "Along X axis…", 'Scale the selected object along the X axis', sub {
$self->changescale(X, 1);
}, undef, 'bullet_red.png');
wxTheApp->append_menu_item($scaleToSizeMenu, "Along Y axis…", 'Scale the selected object along the Y axis', sub {
$self->changescale(Y, 1);
}, undef, 'bullet_green.png');
wxTheApp->append_menu_item($scaleToSizeMenu, "Along Z axis…", 'Scale the selected object along the Z axis', sub {
$self->changescale(Z, 1);
}, undef, 'bullet_blue.png');
wxTheApp->append_submenu($menu, "Scale to size", 'Scale the selected object to match a given size', $scaleToSizeMenu, undef, 'arrow_out.png');
}
wxTheApp->append_menu_item($menu, "Split", 'Split the selected object into individual parts', sub {
$self->split_object;
}, undef, 'shape_ungroup.png');
wxTheApp->append_menu_item($menu, "Cut…", 'Open the 3D cutting tool', sub {
$self->object_cut_dialog;
}, undef, 'package.png');
wxTheApp->append_menu_item($menu, "Layer heights…", 'Open the dynamic layer height control', sub {
$self->object_layers_dialog;
}, undef, 'variable_layer_height.png');
$menu->AppendSeparator();
wxTheApp->append_menu_item($menu, "Settings…", 'Open the object editor dialog', sub {
$self->object_settings_dialog;
}, undef, 'cog.png');
$menu->AppendSeparator();
wxTheApp->append_menu_item($menu, "Reload from Disk", 'Reload the selected file from Disk', sub {
$self->reload_from_disk;
}, undef, 'arrow_refresh.png');
wxTheApp->append_menu_item($menu, "Export object as STL…", 'Export this single object as STL file', sub {
$self->export_object_stl;
}, undef, 'brick_go.png');
wxTheApp->append_menu_item($menu, "Export object and modifiers as AMF…", 'Export this single object and all associated modifiers as AMF file', sub {
$self->export_object_amf;
}, undef, 'brick_go.png');
wxTheApp->append_menu_item($menu, "Export object and modifiers as 3MF…", 'Export this single object and all associated modifiers as 3MF file', sub {
$self->export_object_tmf;
}, undef, 'brick_go.png');
return $menu;
}
# Set a camera direction, zoom to all objects.
sub select_view {
my ($self, $direction) = @_;
my $idx_page = $self->{preview_notebook}->GetSelection;
my $page = ($idx_page == &Wx::wxNOT_FOUND) ? '3D' : $self->{preview_notebook}->GetPageText($idx_page);
if ($page eq 'Preview') {
$self->{preview3D}->canvas->select_view($direction);
$self->{canvas3D}->set_viewport_from_scene($self->{preview3D}->canvas);
} else {
$self->{canvas3D}->select_view($direction);
$self->{preview3D}->canvas->set_viewport_from_scene($self->{canvas3D});
}
}
sub zoom{
my ($self, $direction) = @_;
#Apply Zoom to the current active tab
my ($currentSelection) = $self->{preview_notebook}->GetSelection;
if($currentSelection == 0){
$self->{canvas3D}->zoom($direction) if($self->{canvas3D});
}
elsif($currentSelection == 2){ #3d Preview tab
$self->{preview3D}->canvas->zoom($direction) if($self->{preview3D});
}
elsif($currentSelection == 3) { #2D toolpaths tab
$self->{toolpaths2D}->{canvas}->zoom($direction) if($self->{toolpaths2D});
}
}
package Slic3r::GUI::Plater::DropTarget;
use Wx::DND;
use base 'Wx::FileDropTarget';
sub new {
my $class = shift;
my ($window) = @_;
my $self = $class->SUPER::new;
$self->{window} = $window;
return $self;
}
sub OnDropFiles {
my $self = shift;
my ($x, $y, $filenames) = @_;
# stop scalars leaking on older perl
# https://rt.perl.org/rt3/Public/Bug/Display.html?id=70602
@_ = ();
# only accept STL, OBJ and AMF files
return 0 if grep !/\.(?:stl|obj|amf(?:\.xml)?)$/i, @$filenames;
$self->{window}->load_file($_) for @$filenames;
}
# 2D preview of an object. Each object is previewed by its convex hull.
package Slic3r::GUI::Plater::Object;
use Moo;
use List::Util qw(first);
use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad);
has 'name' => (is => 'rw', required => 1);
has 'identifier' => (is => 'rw', required => 1);
has 'input_file' => (is => 'rw');
has 'input_file_obj_idx' => (is => 'rw');
has 'thumbnail' => (is => 'rw'); # ExPolygon::Collection in scaled model units with no transforms
has 'transformed_thumbnail' => (is => 'rw');
has 'instance_thumbnails' => (is => 'ro', default => sub { [] }); # array of ExPolygon::Collection objects, each one representing the actual placed thumbnail of each instance in pixel units
has 'selected' => (is => 'rw', default => sub { 0 });
has 'selected_instance' => (is => 'rw', default => sub { -1 });
sub make_thumbnail {
my ($self, $model, $obj_idx) = @_;
# make method idempotent
$self->thumbnail->clear;
my $mesh = $model->objects->[$obj_idx]->raw_mesh;
# Apply x, y rotations and scaling vector in case of reading a 3MF model object.
my $model_instance = $model->objects->[$obj_idx]->instances->[0];
$mesh->rotate_x($model_instance->x_rotation);
$mesh->rotate_y($model_instance->y_rotation);
$mesh->scale_xyz($model_instance->scaling_vector);
if ($mesh->facets_count <= 5000) {
# remove polygons with area <= 1mm
my $area_threshold = Slic3r::Geometry::scale 1;
$self->thumbnail->append(
grep $_->area >= $area_threshold,
@{ $mesh->horizontal_projection }, # horizontal_projection returns scaled expolygons
);
$self->thumbnail->simplify(0.5);
} else {
my $convex_hull = Slic3r::ExPolygon->new($mesh->convex_hull);
$self->thumbnail->append($convex_hull);
}
return $self->thumbnail;
}
sub transform_thumbnail {
my ($self, $model, $obj_idx) = @_;
return unless defined $self->thumbnail;
my $model_object = $model->objects->[$obj_idx];
my $model_instance = $model_object->instances->[0];
# the order of these transformations MUST be the same everywhere, including
# in Slic3r::Print->add_model_object()
my $t = $self->thumbnail->clone;
$t->rotate($model_instance->rotation, Slic3r::Point->new(0,0));
$t->scale($model_instance->scaling_factor);
$self->transformed_thumbnail($t);
}
package Slic3r::GUI::Plater::OctoPrintSpoolDialog;
use Wx qw(:dialog :id :misc :sizer :icon wxTheApp);
use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER);
use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent, $filename) = @_;
my $self = $class->SUPER::new($parent, -1, "Send to Server", wxDefaultPosition,
[400, -1]);
$self->{filename} = $filename;
$Slic3r::GUI::Settings->{octoprint} //= {};
my $optgroup;
$optgroup = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Send to Server',
on_change => sub {
my ($opt_id) = @_;
if ($opt_id eq 'filename') {
$self->{filename} = $optgroup->get_value($opt_id);
} else {
$Slic3r::GUI::Settings->{octoprint}{$opt_id} = $optgroup->get_value($opt_id);
}
},
label_width => 200,
);
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'filename',
type => 's',
label => 'File name',
width => 200,
tooltip => 'The name used for labelling the print job.',
default => $filename,
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'overwrite',
type => 'bool',
label => 'Overwrite existing file',
tooltip => 'If selected, any existing file with the same name will be overwritten without confirmation.',
default => $Slic3r::GUI::Settings->{octoprint}{overwrite} // 0,
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'start',
type => 'bool',
label => 'Start print',
tooltip => 'If selected, print will start after the upload.',
default => $Slic3r::GUI::Settings->{octoprint}{start} // 0,
));
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxTOP | wxBOTTOM | wxLEFT | wxRIGHT, 10);
my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
EVT_BUTTON($self, wxID_OK, sub {
wxTheApp->save_settings;
$self->EndModal(wxID_OK);
$self->Destroy;
});
$self->SetSizer($sizer);
$sizer->SetSizeHints($self);
return $self;
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Plater/ 0000775 0000000 0000000 00000000000 13274424355 0016443 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/lib/Slic3r/GUI/Plater/2D.pm 0000664 0000000 0000000 00000043043 13274424355 0017252 0 ustar 00root root 0000000 0000000 # 2D preview on the platter.
# 3D objects are visualized by their convex hulls.
package Slic3r::GUI::Plater::2D;
use strict;
use warnings;
use utf8;
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_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';
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::Colour->new(@BACKGROUND255));
$self->{objects} = $objects;
$self->{model} = $model;
$self->{config} = $config;
$self->{on_select_object} = sub {};
$self->{on_double_click} = sub {};
$self->{on_right_click} = sub {};
$self->{on_instances_moved} = sub {};
$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(@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);
EVT_SIZE($self, sub {
$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;
}
sub on_select_object {
my ($self, $cb) = @_;
$self->{on_select_object} = $cb;
}
sub on_double_click {
my ($self, $cb) = @_;
$self->{on_double_click} = $cb;
}
sub on_right_click {
my ($self, $cb) = @_;
$self->{on_right_click} = $cb;
}
sub on_instances_moved {
my ($self, $cb) = @_;
$self->{on_instances_moved} = $cb;
}
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);
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 bed
{
$dc->SetPen($self->{print_center_pen});
$dc->SetBrush($self->{bed_brush});
$dc->DrawPolygon($self->scaled_points_to_pixel($self->{bed_polygon}, 1), 0, 0);
}
# draw print center
if (@{$self->{objects}} && $Slic3r::GUI::Settings->{_}{autocenter}) {
my $center = $self->unscaled_point_to_pixel($self->{print_center});
$dc->SetPen($self->{print_center_pen});
$dc->DrawLine($center->[X], 0, $center->[X], $size[Y]);
$dc->DrawLine(0, $center->[Y], $size[X], $center->[Y]);
$dc->SetTextForeground(Wx::Colour->new(0,0,0));
$dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
$dc->DrawLabel("X = " . sprintf('%.0f', $self->{print_center}->[X]), Wx::Rect->new(0, 0, $center->[X]*2, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_BOTTOM);
$dc->DrawRotatedText("Y = " . sprintf('%.0f', $self->{print_center}->[Y]), 0, $center->[Y]+15, 90);
}
# draw frame
if (0) {
$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(@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($self->{dark_pen});
$self->clean_instance_thumbnails;
for my $obj_idx (0 .. $#{$self->{objects}}) {
my $object = $self->{objects}[$obj_idx];
my $model_object = $self->{model}->objects->[$obj_idx];
next unless defined $object->thumbnail;
for my $instance_idx (0 .. $#{$model_object->instances}) {
my $instance = $model_object->instances->[$instance_idx];
next if !defined $object->transformed_thumbnail;
my $thumbnail = $object->transformed_thumbnail->clone; # in scaled model coordinates
$thumbnail->translate(map scale($_), @{$instance->offset});
$object->instance_thumbnails->[$instance_idx] = $thumbnail;
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 {
$dc->SetBrush($self->{objects_brush});
}
foreach my $expolygon (@$thumbnail) {
foreach my $points (@{$expolygon->pp}) {
$dc->DrawPolygon($self->scaled_points_to_pixel($points, 1), 0, 0);
}
}
if (0) {
# draw bounding box for debugging purposes
my $bb = $model_object->instance_bounding_box($instance_idx);
$bb->scale($self->{scaling_factor});
# no need to translate by instance offset because instance_bounding_box() does that
my $points = $bb->polygon->pp;
$dc->SetPen($self->{clearance_pen});
$dc->SetBrush($self->{transparent_brush});
$dc->DrawPolygon($self->_y($points), 0, 0);
}
# if sequential printing is enabled and we have more than one object, draw clearance area
if ($self->{config}->complete_objects && (map @{$_->instances}, @{$self->{model}->objects}) > 1) {
my ($clearance) = @{offset([$thumbnail->convex_hull], (scale($self->{config}->extruder_clearance_radius) / 2), 1, JT_ROUND, scale(0.1))};
$dc->SetPen($self->{clearance_pen});
$dc->SetBrush($self->{transparent_brush});
$dc->DrawPolygon($self->scaled_points_to_pixel($clearance, 1), 0, 0);
}
}
}
# draw skirt
if (@{$self->{objects}} && $self->{config}->skirts) {
my @points = map @{$_->contour}, map @$_, map @{$_->instance_thumbnails}, @{$self->{objects}};
if (@points >= 3) {
my ($convex_hull) = @{offset([convex_hull(\@points)], scale max($self->{config}->brim_width + $self->{config}->skirt_distance), 1, JT_ROUND, scale(0.1))};
$dc->SetPen($self->{skirt_pen});
$dc->SetBrush($self->{transparent_brush});
$dc->DrawPolygon($self->scaled_points_to_pixel($convex_hull, 1), 0, 0);
}
}
$event->Skip;
}
sub mouse_event {
my ($self, $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}}) {
my $object = $self->{objects}->[$obj_idx];
for my $instance_idx (reverse 0 .. $#{ $object->instance_thumbnails }) {
my $thumbnail = $object->instance_thumbnails->[$instance_idx];
if (defined first { $_->contour->contains_point($point) } @$thumbnail) {
$self->{on_select_object}->($obj_idx);
if ($event->LeftDown) {
# start dragging
my $instance = $self->{model}->objects->[$obj_idx]->instances->[$instance_idx];
my $instance_origin = [ map scale($_), @{$instance->offset} ];
$self->{drag_start_pos} = [ # displacement between the click and the instance origin in scaled model units
$point->x - $instance_origin->[X],
$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);
}
last OBJECTS;
}
}
}
$self->Refresh;
} elsif ($event->LeftUp) {
$self->{on_instances_moved}->()
if $self->{drag_object};
$self->{drag_start_pos} = undef;
$self->{drag_object} = undef;
$self->SetCursor(wxSTANDARD_CURSOR);
} elsif ($event->LeftDClick) {
$self->{on_double_click}->();
} elsif ($event->Dragging) {
return if !$self->{drag_start_pos}; # concurrency problems
my ($obj_idx, $instance_idx) = @{ $self->{drag_object} };
my $model_object = $self->{model}->objects->[$obj_idx];
$model_object->instances->[$instance_idx]->set_offset(
Slic3r::Pointf->new(
unscale($point->[X] - $self->{drag_start_pos}[X]),
unscale($point->[Y] - $self->{drag_start_pos}[Y]),
));
$model_object->update_bounding_box;
$self->Refresh;
} elsif ($event->Moving) {
my $cursor = wxSTANDARD_CURSOR;
if (defined first { $_->contour->contains_point($point) } map @$_, map @{$_->instance_thumbnails}, @{ $self->{objects} }) {
$cursor = Wx::Cursor->new(wxCURSOR_HAND);
}
$self->SetCursor($cursor);
}
}
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;
# 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;
# get bed shape polygon
$self->{bed_polygon} = my $polygon = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape});
my $bb = $polygon->bounding_box;
my $size = $bb->size;
# calculate the scaling factor needed for constraining print bed area inside preview
# scaling_factor is expressed in pixel / mm
$self->{scaling_factor} = min($canvas_w / unscale($size->x), $canvas_h / unscale($size->y)); #)
# calculate the displacement needed to center bed
$self->{bed_origin} = [
$canvas_w/2 - (unscale($bb->x_max + $bb->x_min)/2 * $self->{scaling_factor}),
$canvas_h - ($canvas_h/2 - (unscale($bb->y_max + $bb->y_min)/2 * $self->{scaling_factor})),
];
# calculate print center
my $center = $bb->center;
$self->{print_center} = [ unscale($center->x), unscale($center->y) ]; #))
# cache bed contours and grid
{
my $step = scale 10; # 1cm grid
my @polylines = ();
for (my $x = $bb->x_min - ($bb->x_min % $step) + $step; $x < $bb->x_max; $x += $step) {
push @polylines, Slic3r::Polyline->new([$x, $bb->y_min], [$x, $bb->y_max]);
}
for (my $y = $bb->y_min - ($bb->y_min % $step) + $step; $y < $bb->y_max; $y += $step) {
push @polylines, Slic3r::Polyline->new([$bb->x_min, $y], [$bb->x_max, $y]);
}
@polylines = @{intersection_pl(\@polylines, [$polygon])};
$self->{grid} = [ map $self->scaled_points_to_pixel([ @$_[0,-1] ], 1), @polylines ];
}
}
sub clean_instance_thumbnails {
my ($self) = @_;
foreach my $object (@{ $self->{objects} }) {
@{ $object->instance_thumbnails } = ();
}
}
# convert a model coordinate into a pixel coordinate
sub unscaled_point_to_pixel {
my ($self, $point) = @_;
my $canvas_height = $self->GetSize->GetHeight;
my $zero = $self->{bed_origin};
return [
$point->[X] * $self->{scaling_factor} + $zero->[X],
$canvas_height - $point->[Y] * $self->{scaling_factor} + ($zero->[Y] - $canvas_height),
];
}
sub scaled_points_to_pixel {
my ($self, $points, $unscale) = @_;
my $result = [];
foreach my $point (@$points) {
$point = [ map unscale($_), @$point ] if $unscale;
push @$result, $self->unscaled_point_to_pixel($point);
}
return $result;
}
sub point_to_model_units {
my ($self, $point) = @_;
my $zero = $self->{bed_origin};
return Slic3r::Point->new(
scale ($point->[X] - $zero->[X]) / $self->{scaling_factor},
scale ($zero->[Y] - $point->[Y]) / $self->{scaling_factor},
);
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Plater/2DToolpaths.pm 0000664 0000000 0000000 00000061411 13274424355 0021147 0 ustar 00root root 0000000 0000000 # 2D preview of the tool paths of a single layer, using a thin line.
# OpenGL is used to render the paths.
# Vojtech also added a 2D simulation of under/over extrusion in a single layer.
package Slic3r::GUI::Plater::2DToolpaths;
use strict;
use warnings;
use utf8;
use Slic3r::Print::State ':steps';
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);
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(
$self, -1,
0, # default
0, # min
# we set max to a bogus non-zero value because the MSW implementation of wxSlider
# will skip drawing the slider if max <= min:
1, # max
wxDefaultPosition,
wxDefaultSize,
wxVERTICAL | wxSL_INVERSE,
);
my $z_label = $self->{z_label} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
$z_label->SetFont($Slic3r::GUI::small_font);
my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
$vsizer->Add($slider, 1, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
$vsizer->Add($z_label, 0, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($canvas, 1, wxALL | wxEXPAND, 0);
$sizer->Add($vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
EVT_SLIDER($self, $slider, sub {
$self->set_z($self->{layers_z}[$slider->GetValue])
if $self->enabled;
});
EVT_KEY_DOWN($canvas, sub {
my ($s, $event) = @_;
my $key = $event->GetKeyCode;
if ($key == 85 || $key == 315) {
$slider->SetValue($slider->GetValue + 1);
$self->set_z($self->{layers_z}[$slider->GetValue]);
} elsif ($key == 68 || $key == 317) {
$slider->SetValue($slider->GetValue - 1);
$self->set_z($self->{layers_z}[$slider->GetValue]);
} else {
$event->Skip;
}
});
$self->SetSizer($sizer);
$self->SetMinSize($self->GetSize);
$sizer->SetSizeHints($self);
# init print
$self->{print} = $print;
$self->reload_print;
return $self;
}
sub reload_print {
my ($self) = @_;
# we require that there's at least one object and the posSlice step
# is performed on all of them (this ensures that _shifted_copies was
# populated and we know the number of layers)
if (!$self->print->object_step_done(STEP_SLICE)) {
$self->enabled(0);
$self->{slider}->Hide;
$self->{canvas}->Refresh; # clears canvas
return;
}
$self->{canvas}->bb($self->print->total_bounding_box);
$self->{canvas}->_dirty(1);
my %z = (); # z => 1
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 ];
$self->{slider}->SetRange(0, scalar(@{$self->{layers_z}})-1);
if ((my $z_idx = $self->{slider}->GetValue) <= $#{$self->{layers_z}}) {
$self->set_z($self->{layers_z}[$z_idx]);
} else {
$self->{slider}->SetValue(0);
$self->set_z($self->{layers_z}[0]) if @{$self->{layers_z}};
}
$self->{slider}->Show;
$self->Layout;
}
sub set_z {
my ($self, $z) = @_;
return if !$self->enabled;
$self->{z_label}->SetLabel(sprintf '%.2f', $z);
$self->{canvas}->set_z($z);
}
package Slic3r::GUI::Plater::2DToolpaths::Canvas;
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
use base qw(Wx::GLCanvas Class::Accessor);
use Wx::GLCanvas qw(:all);
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
_camera_bb
_dirty
_zoom
_camera_target
_drag_start_xy
));
# make OpenGL::Array thread-safe
{
no warnings 'redefine';
*OpenGL::Array::CLONE_SKIP = sub { 1 };
}
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, "",
[WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, 0]) :
$class->SUPER::new($parent);
# Immediatelly force creation of the OpenGL context to consume the static variable s_wglContextAttribs.
$self->GetContext();
$self->print($print);
$self->_zoom(1);
# 2D point in model space
$self->_camera_target(Slic3r::Pointf->new(0,0));
EVT_PAINT($self, sub {
my $dc = Wx::PaintDC->new($self);
$self->Render($dc);
});
EVT_SIZE($self, sub { $self->_dirty(1) });
EVT_IDLE($self, sub {
return unless $self->_dirty;
return if !$self->IsShownOnScreen;
$self->Resize;
$self->Refresh;
});
EVT_MOUSEWHEEL($self, sub {
my ($self, $e) = @_;
return if !$self->GetParent->enabled;
my $old_zoom = $self->_zoom;
# Calculate the zoom delta and apply it to the current zoom factor
my $zoom = -$e->GetWheelRotation() / $e->GetWheelDelta();
if ($Slic3r::GUI::Settings->{_}{invert_zoom}) {
$zoom *= -1;
}
$zoom = max(min($zoom, 4), -4);
$zoom /= 10;
$self->_zoom($self->_zoom / (1-$zoom));
$self->_zoom(1) if $self->_zoom > 1; #Â prevent from zooming out too much
{
# In order to zoom around the mouse point we need to translate
#Â the camera target. This math is almost there but not perfect yet...
my $camera_bb_size = $self->_camera_bb->size;
my $size = Slic3r::Pointf->new($self->GetSizeWH);
my $pos = Slic3r::Pointf->new($e->GetPositionXY);
# calculate the zooming center in pixel coordinates relative to the viewport center
my $vec = Slic3r::Pointf->new($pos->x - $size->x/2, $pos->y - $size->y/2); #-
# calculate where this point will end up after applying the new zoom
my $vec2 = $vec->clone;
$vec2->scale($old_zoom / $self->_zoom);
# move the camera target by the difference of the two positions
$self->_camera_target->translate(
-($vec->x - $vec2->x) * $camera_bb_size->x / $size->x,
($vec->y - $vec2->y) * $camera_bb_size->y / $size->y, #//
);
}
$self->_dirty(1);
$self->Refresh;
});
EVT_MOUSE_EVENTS($self, \&mouse_event);
return $self;
}
sub zoom{
my($self, $direction) = @_;
if( $direction eq 'in'){
$self->_zoom($self->_zoom / (1+0.3));
}
elsif($direction eq 'out'){
$self->_zoom($self->_zoom / (1-0.3));
$self->_zoom(1) if $self->_zoom > 1; #Â prevent from zooming out too much
}
#apply changes
$self->_dirty(1);
$self->Refresh;
}
sub mouse_event {
my ($self, $e) = @_;
return if !$self->GetParent->enabled;
my $pos = Slic3r::Pointf->new($e->GetPositionXY);
if ($e->Entering && &Wx::wxMSW) {
# wxMSW needs focus in order to catch mouse wheel events
$self->SetFocus;
} elsif ($e->Dragging) {
if ($e->LeftIsDown || $e->MiddleIsDown || $e->RightIsDown) {
# if dragging, translate view
if (defined $self->_drag_start_xy) {
my $move = $self->_drag_start_xy->vector_to($pos); #Â in pixels
# get viewport and camera size in order to convert pixel to model units
my ($x, $y) = $self->GetSizeWH;
my $camera_bb_size = $self->_camera_bb->size;
# compute translation in model units
$self->_camera_target->translate(
-$move->x * $camera_bb_size->x / $x,
$move->y * $camera_bb_size->y / $y, # /**
);
$self->_dirty(1);
$self->Refresh;
}
$self->_drag_start_xy($pos);
}
} elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
$self->_drag_start_xy(undef);
} else {
$e->Skip();
}
}
sub set_z {
my ($self, $z) = @_;
my $print = $self->print;
# can we have interlaced layers?
my $interlaced = (defined first { $_->config->support_material } @{$print->objects})
|| (defined first { $_->config->infill_every_layers > 1 } @{$print->regions});
my $max_layer_height = $print->max_allowed_layer_height;
my @layers = ();
foreach my $object (@{$print->objects}) {
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
if ($interlaced) {
push @layers, $layer
if $z > ($layer->print_z - $max_layer_height - epsilon)
&& $z <= $layer->print_z + epsilon;
} else {
push @layers, $layer if abs($layer->print_z - $z) < epsilon;
}
}
}
# reverse layers so that we draw the lowermost (i.e. current) on top
$self->z($z);
$self->layers([ reverse @layers ]);
$self->Refresh;
}
sub Render {
my ($self, $dc) = @_;
# prevent calling SetCurrent() when window is not shown yet
return unless $self->IsShownOnScreen;
return unless my $context = $self->GetContext;
$self->SetCurrent($context);
$self->InitGL;
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) {
glFlush();
$self->SwapBuffers;
return;
}
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
# anti-alias
if (0) {
glEnable(GL_LINE_SMOOTH);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
glHint(GL_POLYGON_SMOOTH_HINT, GL_DONT_CARE);
}
my $tess;
if (!(&Wx::wxMSW && $OpenGL::VERSION < 0.6704)) {
# We can't use the GLU tesselator on MSW with older OpenGL versions
# because of an upstream bug:
# http://sourceforge.net/p/pogl/bugs/16/
$tess = gluNewTess();
gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_END, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_VERTEX, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT');
gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT');
}
foreach my $layer (@{$self->layers}) {
my $object = $layer->object;
# only draw the slice for the current layer
next unless abs($layer->print_z - $self->z) < epsilon;
# draw slice contour
glLineWidth(1);
foreach my $copy (@{ $object->_shifted_copies }) {
glPushMatrix();
glTranslatef(@$copy, 0);
foreach my $slice (@{$layer->slices}) {
glColor3f(@TOOL_SHADE); # Inside part shade
if ($tess) {
gluTessBeginPolygon($tess);
foreach my $polygon (@$slice) {
gluTessBeginContour($tess);
gluTessVertex_p($tess, @$_, 0) for @$polygon;
gluTessEndContour($tess);
}
gluTessEndPolygon($tess);
}
glColor3f(@TOOL_COLOR); # Perimeter
foreach my $polygon (@$slice) {
foreach my $line (@{$polygon->lines}) {
glBegin(GL_LINES);
glVertex2f(@{$line->a});
glVertex2f(@{$line->b});
glEnd();
}
}
}
glPopMatrix();
}
}
my $skirt_drawn = 0;
my $brim_drawn = 0;
foreach my $layer (@{$self->layers}) {
my $object = $layer->object;
my $print_z = $layer->print_z;
# draw brim
if ($self->print->step_done(STEP_BRIM) && $layer->id == 0 && !$brim_drawn) {
$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(@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(@TOOL_STEPPERIM);
$self->_draw($object, $print_z, $_) for map @$_, @{$layerm->perimeters};
}
if ($object->step_done(STEP_INFILL)) {
$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(@TOOL_SUPPORT);
$self->_draw($object, $print_z, $_) for @{$layer->support_fills};
$self->_draw($object, $print_z, $_) for @{$layer->support_interface_fills};
}
}
}
gluDeleteTess($tess) if $tess;
glFlush();
$self->SwapBuffers;
}
sub _draw {
my ($self, $object, $print_z, $path) = @_;
my @paths = $path->isa('Slic3r::ExtrusionLoop')
? @$path
: ($path);
$self->_draw_path($object, $print_z, $_) for @paths;
}
sub _draw_path {
my ($self, $object, $print_z, $path) = @_;
return if $print_z - $path->height > $self->z - epsilon;
if (abs($print_z - $self->z) < epsilon) {
glColor3f($self->color->[0], $self->color->[1], $self->color->[2]);
} else {
glColor3f(0.8, 0.8, 0.8);
}
glLineWidth(1);
if (defined $object) {
foreach my $copy (@{ $object->_shifted_copies }) {
glPushMatrix();
glTranslatef(@$copy, 0);
foreach my $line (@{$path->polyline->lines}) {
glBegin(GL_LINES);
glVertex2f(@{$line->a});
glVertex2f(@{$line->b});
glEnd();
}
glPopMatrix();
}
} else {
foreach my $line (@{$path->polyline->lines}) {
glBegin(GL_LINES);
glVertex2f(@{$line->a});
glVertex2f(@{$line->b});
glEnd();
}
}
}
sub InitGL {
my $self = shift;
return if $self->init;
return unless $self->GetContext;
$self->init(1);
}
sub GetContext {
my ($self) = @_;
if (Wx::wxVERSION >= 2.009) {
return $self->{context} ||= Wx::GLContext->new($self);
} else {
return $self->SUPER::GetContext;
}
}
sub SetCurrent {
my ($self, $context) = @_;
if (Wx::wxVERSION >= 2.009) {
return $self->SUPER::SetCurrent($context);
} else {
return $self->SUPER::SetCurrent;
}
}
sub Resize {
my ($self) = @_;
return unless $self->GetContext;
return unless $self->bb;
$self->_dirty(0);
$self->SetCurrent($self->GetContext);
my ($x, $y) = $self->GetSizeWH;
glViewport(0, 0, $x, $y);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
my $bb = $self->bb->clone;
# center bounding box around origin before scaling it
my $bb_center = $bb->center;
$bb->translate(@{$bb_center->negative});
# scale bounding box according to zoom factor
$bb->scale($self->_zoom);
# reposition bounding box around original center
$bb->translate(@{$bb_center});
# translate camera
$bb->translate(@{$self->_camera_target});
#Â keep camera_bb within total bb
#Â (i.e. prevent user from panning outside the bounding box)
{
my @translate = (0,0);
if ($bb->x_min < $self->bb->x_min) {
$translate[X] += $self->bb->x_min - $bb->x_min;
}
if ($bb->y_min < $self->bb->y_min) {
$translate[Y] += $self->bb->y_min - $bb->y_min;
}
if ($bb->x_max > $self->bb->x_max) {
$translate[X] -= $bb->x_max - $self->bb->x_max;
}
if ($bb->y_max > $self->bb->y_max) {
$translate[Y] -= $bb->y_max - $self->bb->y_max;
}
$self->_camera_target->translate(@translate);
$bb->translate(@translate);
}
# save camera
$self->_camera_bb($bb);
my ($x1, $y1, $x2, $y2) = ($bb->x_min, $bb->y_min, $bb->x_max, $bb->y_max);
if (($x2 - $x1)/($y2 - $y1) > $x/$y) {
# adjust Y
my $new_y = $y * ($x2 - $x1) / $x;
$y1 = ($y2 + $y1)/2 - $new_y/2;
$y2 = $y1 + $new_y;
} else {
my $new_x = $x * ($y2 - $y1) / $y;
$x1 = ($x2 + $x1)/2 - $new_x/2;
$x2 = $x1 + $new_x;
}
glOrtho($x1, $x2, $y1, $y2, 0, 1);
glMatrixMode(GL_MODELVIEW);
}
sub line {
my (
$x1, $y1, $x2, $y2, # coordinates of the line
$w, # width/thickness of the line in pixel
$Cr, $Cg, $Cb, # RGB color components
$Br, $Bg, $Bb, # color of background when alphablend=false
# Br=alpha of color when alphablend=true
$alphablend, # use alpha blend or not
) = @_;
my $t;
my $R;
my $f = $w - int($w);
my $A;
if ($alphablend) {
$A = $Br;
} else {
$A = 1;
}
# determine parameters t,R
if ($w >= 0 && $w < 1) {
$t = 0.05; $R = 0.48 + 0.32 * $f;
if (!$alphablend) {
$Cr += 0.88 * (1-$f);
$Cg += 0.88 * (1-$f);
$Cb += 0.88 * (1-$f);
$Cr = 1.0 if ($Cr > 1.0);
$Cg = 1.0 if ($Cg > 1.0);
$Cb = 1.0 if ($Cb > 1.0);
} else {
$A *= $f;
}
} elsif ($w >= 1.0 && $w < 2.0) {
$t = 0.05 + $f*0.33; $R = 0.768 + 0.312*$f;
} elsif ($w >= 2.0 && $w < 3.0) {
$t = 0.38 + $f*0.58; $R = 1.08;
} elsif ($w >= 3.0 && $w < 4.0) {
$t = 0.96 + $f*0.48; $R = 1.08;
} elsif ($w >= 4.0 && $w < 5.0) {
$t= 1.44 + $f*0.46; $R = 1.08;
} elsif ($w >= 5.0 && $w < 6.0) {
$t= 1.9 + $f*0.6; $R = 1.08;
} elsif ($w >= 6.0) {
my $ff = $w - 6.0;
$t = 2.5 + $ff*0.50; $R = 1.08;
}
#printf( "w=%f, f=%f, C=%.4f\n", $w, $f, $C);
# determine angle of the line to horizontal
my $tx = 0; my $ty = 0; # core thinkness of a line
my $Rx = 0; my $Ry = 0; # fading edge of a line
my $cx = 0; my $cy = 0; # cap of a line
my $ALW = 0.01;
my $dx = $x2 - $x1;
my $dy = $y2 - $y1;
if (abs($dx) < $ALW) {
# vertical
$tx = $t; $ty = 0;
$Rx = $R; $Ry = 0;
if ($w > 0.0 && $w < 1.0) {
$tx *= 8;
} elsif ($w == 1.0) {
$tx *= 10;
}
} elsif (abs($dy) < $ALW) {
#horizontal
$tx = 0; $ty = $t;
$Rx = 0; $Ry = $R;
if ($w > 0.0 && $w < 1.0) {
$ty *= 8;
} elsif ($w == 1.0) {
$ty *= 10;
}
} else {
if ($w < 3) { # approximate to make things even faster
my $m = $dy/$dx;
# and calculate tx,ty,Rx,Ry
if ($m > -0.4142 && $m <= 0.4142) {
# -22.5 < $angle <= 22.5, approximate to 0 (degree)
$tx = $t * 0.1; $ty = $t;
$Rx = $R * 0.6; $Ry = $R;
} elsif ($m > 0.4142 && $m <= 2.4142) {
# 22.5 < $angle <= 67.5, approximate to 45 (degree)
$tx = $t * -0.7071; $ty = $t * 0.7071;
$Rx = $R * -0.7071; $Ry = $R * 0.7071;
} elsif ($m > 2.4142 || $m <= -2.4142) {
# 67.5 < $angle <= 112.5, approximate to 90 (degree)
$tx = $t; $ty = $t*0.1;
$Rx = $R; $Ry = $R*0.6;
} elsif ($m > -2.4142 && $m < -0.4142) {
# 112.5 < angle < 157.5, approximate to 135 (degree)
$tx = $t * 0.7071; $ty = $t * 0.7071;
$Rx = $R * 0.7071; $Ry = $R * 0.7071;
} else {
# error in determining angle
printf("error in determining angle: m=%.4f\n", $m);
}
} else { # calculate to exact
$dx= $y1 - $y2;
$dy= $x2 - $x1;
my $L = sqrt($dx*$dx + $dy*$dy);
$dx /= $L;
$dy /= $L;
$cx = -0.6*$dy; $cy=0.6*$dx;
$tx = $t*$dx; $ty = $t*$dy;
$Rx = $R*$dx; $Ry = $R*$dy;
}
}
# draw the line by triangle strip
glBegin(GL_TRIANGLE_STRIP);
if (!$alphablend) {
glColor3f($Br, $Bg, $Bb);
} else {
glColor4f($Cr, $Cg, $Cb, 0);
}
glVertex2f($x1 - $tx - $Rx, $y1 - $ty - $Ry); # fading edge
glVertex2f($x2 - $tx - $Rx, $y2 - $ty - $Ry);
if (!$alphablend) {
glColor3f($Cr, $Cg, $Cb);
} else {
glColor4f($Cr, $Cg, $Cb, $A);
}
glVertex2f($x1 - $tx, $y1 - $ty); # core
glVertex2f($x2 - $tx, $y2 - $ty);
glVertex2f($x1 + $tx, $y1 + $ty);
glVertex2f($x2 + $tx, $y2 + $ty);
if ((abs($dx) < $ALW || abs($dy) < $ALW) && $w <= 1.0) {
# printf("skipped one fading edge\n");
} else {
if (!$alphablend) {
glColor3f($Br, $Bg, $Bb);
} else {
glColor4f($Cr, $Cg, $Cb, 0);
}
glVertex2f($x1 + $tx+ $Rx, $y1 + $ty + $Ry); # fading edge
glVertex2f($x2 + $tx+ $Rx, $y2 + $ty + $Ry);
}
glEnd();
# cap
if ($w < 3) {
# do not draw cap
} else {
# draw cap
glBegin(GL_TRIANGLE_STRIP);
if (!$alphablend) {
glColor3f($Br, $Bg, $Bb);
} else {
glColor4f($Cr, $Cg, $Cb, 0);
}
glVertex2f($x1 - $Rx + $cx, $y1 - $Ry + $cy);
glVertex2f($x1 + $Rx + $cx, $y1 + $Ry + $cy);
glColor3f($Cr, $Cg, $Cb);
glVertex2f($x1 - $tx - $Rx, $y1 - $ty - $Ry);
glVertex2f($x1 + $tx + $Rx, $y1 + $ty + $Ry);
glEnd();
glBegin(GL_TRIANGLE_STRIP);
if (!$alphablend) {
glColor3f($Br, $Bg, $Bb);
} else {
glColor4f($Cr, $Cg, $Cb, 0);
}
glVertex2f($x2 - $Rx - $cx, $y2 - $Ry - $cy);
glVertex2f($x2 + $Rx - $cx, $y2 + $Ry - $cy);
glColor3f($Cr, $Cg, $Cb);
glVertex2f($x2 - $tx - $Rx, $y2 - $ty - $Ry);
glVertex2f($x2 + $tx + $Rx, $y2 + $ty + $Ry);
glEnd();
}
}
package Slic3r::GUI::Plater::2DToolpaths::Dialog;
use Wx qw(:dialog :id :misc :sizer);
use Wx::Event qw(EVT_CLOSE);
use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent, $print) = @_;
my $self = $class->SUPER::new($parent, -1, "Toolpaths", wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add(Slic3r::GUI::Plater::2DToolpaths->new($self, $print), 1, wxEXPAND, 0);
$self->SetSizer($sizer);
$self->SetMinSize($self->GetSize);
# needed to actually free memory
EVT_CLOSE($self, sub {
$self->EndModal(wxID_OK);
$self->Destroy;
});
return $self;
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Plater/3D.pm 0000664 0000000 0000000 00000005346 13274424355 0017257 0 ustar 00root root 0000000 0000000 package Slic3r::GUI::Plater::3D;
use strict;
use warnings;
use utf8;
use List::Util qw();
use Slic3r::Geometry qw();
use Slic3r::Geometry::Clipper qw();
use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
use Wx::Event qw();
use base qw(Slic3r::GUI::3DScene Class::Accessor);
sub new {
my $class = shift;
my ($parent, $objects, $model, $config) = @_;
my $self = $class->SUPER::new($parent);
$self->enable_picking(1);
$self->enable_moving(1);
$self->select_by('object');
$self->drag_by('instance');
$self->{objects} = $objects;
$self->{model} = $model;
$self->{config} = $config;
$self->{on_select_object} = sub {};
$self->{on_instances_moved} = sub {};
$self->on_select(sub {
my ($volume_idx) = @_;
my $obj_idx = undef;
if ($volume_idx != -1) {
$obj_idx = $self->object_idx($volume_idx);
}
$self->{on_select_object}->($obj_idx)
if $self->{on_select_object};
});
$self->on_move(sub {
my @volume_idxs = @_;
my %done = (); #Â prevent moving instances twice
foreach my $volume_idx (@volume_idxs) {
my $volume = $self->volumes->[$volume_idx];
my $obj_idx = $self->object_idx($volume_idx);
my $instance_idx = $self->instance_idx($volume_idx);
next if $done{"${obj_idx}_${instance_idx}"};
$done{"${obj_idx}_${instance_idx}"} = 1;
my $model_object = $self->{model}->get_object($obj_idx);
$model_object
->instances->[$instance_idx]
->offset
->translate($volume->origin->x, $volume->origin->y); #))
$model_object->invalidate_bounding_box;
}
$self->{on_instances_moved}->()
if $self->{on_instances_moved};
});
return $self;
}
sub set_on_select_object {
my ($self, $cb) = @_;
$self->{on_select_object} = $cb;
}
sub set_on_double_click {
my ($self, $cb) = @_;
$self->on_double_click($cb);
}
sub set_on_right_click {
my ($self, $cb) = @_;
$self->on_right_click($cb);
}
sub set_on_instances_moved {
my ($self, $cb) = @_;
$self->{on_instances_moved} = $cb;
}
sub update {
my ($self) = @_;
$self->reset_objects;
$self->update_bed_size;
foreach my $obj_idx (0..$#{$self->{model}->objects}) {
my @volume_idxs = $self->load_object($self->{model}, $obj_idx);
if ($self->{objects}[$obj_idx]->selected) {
$self->select_volume($_) for @volume_idxs;
}
}
}
sub update_bed_size {
my ($self) = @_;
$self->set_bed_shape($self->{config}->bed_shape);
}
1; Slic3r-1.3.0/lib/Slic3r/GUI/Plater/3DPreview.pm 0000664 0000000 0000000 00000013007 13274424355 0020612 0 ustar 00root root 0000000 0000000 package Slic3r::GUI::Plater::3DPreview;
use strict;
use warnings;
use utf8;
use Slic3r::Print::State ':steps';
use Wx qw(:misc :sizer :slider :statictext);
use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN);
use base qw(Wx::Panel Class::Accessor);
__PACKAGE__->mk_accessors(qw(print enabled _loaded canvas slider));
sub new {
my $class = shift;
my ($parent, $print) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition);
#Â init GUI elements
my $canvas = Slic3r::GUI::3DScene->new($self);
$self->canvas($canvas);
my $slider = Wx::Slider->new(
$self, -1,
0, # default
0, # min
# we set max to a bogus non-zero value because the MSW implementation of wxSlider
# will skip drawing the slider if max <= min:
1, # max
wxDefaultPosition,
wxDefaultSize,
wxVERTICAL | wxSL_INVERSE,
);
$self->slider($slider);
my $z_label = $self->{z_label} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
$z_label->SetFont($Slic3r::GUI::small_font);
my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
$vsizer->Add($slider, 1, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
$vsizer->Add($z_label, 0, wxALL | wxEXPAND | wxALIGN_CENTER, 3);
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($canvas, 1, wxALL | wxEXPAND, 0);
$sizer->Add($vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
EVT_SLIDER($self, $slider, sub {
$self->set_z($self->{layers_z}[$slider->GetValue])
if $self->enabled;
});
EVT_KEY_DOWN($canvas, sub {
my ($s, $event) = @_;
my $key = $event->GetKeyCode;
if ($key == 85 || $key == 315) {
$slider->SetValue($slider->GetValue + 1);
$self->set_z($self->{layers_z}[$slider->GetValue]);
} elsif ($key == 68 || $key == 317) {
$slider->SetValue($slider->GetValue - 1);
$self->set_z($self->{layers_z}[$slider->GetValue]);
} else {
$event->Skip;
}
});
$self->SetSizer($sizer);
$self->SetMinSize($self->GetSize);
$sizer->SetSizeHints($self);
# init canvas
$self->print($print);
$self->reload_print;
return $self;
}
sub reload_print {
my ($self, $obj_idx) = @_;
$self->canvas->reset_objects;
$self->_loaded(0);
$self->load_print($obj_idx);
}
sub load_print {
my ($self, $obj_idx) = @_;
return if $self->_loaded;
# we require that there's at least one object and the posSlice step
# is performed on all of them (this ensures that _shifted_copies was
# populated and we know the number of layers)
if (!$self->print->object_step_done(STEP_SLICE)) {
$self->enabled(0);
$self->slider->Hide;
$self->canvas->Refresh; # clears canvas
return;
}
my $z_idx;
{
my %z = (); # z => 1
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 ];
$self->slider->SetRange(0, scalar(@{$self->{layers_z}})-1);
if (($z_idx = $self->slider->GetValue) <= $#{$self->{layers_z}} && $self->slider->GetValue != 0) {
# use $z_idx
} else {
$self->slider->SetValue(scalar(@{$self->{layers_z}})-1);
$z_idx = @{$self->{layers_z}} ? -1 : undef;
}
$self->slider->Show;
$self->Layout;
}
if ($self->IsShown) {
# set colors
$self->canvas->color_toolpaths_by($Slic3r::GUI::Settings->{_}{color_toolpaths_by});
if ($self->canvas->color_toolpaths_by eq 'extruder') {
my @filament_colors = map { s/^#//; [ map $_/255, (unpack 'C*', pack 'H*', $_), 255 ] }
@{$self->print->config->filament_colour};
$self->canvas->colors->[$_] = $filament_colors[$_] for 0..$#filament_colors;
} else {
$self->canvas->colors([ $self->canvas->default_colors ]);
}
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);
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);
}
$self->set_z($self->{layers_z}[$z_idx]);
}
sub set_z {
my ($self, $z) = @_;
return if !$self->enabled;
$self->{z_label}->SetLabel(sprintf '%.2f', $z);
$self->canvas->set_toolpaths_range(0, $z);
$self->canvas->Refresh if $self->IsShown;
}
sub set_bed_shape {
my ($self, $bed_shape) = @_;
$self->canvas->set_bed_shape($bed_shape);
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Plater/LambdaObjectDialog.pm 0000664 0000000 0000000 00000016421 13274424355 0022434 0 ustar 00root root 0000000 0000000 # Generate an anonymous or "lambda" 3D object. This gets used with the Create Modifier option in Settings.
#
package Slic3r::GUI::Plater::LambdaObjectDialog;
use strict;
use warnings;
use utf8;
use Slic3r::Geometry qw(PI X);
use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL wxCB_READONLY wxTE_PROCESS_TAB);
use Wx::Event qw(EVT_CLOSE EVT_BUTTON EVT_COMBOBOX EVT_TEXT);
use Scalar::Util qw(looks_like_number);
use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, "Create Modifier", wxDefaultPosition, [500,500],
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
# Note whether the window was already closed, so a pending update is not executed.
$self->{already_closed} = 0;
$self->{object_parameters} = {
type => 'slab',
dim => [1, 1, 1],
cyl_r => 1,
cyl_h => 1,
sph_rho => 1.0,
slab_h => 1.0,
};
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
EVT_BUTTON($self, wxID_OK, sub {
$self->EndModal(wxID_OK);
$self->Destroy;
});
EVT_BUTTON($self, wxID_CANCEL, sub {
$self->EndModal(wxID_CANCEL);
$self->Destroy;
});
$self->{type} = Wx::ComboBox->new($self, 1, $self->{object_parameters}{type},
wxDefaultPosition, wxDefaultSize,
[qw(slab box cylinder sphere)], wxCB_READONLY);
my $optgroup_box;
$optgroup_box = $self->{optgroup_box} = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Add Cube...',
on_change => sub {
# Do validation
my ($opt_id) = @_;
if ($opt_id == 0 || $opt_id == 1 || $opt_id == 2) {
if (!looks_like_number($optgroup_box->get_value($opt_id))) {
return 0;
}
}
$self->{object_parameters}->{dim}[$opt_id] = $optgroup_box->get_value($opt_id);
},
label_width => 100,
);
$optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 0,
label => 'L (x)',
type => 'f',
default => $self->{object_parameters}{dim}[0],
sidetext => 'mm',
));
$optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 1,
label => 'W (y)',
type => 'f',
default => $self->{object_parameters}{dim}[1],
sidetext => 'mm',
));
$optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 2,
label => 'H (z)',
type => 'f',
default => $self->{object_parameters}{dim}[2],
sidetext => 'mm',
));
my $optgroup_cylinder;
$optgroup_cylinder = $self->{optgroup_cylinder} = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Add Cylinder...',
on_change => sub {
# Do validation
my ($opt_id) = @_;
if ($opt_id eq 'cyl_r' || $opt_id eq 'cyl_h') {
if (!looks_like_number($optgroup_cylinder->get_value($opt_id))) {
return 0;
}
}
$self->{object_parameters}->{$opt_id} = $optgroup_cylinder->get_value($opt_id);
},
label_width => 100,
);
$optgroup_cylinder->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => "cyl_r",
label => 'Radius',
type => 'f',
default => $self->{object_parameters}{cyl_r},
sidetext => 'mm',
));
$optgroup_cylinder->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => "cyl_h",
label => 'Height',
type => 'f',
default => $self->{object_parameters}{cyl_h},
sidetext => 'mm',
));
my $optgroup_sphere;
$optgroup_sphere = $self->{optgroup_sphere} = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Add Sphere...',
on_change => sub {
# Do validation
my ($opt_id) = @_;
if ($opt_id eq 'sph_rho') {
if (!looks_like_number($optgroup_sphere->get_value($opt_id))) {
return 0;
}
}
$self->{object_parameters}->{$opt_id} = $optgroup_sphere->get_value($opt_id);
},
label_width => 100,
);
$optgroup_sphere->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => "sph_rho",
label => 'Radius',
type => 'f',
default => $self->{object_parameters}{sph_rho},
sidetext => 'mm',
));
my $optgroup_slab;
$optgroup_slab = $self->{optgroup_slab} = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Add Slab...',
on_change => sub {
# Do validation
my ($opt_id) = @_;
if ($opt_id eq 'slab_h') {
if (!looks_like_number($optgroup_slab->get_value($opt_id))) {
return 0;
}
}
$self->{object_parameters}->{$opt_id} = $optgroup_slab->get_value($opt_id);
},
label_width => 100,
);
$optgroup_slab->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => "slab_h",
label => 'Thickness',
type => 'f',
default => $self->{object_parameters}{slab_h},
sidetext => 'mm',
));
EVT_COMBOBOX($self, 1, sub{
$self->{object_parameters}->{type} = $self->{type}->GetValue();
$self->_update_ui;
});
$self->{sizer}->Add($self->{type}, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
$self->{sizer}->Add($optgroup_box->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
$self->{sizer}->Add($optgroup_cylinder->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
$self->{sizer}->Add($optgroup_sphere->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
$self->{sizer}->Add($optgroup_slab->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
$self->{sizer}->Add($buttons,0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
$self->_update_ui;
$self->SetSizer($self->{sizer});
$self->{sizer}->Fit($self);
$self->{sizer}->SetSizeHints($self);
return $self;
}
sub ObjectParameter {
my ($self) = @_;
return $self->{object_parameters};
}
sub _update_ui {
my ($self) = @_;
$self->{sizer}->Hide($self->{optgroup_cylinder}->sizer);
$self->{sizer}->Hide($self->{optgroup_slab}->sizer);
$self->{sizer}->Hide($self->{optgroup_box}->sizer);
$self->{sizer}->Hide($self->{optgroup_sphere}->sizer);
if ($self->{type}->GetValue eq "box") {
$self->{sizer}->Show($self->{optgroup_box}->sizer);
} elsif ($self->{type}->GetValue eq "cylinder") {
$self->{sizer}->Show($self->{optgroup_cylinder}->sizer);
} elsif ($self->{type}->GetValue eq "slab") {
$self->{sizer}->Show($self->{optgroup_slab}->sizer);
} elsif ($self->{type}->GetValue eq "sphere") {
$self->{sizer}->Show($self->{optgroup_sphere}->sizer);
}
$self->{sizer}->Fit($self);
$self->{sizer}->SetSizeHints($self);
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm 0000664 0000000 0000000 00000034110 13274424355 0022002 0 ustar 00root root 0000000 0000000 # Cut an object at a Z position, keep either the top or the bottom of the object.
# This dialog gets opened with the "Cut..." button above the platter.
package Slic3r::GUI::Plater::ObjectCutDialog;
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->{new_model_objects} = [];
# Mark whether the mesh cut is valid.
# If not, it needs to be recalculated by _update() on wxTheApp->CallAfter() or on exit of the dialog.
$self->{mesh_cut_valid} = 0;
# 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);
# cut options
my $size_z = $self->{model_object}->instance_bounding_box(0)->size->z;
$self->{cut_options} = {
axis => Z,
z => $size_z/2,
keep_upper => 0,
keep_lower => 1,
rotate_lower => 0,
preview => 1,
};
my $optgroup;
$optgroup = $self->{optgroup} = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Cut',
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->{cut_options}{$opt_id} != $optgroup->get_value($opt_id) ||
! $self->{mesh_cut_valid} && $self->_life_preview_active()) {
$self->{cut_options}{$opt_id} = $optgroup->get_value($opt_id);
$self->{mesh_cut_valid} = 0;
wxTheApp->CallAfter(sub {
$self->_update;
});
}
},
label_width => 120,
);
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'axis',
type => 'select',
label => 'Axis',
labels => ['X','Y','Z'],
values => [X,Y,Z],
default => $self->{cut_options}{axis},
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'z',
type => 'slider',
label => 'Z',
default => $self->{cut_options}{z},
min => 0,
max => $size_z,
full_width => 1,
));
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Keep',
);
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'keep_upper',
type => 'bool',
label => 'Upper part',
default => $self->{cut_options}{keep_upper},
));
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'keep_lower',
type => 'bool',
label => 'Lower part',
default => $self->{cut_options}{keep_lower},
));
$optgroup->append_line($line);
}
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'rotate_lower',
label => 'Rotate lower part upwards',
type => 'bool',
tooltip => 'If enabled, the lower part will be rotated by 180° so that the flat cut surface lies on the print bed.',
default => $self->{cut_options}{rotate_lower},
));
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'preview',
label => 'Show preview',
type => 'bool',
tooltip => 'If enabled, object will be cut in real time.',
default => $self->{cut_options}{preview},
));
{
my $cut_button_sizer = Wx::BoxSizer->new(wxVERTICAL);
$self->{btn_cut} = Wx::Button->new($self, -1, "Perform cut", wxDefaultPosition, wxDefaultSize);
$self->{btn_cut}->SetDefault;
$cut_button_sizer->Add($self->{btn_cut}, 0, wxALIGN_RIGHT | wxALL, 10);
$self->{btn_cut_grid} = Wx::Button->new($self, -1, "Cut by grid…", wxDefaultPosition, wxDefaultSize);
$cut_button_sizer->Add($self->{btn_cut_grid}, 0, wxALIGN_RIGHT | wxALL, 10);
$optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new(
sizer => $cut_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->SetMinSize($canvas->GetSize);
$canvas->zoom_to_volumes;
}
$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_cut}, sub {
# Recalculate the cut if the preview was not active.
$self->_perform_cut() unless $self->{mesh_cut_valid};
# Adjust position / orientation of the split object halves.
if (my $lower = $self->{new_model_objects}[0]) {
if ($self->{cut_options}{rotate_lower} && $self->{cut_options}{axis} == Z) {
$lower->rotate(PI, X);
}
$lower->center_around_origin; #Â align to Z = 0
}
if (my $upper = $self->{new_model_objects}[1]) {
$upper->center_around_origin; #Â align to Z = 0
}
# Note that the window was already closed, so a pending update will not be executed.
$self->{already_closed} = 1;
$self->EndModal(wxID_OK);
$self->Destroy();
});
EVT_BUTTON($self, $self->{btn_cut_grid}, sub {
my $grid_x = Wx::GetTextFromUser("Enter the width of the desired tiles along the X axis:",
"Cut by Grid", 100, $self);
return if !looks_like_number($grid_x) || $grid_x <= 0;
my $grid_y = Wx::GetTextFromUser("Enter the width of the desired tiles along the Y axis:",
"Cut by Grid", 100, $self);
return if !looks_like_number($grid_y) || $grid_y <= 0;
my $process_dialog = Wx::ProgressDialog->new('Cutting…', "Cutting model by grid…", 100, $self, 0);
$process_dialog->Pulse;
my $meshes = $self->{model_object}->mesh->cut_by_grid(Slic3r::Pointf->new($grid_x, $grid_y));
$self->{new_model_objects} = [];
my $bb = $self->{model_object}->bounding_box;
$self->{new_model} = my $model = Slic3r::Model->new;
for my $i (0..$#$meshes) {
push @{$self->{new_model_objects}}, my $o = $model->add_object(
name => sprintf('%s (%d)', $self->{model_object}->name, $i+1),
);
my $v = $o->add_volume(
mesh => $meshes->[$i],
name => $o->name,
);
$o->center_around_origin;
my $i = $o->add_instance(
offset => Slic3r::Pointf->new(@{$o->origin_translation->negative}[X,Y]),
);
$i->offset->translate(
5 * ceil(($i->offset->x - $bb->center->x) / $grid_x),
5 * ceil(($i->offset->y - $bb->center->y) / $grid_y),
);
}
$process_dialog->Destroy;
# Note that the window was already closed, so a pending update will not be executed.
$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();
});
$self->_update;
return $self;
}
# scale Z down to original size since we're using the transformed mesh for 3D preview
# and cut dialog but ModelObject::cut() needs Z without any instance transformation
sub _mesh_slice_z_pos
{
my ($self) = @_;
my $bb = $self->{model_object}->instance_bounding_box(0);
my $z = $self->{cut_options}{axis} == X ? $bb->x_min
: $self->{cut_options}{axis} == Y ? $bb->y_min
: $bb->z_min;
$z += $self->{cut_options}{z} / $self->{model_object}->instances->[0]->scaling_factor;
return $z;
}
# Only perform live preview if just a single part of the object shall survive.
sub _life_preview_active
{
my ($self) = @_;
return $self->{cut_options}{preview} && ($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower});
}
# Slice the mesh, keep the top / bottom part.
sub _perform_cut
{
my ($self) = @_;
# Early exit. If the cut is valid, don't recalculate it.
return if $self->{mesh_cut_valid};
my $z = $self->_mesh_slice_z_pos();
my ($new_model) = $self->{model_object}->cut($self->{cut_options}{axis}, $z);
my ($upper_object, $lower_object) = @{$new_model->objects};
$self->{new_model} = $new_model;
$self->{new_model_objects} = [];
if ($self->{cut_options}{keep_upper} && $upper_object->volumes_count > 0) {
$self->{new_model_objects}[1] = $upper_object;
}
if ($self->{cut_options}{keep_lower} && $lower_object->volumes_count > 0) {
$self->{new_model_objects}[0] = $lower_object;
}
$self->{mesh_cut_valid} = 1;
}
sub _update {
my ($self) = @_;
# Don't update if the window was already closed.
# We are not sure whether the action planned by wxTheApp->CallAfter() may be triggered after the window is closed.
# Probably not, but better be safe than sorry, which is espetially true on multiple platforms.
return if $self->{already_closed};
# Only recalculate the cut, if the live cut preview is active.
my $life_preview_active = $self->_life_preview_active();
$self->_perform_cut() if $life_preview_active;
{
# scale Z down to original size since we're using the transformed mesh for 3D preview
# and cut dialog but ModelObject::cut() needs Z without any instance transformation
my $z = $self->_mesh_slice_z_pos();
# update canvas
if ($self->{canvas}) {
# get volumes to render
my @objects = ();
if ($life_preview_active) {
push @objects, grep defined, @{$self->{new_model_objects}};
} else {
push @objects, $self->{model_object};
}
# get section contour
my @expolygons = ();
foreach my $volume (@{$self->{model_object}->volumes}) {
next if !$volume->mesh;
next if $volume->modifier;
my $expp = $volume->mesh->slice_at($self->{cut_options}{axis}, $z);
push @expolygons, @$expp;
}
my $offset = $self->{model_object}->instances->[0]->offset;
foreach my $expolygon (@expolygons) {
$self->{model_object}->instances->[0]->transform_polygon($_)
for @$expolygon;
if ($self->{cut_options}{axis} != X) {
$expolygon->translate(0, Slic3r::Geometry::scale($offset->y)); #)
}
if ($self->{cut_options}{axis} != Y) {
$expolygon->translate(Slic3r::Geometry::scale($offset->x), 0);
}
}
$self->{canvas}->reset_objects;
$self->{canvas}->load_object($_, undef, [0]) for @objects;
my $plane_z = $self->{cut_options}{z};
$plane_z += 0.02 if !$self->{cut_options}{keep_upper};
$plane_z -= 0.02 if !$self->{cut_options}{keep_lower};
$self->{canvas}->SetCuttingPlane(
$self->{cut_options}{axis},
$plane_z,
[@expolygons],
);
$self->{canvas}->Render;
}
}
# update controls
{
my $z = $self->{cut_options}{z};
my $optgroup = $self->{optgroup};
{
my $bb = $self->{model_object}->instance_bounding_box(0);
my $max = $self->{cut_options}{axis} == X ? $bb->size->x
: $self->{cut_options}{axis} == Y ? $bb->size->y ###
: $bb->size->z;
$optgroup->get_field('z')->set_range(0, $max);
}
$optgroup->get_field('keep_upper')->toggle(my $have_upper = abs($z - $optgroup->get_option('z')->max) > 0.1);
$optgroup->get_field('keep_lower')->toggle(my $have_lower = $z > 0.1);
$optgroup->get_field('rotate_lower')->toggle($z > 0 && $self->{cut_options}{keep_lower} && $self->{cut_options}{axis} == Z);
$optgroup->get_field('preview')->toggle($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower});
# update cut button
if (($self->{cut_options}{keep_upper} && $have_upper)
|| ($self->{cut_options}{keep_lower} && $have_lower)) {
$self->{btn_cut}->Enable;
} else {
$self->{btn_cut}->Disable;
}
}
}
sub NewModelObjects {
my ($self) = @_;
return grep defined, @{ $self->{new_model_objects} };
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm 0000664 0000000 0000000 00000060327 13274424355 0022211 0 ustar 00root root 0000000 0000000 # Configuration of mesh modifiers and their parameters.
# This panel is inserted into ObjectSettingsDialog.
package Slic3r::GUI::Plater::ObjectPartsPanel;
use strict;
use warnings;
use utf8;
use File::Basename qw(basename);
use Wx qw(:misc :sizer :treectrl :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxID_CANCEL
wxTheApp);
use List::Util qw(max);
use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED EVT_TREE_ITEM_RIGHT_CLICK);
use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad rad2deg);
use base 'Wx::Panel';
use constant ICON_OBJECT => 0;
use constant ICON_SOLIDMESH => 1;
use constant ICON_MODIFIERMESH => 2;
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
my $object = $self->{model_object} = $params{model_object};
# create TreeCtrl
my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100],
wxTR_NO_BUTTONS | wxSUNKEN_BORDER | wxTR_HAS_VARIABLE_ROW_HEIGHT
| wxTR_SINGLE | wxTR_NO_BUTTONS);
{
$self->{tree_icons} = Wx::ImageList->new(16, 16, 1);
$tree->AssignImageList($self->{tree_icons});
$self->{tree_icons}->Add(Wx::Bitmap->new($Slic3r::var->("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT
$self->{tree_icons}->Add(Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH
$self->{tree_icons}->Add(Wx::Bitmap->new($Slic3r::var->("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH
my $rootId = $tree->AddRoot("Object", ICON_OBJECT);
$tree->SetPlData($rootId, { type => 'object' });
}
# buttons
$self->{btn_load_part} = Wx::Button->new($self, -1, "Load part…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
$self->{btn_load_modifier} = Wx::Button->new($self, -1, "Load modifier…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
$self->{btn_load_lambda_modifier} = Wx::Button->new($self, -1, "Create modifier…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
$self->{btn_delete} = Wx::Button->new($self, -1, "Delete part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
if ($Slic3r::GUI::have_button_icons) {
$self->{btn_load_part}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG));
$self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG));
$self->{btn_load_lambda_modifier}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_add.png"), wxBITMAP_TYPE_PNG));
$self->{btn_delete}->SetBitmap(Wx::Bitmap->new($Slic3r::var->("brick_delete.png"), wxBITMAP_TYPE_PNG));
}
# buttons sizer
my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$buttons_sizer->Add($self->{btn_load_part}, 0);
$buttons_sizer->Add($self->{btn_load_modifier}, 0);
$buttons_sizer->Add($self->{btn_load_lambda_modifier}, 0);
$buttons_sizer->Add($self->{btn_delete}, 0);
$self->{btn_load_part}->SetFont($Slic3r::GUI::small_font);
$self->{btn_load_modifier}->SetFont($Slic3r::GUI::small_font);
$self->{btn_load_lambda_modifier}->SetFont($Slic3r::GUI::small_font);
$self->{btn_delete}->SetFont($Slic3r::GUI::small_font);
# part settings panel
$self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; });
my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL);
$settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0);
my $optgroup_movers;
# initialize the movement target before it's used.
# on Windows this causes a segfault due to calling distance_to()
# on the object.
$self->{move_target} = Slic3r::Pointf3->new;
$optgroup_movers = $self->{optgroup_movers} = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Move',
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.
my $new = Slic3r::Pointf3->new(map $optgroup_movers->get_value($_), qw(x y z));
if ($self->{move_target}->distance_to($new) > 0) {
$self->{move_target} = $new;
wxTheApp->CallAfter(sub {
$self->_update;
});
}
},
label_width => 20,
);
$optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'x',
type => 'slider',
label => 'X',
default => 0,
full_width => 1,
));
$optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'y',
type => 'slider',
label => 'Y',
default => 0,
full_width => 1,
));
$optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'z',
type => 'slider',
label => 'Z',
default => 0,
full_width => 1,
));
# left pane with tree
my $left_sizer = $self->{left_sizer} = Wx::BoxSizer->new(wxVERTICAL);
$left_sizer->Add($tree, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
$left_sizer->Add($buttons_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
$left_sizer->Add($settings_sizer, 1, wxEXPAND | wxALL, 0);
$left_sizer->Add($optgroup_movers->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->enable_picking(1);
$canvas->select_by('volume');
$canvas->on_select(sub {
my ($volume_idx) = @_;
# convert scene volume to model object volume
$self->reload_tree($canvas->volume_idx($volume_idx));
});
$canvas->load_object($self->{model_object}, undef, [0]);
$canvas->set_auto_bed_shape;
$canvas->SetSize([500,700]);
$canvas->zoom_to_volumes;
}
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
$self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxALL, 0);
$self->{sizer}->Add($canvas, 1, wxEXPAND | wxALL, 0) if $canvas;
$self->SetSizer($self->{sizer});
$self->{sizer}->SetSizeHints($self);
# attach events
EVT_TREE_ITEM_COLLAPSING($self, $tree, sub {
my ($self, $event) = @_;
$event->Veto;
});
EVT_TREE_SEL_CHANGED($self, $tree, sub {
my ($self, $event) = @_;
return if $self->{disable_tree_sel_changed_event};
$self->selection_changed;
});
EVT_TREE_ITEM_RIGHT_CLICK($self, $tree, sub {
my ($self, $event) = @_;
my $item = $event->GetItem;
my $frame = $self->GetFrame;
my $menu = Wx::Menu->new;
{
my $scaleMenu = Wx::Menu->new;
wxTheApp->append_menu_item($scaleMenu, "Uniformly… ", 'Scale the selected object along the XYZ axes',
sub { $self->changescale(undef, 0) });
wxTheApp->append_menu_item($scaleMenu, "Along X axis…", 'Scale the selected object along the X axis',
sub { $self->changescale(X, 0) }, undef, 'bullet_red.png');
wxTheApp->append_menu_item($scaleMenu, "Along Y axis…", 'Scale the selected object along the Y axis',
sub { $self->changescale(Y, 0) }, undef, 'bullet_green.png');
wxTheApp->append_menu_item($scaleMenu, "Along Z axis…", 'Scale the selected object along the Z axis',
sub { $self->changescale(Z, 0) }, undef, 'bullet_blue.png');
wxTheApp->append_submenu($menu, "Scale", 'Scale the selected object by a given factor',
$scaleMenu, undef, 'arrow_out.png');
}
{
my $scaleToSizeMenu = Wx::Menu->new;
wxTheApp->append_menu_item($scaleToSizeMenu, "Uniformly… ", 'Scale the selected object along the XYZ axes',
sub { $self->changescale(undef, 1) });
wxTheApp->append_menu_item($scaleToSizeMenu, "Along X axis…", 'Scale the selected object along the X axis',
sub { $self->changescale(X, 1) }, undef, 'bullet_red.png');
wxTheApp->append_menu_item($scaleToSizeMenu, "Along Y axis…", 'Scale the selected object along the Y axis',
sub { $self->changescale(Y, 1) }, undef, 'bullet_green.png');
wxTheApp->append_menu_item($scaleToSizeMenu, "Along Z axis…", 'Scale the selected object along the Z axis',
sub { $self->changescale(Z, 1) }, undef, 'bullet_blue.png');
wxTheApp->append_submenu($menu, "Scale to size", 'Scale the selected object to match a given size',
$scaleToSizeMenu, undef, 'arrow_out.png');
}
{
my $rotateMenu = Wx::Menu->new;
wxTheApp->append_menu_item($rotateMenu, "Around X axis…", 'Rotate the selected object by an arbitrary angle around X axis',
sub { $self->rotate(undef, X) }, undef, 'bullet_red.png');
wxTheApp->append_menu_item($rotateMenu, "Around Y axis…", 'Rotate the selected object by an arbitrary angle around Y axis',
sub { $self->rotate(undef, Y) }, undef, 'bullet_green.png');
wxTheApp->append_menu_item($rotateMenu, "Around Z axis…", 'Rotate the selected object by an arbitrary angle around Z axis',
sub { $self->rotate(undef, Z) }, undef, 'bullet_blue.png');
wxTheApp->append_submenu($menu, "Rotate", 'Rotate the selected object by an arbitrary angle',
$rotateMenu, undef, 'arrow_rotate_anticlockwise.png');
}
$frame->PopupMenu($menu, $event->GetPoint);
});
EVT_BUTTON($self, $self->{btn_load_part}, sub { $self->on_btn_load(0) });
EVT_BUTTON($self, $self->{btn_load_modifier}, sub { $self->on_btn_load(1) });
EVT_BUTTON($self, $self->{btn_load_lambda_modifier}, sub { $self->on_btn_lambda(1) });
EVT_BUTTON($self, $self->{btn_delete}, \&on_btn_delete);
$self->reload_tree;
return $self;
}
sub reload_tree {
my ($self, $selected_volume_idx) = @_;
$selected_volume_idx //= -1;
my $object = $self->{model_object};
my $tree = $self->{tree};
my $rootId = $tree->GetRootItem;
# despite wxWidgets states that DeleteChildren "will not generate any events unlike Delete() method",
# the MSW implementation of DeleteChildren actually calls Delete() for each item, so
# EVT_TREE_SEL_CHANGED is being called, with bad effects (the event handler is called; this
# subroutine is never continued; an invisible EndModal is called on the dialog causing Plater
# to continue its logic and rescheduling the background process etc. GH #2774)
$self->{disable_tree_sel_changed_event} = 1;
$tree->DeleteChildren($rootId);
$self->{disable_tree_sel_changed_event} = 0;
my $selectedId = $rootId;
foreach my $volume_id (0..$#{$object->volumes}) {
my $volume = $object->volumes->[$volume_id];
my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH;
my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon);
if ($volume_id == $selected_volume_idx) {
$selectedId = $itemId;
}
$tree->SetPlData($itemId, {
type => 'volume',
volume_id => $volume_id,
});
}
$tree->ExpandAll;
Slic3r::GUI->CallAfter(sub {
$self->{tree}->SelectItem($selectedId);
# SelectItem() should trigger EVT_TREE_SEL_CHANGED as per wxWidgets docs,
# but in fact it doesn't if the given item is already selected (this happens
# on first load)
$self->selection_changed;
});
}
sub get_selection {
my ($self) = @_;
my $nodeId = $self->{tree}->GetSelection;
if ($nodeId->IsOk) {
return $self->{tree}->GetPlData($nodeId);
}
return undef;
}
sub selection_changed {
my ($self) = @_;
# deselect all meshes
if ($self->{canvas}) {
$_->selected(0) for @{$self->{canvas}->volumes};
}
# disable things as if nothing is selected
$self->{btn_delete}->Disable;
$self->{settings_panel}->disable;
$self->{settings_panel}->set_config(undef);
# reset move sliders
$self->{optgroup_movers}->set_value("x", 0);
$self->{optgroup_movers}->set_value("y", 0);
$self->{optgroup_movers}->set_value("z", 0);
$self->{move_target} = Slic3r::Pointf3->new;
if (my $itemData = $self->get_selection) {
my ($config, @opt_keys);
if ($itemData->{type} eq 'volume') {
# select volume in 3D preview
if ($self->{canvas}) {
$self->{canvas}->volumes->[ $itemData->{volume_id} ]{selected} = 1;
}
$self->{btn_delete}->Enable;
# attach volume config to settings panel
my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
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');
# get default values
@opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys};
} elsif ($itemData->{type} eq 'object') {
# select nothing in 3D preview
# attach object config to settings panel
$self->{left_sizer}->Hide($self->{optgroup_movers}->sizer);
$self->{staticbox}->SetLabel('Object Settings');
@opt_keys = (map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new);
$config = $self->{model_object}->config;
}
# get default values
my $default_config = Slic3r::Config->new_from_defaults(@opt_keys);
# append default extruder
push @opt_keys, 'extruder';
$default_config->set('extruder', 0);
$config->set_ifndef('extruder', 0);
$self->{settings_panel}->set_default_config($default_config);
$self->{settings_panel}->set_config($config);
$self->{settings_panel}->set_opt_keys(\@opt_keys);
$self->{settings_panel}->set_fixed_options([qw(extruder)]);
$self->{settings_panel}->enable;
}
$self->{canvas}->Render if $self->{canvas};
}
sub on_btn_load {
my ($self, $is_modifier) = @_;
my @input_files = wxTheApp->open_model($self);
foreach my $input_file (@input_files) {
my $model = eval { Slic3r::Model->read_from_file($input_file) };
if ($@) {
Slic3r::GUI::show_error($self, $@);
next;
}
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});
# set a default extruder value, since user can't add it manually
$new_volume->config->set_ifndef('extruder', 0);
$self->{parts_changed} = 1;
}
}
}
$self->_parts_changed;
}
sub on_btn_lambda {
my ($self, $is_modifier) = @_;
my $dlg = Slic3r::GUI::Plater::LambdaObjectDialog->new($self);
if ($dlg->ShowModal() == wxID_CANCEL) {
return;
}
my $params = $dlg->ObjectParameter;
my $type = "".$params->{"type"};
my $name = "lambda-".$params->{"type"};
my $mesh;
if ($type eq "box") {
$mesh = Slic3r::TriangleMesh::make_cube(@{$params->{"dim"}});
} elsif ($type eq "cylinder") {
$mesh = Slic3r::TriangleMesh::make_cylinder($params->{"cyl_r"}, $params->{"cyl_h"});
} elsif ($type eq "sphere") {
$mesh = Slic3r::TriangleMesh::make_sphere($params->{"sph_rho"});
} elsif ($type eq "slab") {
my $size = $self->{model_object}->bounding_box->size;
$mesh = Slic3r::TriangleMesh::make_cube(
$size->x*1.5,
$size->y*1.5, #**
$params->{"slab_h"},
);
# box sets the base coordinate at 0,0, move to center of plate
$mesh->translate(
-$size->x*1.5/2.0,
-$size->y*1.5/2.0, #**
0,
);
} 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);
$new_volume->set_name($name);
# set a default extruder value, since user can't add it manually
$new_volume->config->set_ifndef('extruder', 0);
$self->_parts_changed($self->{model_object}->volumes_count-1);
}
sub on_btn_delete {
my ($self) = @_;
my $itemData = $self->get_selection;
if ($itemData && $itemData->{type} eq 'volume') {
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
# if user is deleting the last solid part, throw error
if (!$volume->modifier && scalar(grep !$_->modifier, @{$self->{model_object}->volumes}) == 1) {
Slic3r::GUI::show_error($self, "You can't delete the last solid part from this object.");
return;
}
$self->{model_object}->delete_volume($itemData->{volume_id});
$self->{parts_changed} = 1;
}
$self->_parts_changed;
}
sub _parts_changed {
my ($self, $selected_volume_idx) = @_;
$self->{parts_changed} = 1;
$self->reload_tree($selected_volume_idx);
if ($self->{canvas}) {
$self->{canvas}->reset_objects;
$self->{canvas}->load_object($self->{model_object});
$self->{canvas}->zoom_to_volumes;
$self->{canvas}->Render;
}
}
sub CanClose {
my $self = shift;
return 1; # skip validation for now
# validate options before allowing user to dismiss the dialog
# the validate method only works on full configs so we have
# to merge our settings with the default ones
my $config = Slic3r::Config->merge($self->GetParent->GetParent->GetParent->GetParent->GetParent->config, $self->model_object->config);
eval {
$config->validate;
};
return 0 if Slic3r::GUI::catch_error($self);
return 1;
}
sub PartsChanged {
my ($self) = @_;
return $self->{parts_changed};
}
sub PartSettingsChanged {
my ($self) = @_;
return $self->{part_settings_changed};
}
sub _update {
my ($self) = @_;
my $itemData = $self->get_selection;
if ($itemData && $itemData->{type} eq 'volume') {
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
$volume->mesh->translate(@{ $volume->mesh->bounding_box->min_point->vector_to($self->{move_target}) });
}
$self->{parts_changed} = 1;
my @objects = ();
push @objects, $self->{model_object};
$self->{canvas}->reset_objects;
$self->{canvas}->load_object($_, undef, [0]) for @objects;
$self->{canvas}->Render;
}
sub changescale {
my ($self, $axis, $tosize) = @_;
my $itemData = $self->get_selection;
if ($itemData && $itemData->{type} eq 'volume') {
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
my $object_size = $volume->bounding_box->size;
if (defined $axis) {
my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z';
my $scale;
if (defined $tosize) {
my $cursize = $object_size->[$axis];
# Wx::GetNumberFromUser() does not support decimal numbers
my $newsize = Wx::GetTextFromUser(
sprintf("Enter the new size for the selected mesh:"),
"Scale along $axis_name",
$cursize, $self);
return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
$scale = $newsize / $cursize * 100;
} else {
# Wx::GetNumberFromUser() does not support decimal numbers
$scale = Wx::GetTextFromUser("Enter the scale % for the selected object:",
"Scale along $axis_name", 100, $self);
$scale =~ s/%$//;
return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
}
my $versor = [1,1,1];
$versor->[$axis] = $scale/100;
$volume->mesh->scale_xyz(Slic3r::Pointf3->new(@$versor));
} else {
my $scale;
if ($tosize) {
my $cursize = max(@$object_size);
# Wx::GetNumberFromUser() does not support decimal numbers
my $newsize = Wx::GetTextFromUser("Enter the new max size for the selected object:",
"Scale", $cursize, $self);
return if !$newsize || $newsize !~ /^\d*(?:\.\d*)?$/ || $newsize < 0;
$scale = $newsize / $cursize;
} else {
# max scale factor should be above 2540 to allow importing files exported in inches
# Wx::GetNumberFromUser() does not support decimal numbers
$scale = Wx::GetTextFromUser("Enter the scale % for the selected object:", 'Scale',
100, $self);
return if !$scale || $scale !~ /^\d*(?:\.\d*)?$/ || $scale < 0;
}
return if !$scale || $scale < 0;
$volume->mesh->scale($scale);
}
$self->_parts_changed;
}
}
sub rotate {
my $self = shift;
my ($angle, $axis) = @_;
# angle is in degrees
my $itemData = $self->get_selection;
if ($itemData && $itemData->{type} eq 'volume') {
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
if (!defined $angle) {
my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z';
my $default = $axis == Z ? 0 : 0;
# Wx::GetNumberFromUser() does not support decimal numbers
$angle = Wx::GetTextFromUser("Enter the rotation angle:", "Rotate around $axis_name axis",
$default, $self);
return if !$angle || $angle !~ /^-?\d*(?:\.\d*)?$/ || $angle == -1;
}
if ($axis == X) { $volume->mesh->rotate_x(deg2rad($angle)); }
if ($axis == Y) { $volume->mesh->rotate_y(deg2rad($angle)); }
if ($axis == Z) { $volume->mesh->rotate_z(deg2rad($angle)); }
$self->_parts_changed;
}
}
sub GetFrame {
my ($self) = @_;
return &Wx::GetTopLevelParent($self);
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm 0000664 0000000 0000000 00000032562 13274424355 0023060 0 ustar 00root root 0000000 0000000 # This dialog opens up when double clicked on an object line in the list at the right side of the platter.
# One may load additional STLs and additional modifier STLs,
# one may change the properties of the print per each modifier mesh or a Z-span.
package Slic3r::GUI::Plater::ObjectSettingsDialog;
use strict;
use warnings;
use utf8;
use Wx qw(:dialog :id :misc :sizer :systemsettings :notebook wxTAB_TRAVERSAL wxTheApp);
use Wx::Event qw(EVT_BUTTON EVT_MENU);
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, [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->{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 {
# validate user input
return if !$self->{parts}->CanClose;
return if !$self->{layers}->CanClose;
# notify tabs
$self->{layers}->Closing;
# save window size
wxTheApp->save_window_pos($self, "object_settings");
$self->EndModal(wxID_OK);
$self->Destroy;
});
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($self->{tabpanel}, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
$self->SetSizer($sizer);
$self->SetMinSize($self->GetSize);
wxTheApp->restore_window_pos($self, "object_settings");
return $self;
}
sub PartsChanged {
my ($self) = @_;
return $self->{parts}->PartsChanged;
}
sub PartSettingsChanged {
my ($self) = @_;
return $self->{parts}->PartSettingsChanged || $self->{layers}->LayersChanged;
}
package Slic3r::GUI::Plater::ObjectDialog::BaseTab;
use base 'Wx::Panel';
sub model_object {
my ($self) = @_;
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;
use Wx::Event qw(EVT_GRID_CELL_CHANGED);
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 $sizer = Wx::BoxSizer->new(wxVERTICAL);
{
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);
}
my $grid = $self->{grid} = Wx::Grid->new($self, -1, wxDefaultPosition, wxDefaultSize);
$sizer->Add($grid, 1, wxEXPAND | wxALL, 10);
$grid->CreateGrid(0, 3);
$grid->DisableDragRowSize;
$grid->HideRowLabels if &Wx::wxVERSION_STRING !~ / 2\.8\./;
$grid->SetColLabelValue(0, "Min Z (mm)");
$grid->SetColLabelValue(1, "Max Z (mm)");
$grid->SetColLabelValue(2, "Layer height (mm)");
$grid->SetColSize($_, -1) for 0..2;
$grid->SetDefaultCellAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
# load data
foreach my $range (@{ $self->model_object->layer_height_ranges }) {
$grid->AppendRows(1);
my $i = $grid->GetNumberRows-1;
$grid->SetCellValue($i, $_, $range->[$_]) for 0..2;
}
$grid->AppendRows(1); # append one empty row
EVT_GRID_CELL_CHANGED($grid, sub {
my ($grid, $event) = @_;
# remove any non-numeric character
my $value = $grid->GetCellValue($event->GetRow, $event->GetCol);
$value =~ s/,/./g;
$value =~ s/[^0-9.]//g;
$grid->SetCellValue($event->GetRow, $event->GetCol, $value);
# if there's no empty row, let's append one
for my $i (0 .. $grid->GetNumberRows) {
if ($i == $grid->GetNumberRows) {
# if we're here then we found no empty row
$grid->AppendRows(1);
last;
}
if (!grep $grid->GetCellValue($i, $_), 0..2) {
# exit loop if this row is empty
last;
}
}
$self->{layers_changed} = 1;
});
$self->SetSizer($sizer);
$sizer->SetSizeHints($self);
return $self;
}
sub CanClose {
my $self = shift;
# validate ranges before allowing user to dismiss the dialog
foreach my $range ($self->_get_ranges) {
my ($min, $max, $height) = @$range;
if ($max <= $min) {
Slic3r::GUI::show_error($self, "Invalid Z range $min-$max.");
return 0;
}
if ($min < 0 || $max < 0) {
Slic3r::GUI::show_error($self, "Invalid Z range $min-$max.");
return 0;
}
if ($height < 0) {
Slic3r::GUI::show_error($self, "Invalid layer height $height.");
return 0;
}
# TODO: check for overlapping ranges
}
return 1;
}
sub Closing {
my $self = shift;
# save ranges into the plater object
$self->model_object->set_layer_height_ranges([ $self->_get_ranges ]);
}
sub _get_ranges {
my $self = shift;
my @ranges = ();
for my $i (0 .. $self->{grid}->GetNumberRows-1) {
my ($min, $max, $height) = map $self->{grid}->GetCellValue($i, $_), 0..2;
next if $min eq '' || $max eq '' || $height eq '';
push @ranges, [ $min, $max, $height ];
}
return sort { $a->[0] <=> $b->[0] } @ranges;
}
sub LayersChanged {
my ($self) = @_;
return $self->{layers_changed};
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm 0000664 0000000 0000000 00000017620 13274424355 0023267 0 ustar 00root root 0000000 0000000 # Maintains, displays, adds and removes overrides of slicing parameters.
package Slic3r::GUI::Plater::OverrideSettingsPanel;
use strict;
use warnings;
use utf8;
use List::Util qw(first);
use Wx qw(:misc :sizer :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG
wxTheApp);
use Wx::Event qw(EVT_BUTTON EVT_LEFT_DOWN EVT_MENU);
use base 'Wx::ScrolledWindow';
use constant ICON_MATERIAL => 0;
use constant ICON_SOLIDMESH => 1;
use constant ICON_MODIFIERMESH => 2;
my %icons = (
'Advanced' => 'wand.png',
'Extruders' => 'funnel.png',
'Extrusion Width' => 'funnel.png',
'Infill' => 'infill.png',
'Layers and Perimeters' => 'layers.png',
'Skirt and brim' => 'box.png',
'Speed' => 'time.png',
'Speed > Acceleration' => 'time.png',
'Support material' => 'building.png',
);
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $params{size} // wxDefaultSize, wxTAB_TRAVERSAL);
$self->{default_config} = Slic3r::Config->new;
$self->{config} = Slic3r::Config->new;
$self->{on_change} = $params{on_change};
$self->{can_add} = 1;
$self->{can_delete} = 1;
$self->{fixed_options} = {};
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
$self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL);
$self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 0);
# option selector
{
# create the button
my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("add.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
$btn->SetToolTipString("Override one more option")
if $btn->can('SetToolTipString');
EVT_LEFT_DOWN($btn, sub {
my $menu = Wx::Menu->new;
my $last_cat = '';
# create category submenus
my %categories = (); # category => submenu
foreach my $opt_key (@{$self->{options}}) {
if (my $cat = $Slic3r::Config::Options->{$opt_key}{category}) {
$categories{$cat} //= Wx::Menu->new;
}
}
# append submenus to main menu
foreach my $cat (sort keys %categories) {
wxTheApp->append_submenu($menu, $cat, "", $categories{$cat}, undef, $icons{$cat});
}
# append options to submenus
foreach my $opt_key (@{$self->{options}}) {
my $cat = $Slic3r::Config::Options->{$opt_key}{category} or next;
my $cb = sub {
$self->{config}->set($opt_key, $self->{default_config}->get($opt_key));
$self->update_optgroup;
$self->{on_change}->($opt_key) if $self->{on_change};
};
wxTheApp->append_menu_item($categories{$cat}, $self->{option_labels}{$opt_key},
$Slic3r::Config::Options->{$opt_key}{tooltip}, $cb);
}
$self->PopupMenu($menu, $btn->GetPosition);
$menu->Destroy;
});
my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$h_sizer->Add($btn, 0, wxALL, 0);
$self->{sizer}->Add($h_sizer, 0, wxEXPAND | wxBOTTOM, 10);
}
$self->SetSizer($self->{sizer});
# http://docs.wxwidgets.org/3.0/classwx_scrolled.html#details
$self->SetScrollRate(0, $Slic3r::GUI::scroll_step);
$self->set_opt_keys($params{opt_keys}) if $params{opt_keys};
$self->update_optgroup;
return $self;
}
#Â Sets the config used to get the default values for user-added options.
sub set_default_config {
my ($self, $config) = @_;
$self->{default_config} = $config;
}
# Sets the target config, whose options will be displayed in the OptionsGroup.
sub set_config {
my ($self, $config) = @_;
$self->{config} = $config;
$self->update_optgroup;
}
# Sets the options listed in the Add button.
sub set_opt_keys {
my ($self, $opt_keys) = @_;
# sort options by label
$self->{option_labels} = {};
foreach my $opt_key (@$opt_keys) {
my $def = $Slic3r::Config::Options->{$opt_key} or next;
$self->{option_labels}{$opt_key} = $def->{full_label} // $def->{label};
};
$self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } keys %{$self->{option_labels}} ];
}
# Sets the options that user can't remove.
sub set_fixed_options {
my ($self, $opt_keys) = @_;
$self->{fixed_options} = { map {$_ => 1} @$opt_keys };
$self->update_optgroup;
}
sub fixed_options {
my ($self) = @_;
return keys %{$self->{fixed_options}};
}
sub update_optgroup {
my $self = shift;
$self->{options_sizer}->Clear(1);
return if !defined $self->{config};
$self->{btn_add}->Show($self->{can_add});
my %categories = ();
foreach my $opt_key (@{$self->{config}->get_keys}) {
my $category = $Slic3r::Config::Options->{$opt_key}{category};
$categories{$category} ||= [];
push @{$categories{$category}}, $opt_key;
}
foreach my $category (sort keys %categories) {
my $optgroup;
$optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
parent => $self,
title => $category,
config => $self->{config},
full_labels => 1,
label_font => $Slic3r::GUI::small_font,
sidetext_font => $Slic3r::GUI::small_font,
label_width => 120,
on_change => sub {
my ($opt_key) = @_;
$self->{on_change}->($opt_key) if $self->{on_change};
},
extra_column => sub {
my ($line) = @_;
my $opt_id = $line->get_options->[0]->opt_id; # we assume that we have one option per line
my ($opt_key, $opt_index) = @{ $optgroup->_opt_map->{$opt_id} };
# disallow deleting fixed options
return undef if $self->{fixed_options}{$opt_key} || !$self->{can_delete};
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
EVT_BUTTON($self, $btn, sub {
$self->{config}->erase($opt_key);
$self->{on_change}->($opt_key) if $self->{on_change};
wxTheApp->CallAfter(sub { $self->update_optgroup });
});
return $btn;
},
);
foreach my $opt_key (sort @{$categories{$category}}) {
# For array options we override the first value.
my $opt_index = (ref($self->{config}->get($opt_key)) eq 'ARRAY') ? 0 : -1;
$optgroup->append_single_option_line($opt_key, $opt_index);
}
$self->{options_sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 0);
}
$self->GetParent->Layout; # we need this for showing scrollbars
}
# work around a wxMAC bug causing controls not being disabled when calling Disable() on a Window
sub enable {
my ($self) = @_;
$self->{btn_add}->Enable;
$self->Enable;
}
sub disable {
my ($self) = @_;
$self->{btn_add}->Disable;
$self->Disable;
}
# Shows or hides the Add/Delete buttons.
sub set_editable {
my ($self, $editable) = @_;
$self->{can_add} = $self->{can_delete} = $editable;
}
# Shows or hides the Add button.
sub can_add {
my ($self, $can) = @_;
$self->{can_add} = $can if defined $can;
return $can;
}
# Shows or hides the Delete button.
sub can_delete {
my ($self, $can) = @_;
$self->{can_delete} = $can if defined $can;
return $can;
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Plater/SplineControl.pm 0000664 0000000 0000000 00000034737 13274424355 0021612 0 ustar 00root root 0000000 0000000 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;
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;
Slic3r-1.3.0/lib/Slic3r/GUI/Preferences.pm 0000664 0000000 0000000 00000016464 13274424355 0020026 0 ustar 00root root 0000000 0000000 # Preferences dialog, opens from Menu: File->Preferences
package Slic3r::GUI::Preferences;
use Wx qw(:dialog :id :misc :sizer :systemsettings wxTheApp);
use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER);
use base 'Wx::Dialog';
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new($parent, -1, "Preferences", wxDefaultPosition, wxDefaultSize);
$self->{values} = {};
my $optgroup;
$optgroup = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'General',
on_change => sub {
my ($opt_id) = @_;
$self->{values}{$opt_id} = $optgroup->get_value($opt_id);
},
label_width => 200,
);
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( # version_check
opt_id => 'version_check',
type => 'bool',
label => 'Check for updates',
tooltip => 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.',
default => $Slic3r::GUI::Settings->{_}{version_check} // 1,
readonly => !wxTheApp->have_version_check,
));
$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( # autocenter
opt_id => 'autocenter',
type => 'bool',
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( # 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( # background_processing
opt_id => 'background_processing',
type => 'bool',
label => 'Background processing',
tooltip => 'If this is enabled, Slic3r will pre-process objects as soon as they\'re loaded in order to save time when exporting G-code.',
default => $Slic3r::GUI::Settings->{_}{background_processing},
readonly => !$Slic3r::have_threads,
));
$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( # 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);
my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
EVT_BUTTON($self, wxID_OK, sub { $self->_accept });
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
$self->SetSizer($sizer);
$sizer->SetSizeHints($self);
return $self;
}
sub _accept {
my $self = shift;
if ($self->{values}{mode}) {
Slic3r::GUI::warning_catcher($self)->("You need to restart Slic3r to make the changes effective.");
}
$Slic3r::GUI::Settings->{_}{$_} = $self->{values}{$_} for keys %{$self->{values}};
wxTheApp->save_settings;
$self->EndModal(wxID_OK);
$self->Close; # needed on Linux
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Preset.pm 0000664 0000000 0000000 00000014166 13274424355 0017024 0 ustar 00root root 0000000 0000000 package Slic3r::GUI::Preset;
use Moo;
use Unicode::Normalize;
use Wx qw(:dialog :icon :id wxTheApp);
has 'group' => (is => 'ro', required => 1);
has 'default' => (is => 'ro', default => sub { 0 });
has 'external' => (is => 'ro', default => sub { 0 });
has 'name' => (is => 'rw', required => 1);
has 'file' => (is => 'rw');
has '_config' => (is => 'rw', default => sub { Slic3r::Config->new });
has '_dirty_config' => (is => 'ro', default => sub { Slic3r::Config->new });
sub BUILD {
my ($self) = @_;
$self->name(Unicode::Normalize::NFC($self->name));
}
sub _loaded {
my ($self) = @_;
return !$self->_config->empty;
}
sub dirty_options {
my ($self) = @_;
my @dirty = ();
# Options present in both configs with different values:
push @dirty, @{$self->_config->diff($self->_dirty_config)};
# Overrides added to the dirty config:
my @extra = $self->_group_class->overriding_options;
push @dirty, grep { !$self->_config->has($_) && $self->_dirty_config->has($_) } @extra;
# Overrides removed from the dirty config:
push @dirty, grep { $self->_config->has($_) && !$self->_dirty_config->has($_) } @extra;
return @dirty;
}
sub dirty {
my ($self) = @_;
return !!$self->dirty_options;
}
sub dropdown_name {
my ($self) = @_;
my $name = $self->name;
$name .= " (modified)" if $self->dirty;
return $name;
}
sub file_exists {
my ($self) = @_;
die "Can't call file_exists() on a non-file preset" if !$self->file;
return -e Slic3r::encode_path($self->file);
}
sub rename {
my ($self, $name) = @_;
$self->name($name);
$self->file(sprintf "$Slic3r::GUI::datadir/%s/%s.ini", $self->group, $name);
}
sub prompt_unsaved_changes {
my ($self, $parent) = @_;
if ($self->dirty) {
my $name = $self->default ? 'Default preset' : "Preset \"" . $self->name . "\"";
my $opts = '';
foreach my $opt_key ($self->dirty_options) {
my $opt = $Slic3r::Config::Options->{$opt_key};
my $name = $opt->{full_label} // $opt->{label};
if ($opt->{category}) {
$name = $opt->{category} . " > $name";
}
$opts .= "- $name\n";
}
my $msg = sprintf "%s has unsaved changes:\n%s\nDo you want to save them?", $name, $opts;
my $confirm = Wx::MessageDialog->new($parent, $msg,
'Unsaved Changes', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_QUESTION);
$confirm->SetYesNoCancelLabels('Save', 'Discard', 'Cancel');
my $res = $confirm->ShowModal;
if ($res == wxID_CANCEL) {
return 0;
} elsif ($res == wxID_YES) {
return $self->default ? $self->save_prompt($parent) : $self->save;
} elsif ($res == wxID_NO) {
$self->dismiss_changes;
return 1;
}
}
return 1;
}
sub save_prompt {
my ($self, $parent) = @_;
my $default_name = $self->default ? 'Untitled' : $self->name;
$default_name =~ s/\.ini$//i;
my $dlg = Slic3r::GUI::SavePresetWindow->new($parent,
default => $default_name,
values => [ map $_->name, grep !$_->default && !$_->external, @{wxTheApp->presets->{$self->name}} ],
);
return 0 unless $dlg->ShowModal == wxID_OK;
$self->save_as($dlg->get_name);
}
sub save {
my ($self, $opt_keys) = @_;
return $self->save_as($self->name, $opt_keys);
}
sub save_as {
my ($self, $name, $opt_keys) = @_;
$self->rename($name);
if (!$self->file) {
die "Calling save() without setting filename";
}
if ($opt_keys) {
$self->_config->apply_only($self->_dirty_config, $opt_keys);
} else {
$self->_config->clear;
$self->_config->apply($self->_dirty_config);
}
# unlink the file first to avoid problems on case-insensitive file systems
unlink Slic3r::encode_path($self->file);
$self->_config->save($self->file);
wxTheApp->load_presets;
return 1;
}
sub dismiss_changes {
my ($self) = @_;
$self->_dirty_config->clear;
$self->_dirty_config->apply($self->_config);
}
sub delete {
my ($self) = @_;
die "Default config can't be deleted" if $self->default;
die "External configs can't be deleted" if $self->external;
# Otherwise wxTheApp->load_presets() will keep it
$self->dismiss_changes;
if ($self->file) {
unlink Slic3r::encode_path($self->file) if $self->file_exists;
$self->file(undef);
}
}
# This returns the loaded config with the dirty options applied.
sub dirty_config {
my ($self) = @_;
$self->load_config if !$self->_loaded;
return $self->_dirty_config->clone;
}
sub load_config {
my ($self) = @_;
return $self->_config if $self->_loaded;
my @keys = $self->_group_class->options;
my @extra_keys = $self->_group_class->overriding_options;
if ($self->default) {
$self->_config(Slic3r::Config->new_from_defaults(@keys));
} elsif ($self->file) {
if (!$self->file_exists) {
Slic3r::GUI::show_error(undef, "The selected preset does not exist anymore (" . $self->file . ").");
return undef;
}
my $external_config = Slic3r::Config->load($self->file);
if (!@keys) {
$self->_config($external_config);
} else {
# apply preset values on top of defaults
my $config = Slic3r::Config->new_from_defaults(@keys);
$config->set($_, $external_config->get($_))
for grep $external_config->has($_), @keys;
# For extra_keys we don't populate defaults.
if (@extra_keys && !$self->external) {
$config->set($_, $external_config->get($_))
for grep $external_config->has($_), @extra_keys;
}
$self->_config($config);
}
}
$self->_dirty_config->apply($self->_config);
return $self->_config;
}
sub _group_class {
my ($self) = @_;
return "Slic3r::GUI::PresetEditor::".ucfirst $self->group;
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/PresetEditor.pm 0000664 0000000 0000000 00000220222 13274424355 0020163 0 ustar 00root root 0000000 0000000 package Slic3r::GUI::PresetEditor;
use strict;
use warnings;
use utf8;
use File::Basename qw(basename);
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);
use base qw(Wx::Panel Class::Accessor);
__PACKAGE__->mk_accessors(qw(current_preset config));
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
$self->{presets} = wxTheApp->presets->{$self->name};
# horizontal sizer
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
#$self->{sizer}->SetSizeHints($self);
$self->SetSizer($self->{sizer});
# left vertical sizer
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
$self->{sizer}->Add($left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3);
my $left_col_width = 150;
# preset chooser
{
# choice menu
$self->{presets_choice} = Wx::Choice->new($self, -1, wxDefaultPosition, [$left_col_width, -1], []);
$self->{presets_choice}->SetFont($Slic3r::GUI::small_font);
# buttons
$self->{btn_save_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("disk.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
$self->{btn_delete_preset} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
$self->{btn_save_preset}->SetToolTipString("Save current " . lc($self->title));
$self->{btn_delete_preset}->SetToolTipString("Delete this preset");
$self->{btn_delete_preset}->Disable;
### These cause GTK warnings:
###my $box = Wx::StaticBox->new($self, -1, "Presets:", wxDefaultPosition, [$left_col_width, 50]);
###my $hsizer = Wx::StaticBoxSizer->new($box, wxHORIZONTAL);
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
$left_sizer->Add($hsizer, 0, wxEXPAND | wxBOTTOM, 5);
$hsizer->Add($self->{presets_choice}, 1, wxRIGHT | wxALIGN_CENTER_VERTICAL, 3);
$hsizer->Add($self->{btn_save_preset}, 0, wxALIGN_CENTER_VERTICAL);
$hsizer->Add($self->{btn_delete_preset}, 0, wxALIGN_CENTER_VERTICAL);
}
# tree
$self->{treectrl} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [$left_col_width, -1], wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN | wxWANTS_CHARS);
$left_sizer->Add($self->{treectrl}, 1, wxEXPAND);
$self->{icons} = Wx::ImageList->new(16, 16, 1);
$self->{treectrl}->AssignImageList($self->{icons});
$self->{iconcount} = -1;
$self->{treectrl}->AddRoot("root");
$self->{pages} = [];
$self->{treectrl}->SetIndent(0);
$self->{disable_tree_sel_changed_event} = 0;
EVT_TREE_SEL_CHANGED($parent, $self->{treectrl}, sub {
return if $self->{disable_tree_sel_changed_event};
my $page = first { $_->{title} eq $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection) } @{$self->{pages}}
or return;
$_->Hide for @{$self->{pages}};
$page->Show;
$self->{sizer}->Layout;
$self->Refresh;
});
EVT_KEY_DOWN($self->{treectrl}, sub {
my ($treectrl, $event) = @_;
if ($event->GetKeyCode == WXK_TAB) {
$treectrl->Navigate($event->ShiftDown ? &Wx::wxNavigateBackward : &Wx::wxNavigateForward);
} else {
$event->Skip;
}
});
EVT_CHOICE($parent, $self->{presets_choice}, sub {
$self->_on_select_preset;
});
EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset });
EVT_BUTTON($self, $self->{btn_delete_preset}, sub {
my $res = Wx::MessageDialog->new($self, "Are you sure you want to delete the selected preset?", 'Delete Preset', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal;
return unless $res == wxID_YES;
$self->current_preset->delete;
$self->current_preset(undef);
wxTheApp->load_presets;
$self->load_presets;
$self->select_preset(0, 1);
});
$self->config(Slic3r::Config->new_from_defaults($self->options));
$self->build;
$self->update_tree;
$self->load_presets;
$self->_update;
return $self;
}
# This is called by the save button.
sub save_preset {
my ($self) = @_;
# since buttons (and choices too) don't get focus on Mac, we set focus manually
# to the treectrl so that the EVT_* events are fired for the input field having
# focus currently. is there anything better than this?
$self->{treectrl}->SetFocus;
my $preset = $self->current_preset;
$preset->save_prompt($self);
$self->load_presets;
$self->select_preset_by_name($preset->name);
$self->{on_save_preset}->($self->name, $preset) if $self->{on_save_preset};
return 1;
}
sub on_save_preset {
my ($self, $cb) = @_;
$self->{on_save_preset} = $cb;
}
sub on_value_change {
my ($self, $cb) = @_;
$self->{on_value_change} = $cb;
}
# This method is supposed to be called whenever new values are loaded
# or changed by user (so also when a preset is loaded).
# 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($opt_key);
});
}
sub _update {}
sub on_preset_loaded {}
sub select_preset {
my ($self, $i, $force) = @_;
$self->{presets_choice}->SetSelection($i);
$self->_on_select_preset($force);
}
sub select_preset_by_name {
my ($self, $name, $force) = @_;
my $presets = wxTheApp->presets->{$self->name};
my $i = first { $presets->[$_]->name eq $name } 0..$#$presets;
if (!defined $i) {
warn "No preset named $name";
return 0;
}
$self->{presets_choice}->SetSelection($i);
$self->_on_select_preset($force);
}
sub prompt_unsaved_changes {
my ($self) = @_;
return 1 if !$self->current_preset;
return $self->current_preset->prompt_unsaved_changes($self);
}
sub on_select_preset {
my ($self, $cb) = @_;
$self->{on_select_preset} = $cb;
}
sub _on_select_preset {
my ($self, $force) = @_;
# This method is called:
# - upon first initialization;
# - whenever user selects a preset from the dropdown;
# - whenever select_preset() or select_preset_by_name() are called.
# Get the selected name.
my $preset = wxTheApp->presets->{$self->name}->[$self->{presets_choice}->GetSelection];
# If selection didn't change, do nothing.
# (But still reset current_preset because it might contain an older object of the
# current preset)
if (defined $self->current_preset && $preset->name eq $self->current_preset->name) {
$self->current_preset($preset);
return;
}
# If we have unsaved changes, prompt user.
if (!$force && !$self->prompt_unsaved_changes) {
# User decided not to save the current changes, so we restore the previous selection.
my $presets = wxTheApp->presets->{$self->name};
my $i = first { $presets->[$_]->name eq $self->current_preset->name } 0..$#$presets;
$self->{presets_choice}->SetSelection($i);
return;
}
$self->current_preset($preset);
# We reload presets in order to remove the "(modified)" suffix in case user was
# prompted and chose to discard changes.
$self->load_presets;
$self->reload_preset;
eval {
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
($preset->default || $preset->external)
? $self->{btn_delete_preset}->Disable
: $self->{btn_delete_preset}->Enable;
$self->_update;
$self->on_preset_loaded;
};
if ($@) {
$@ = "I was unable to load the selected config file: $@";
Slic3r::GUI::catch_error($self);
}
$self->{on_select_preset}->($self->name, $preset) if $self->{on_select_preset};
}
sub add_options_page {
my $self = shift;
my ($title, $icon, %params) = @_;
if ($icon) {
my $bitmap = Wx::Bitmap->new($Slic3r::var->($icon), wxBITMAP_TYPE_PNG);
$self->{icons}->Add($bitmap);
$self->{iconcount}++;
}
my $page = Slic3r::GUI::PresetEditor::Page->new($self, $title, $self->{iconcount});
$page->Hide;
$self->{sizer}->Add($page, 1, wxEXPAND | wxLEFT, 5);
push @{$self->{pages}}, $page;
return $page;
}
sub reload_preset {
my ($self) = @_;
$self->current_preset->load_config if !$self->current_preset->_loaded;
$self->config->clear;
$self->config->apply($self->current_preset->dirty_config);
$self->reload_config;
}
sub reload_config {
my $self = shift;
$_->reload_config for @{$self->{pages}};
}
sub update_tree {
my ($self) = @_;
# get label of the currently selected item
my $selected = $self->{treectrl}->GetItemText($self->{treectrl}->GetSelection);
my $rootItem = $self->{treectrl}->GetRootItem;
$self->{treectrl}->DeleteChildren($rootItem);
my $have_selection = 0;
foreach my $page (@{$self->{pages}}) {
my $itemId = $self->{treectrl}->AppendItem($rootItem, $page->{title}, $page->{iconID});
if ($page->{title} eq $selected) {
$self->{disable_tree_sel_changed_event} = 1;
$self->{treectrl}->SelectItem($itemId);
$self->{disable_tree_sel_changed_event} = 0;
$have_selection = 1;
}
}
if (!$have_selection) {
# this is triggered on first load, so we don't disable the sel change event
$self->{treectrl}->SelectItem($self->{treectrl}->GetFirstChild($rootItem));
}
}
sub load_presets {
my $self = shift;
my $presets = wxTheApp->presets->{$self->name};
$self->{presets_choice}->Clear;
foreach my $preset (@$presets) {
$self->{presets_choice}->Append($preset->dropdown_name);
# Preserve selection.
if ($self->current_preset && $self->current_preset->name eq $preset->name) {
$self->{presets_choice}->SetSelection($self->{presets_choice}->GetCount-1);
}
}
}
# This is called internally whenever we make automatic adjustments to configuration
# based on user actions.
sub _load_config {
my $self = shift;
my ($config) = @_;
my $diff = $self->config->diff($config);
$self->config->set($_, $config->get($_)) for @$diff;
# First apply all changes, then call all the _on_value_change triggers.
$self->_on_value_change($_) for @$diff;
$self->reload_config;
$self->_update;
}
sub get_field {
my ($self, $opt_key, $opt_index) = @_;
foreach my $page (@{ $self->{pages} }) {
my $field = $page->get_field($opt_key, $opt_index);
return $field if defined $field;
}
return undef;
}
sub set_value {
my $self = shift;
my ($opt_key, $value) = @_;
my $changed = 0;
foreach my $page (@{ $self->{pages} }) {
$changed = 1 if $page->set_value($opt_key, $value);
}
return $changed;
}
sub _compatible_printers_widget {
my ($self) = @_;
return sub {
my ($parent) = @_;
my $checkbox = $self->{compatible_printers_checkbox} = Wx::CheckBox->new($parent, -1, "All");
my $btn = $self->{compatible_printers_btn} = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize,
wxBU_LEFT | wxBU_EXACTFIT);
$btn->SetFont($Slic3r::GUI::small_font);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("printer_empty.png"), wxBITMAP_TYPE_PNG));
}
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($checkbox, 0, wxALIGN_CENTER_VERTICAL);
$sizer->Add($btn, 0, wxALIGN_CENTER_VERTICAL);
EVT_CHECKBOX($self, $checkbox, sub {
if ($checkbox->GetValue) {
$btn->Disable;
} else {
$btn->Enable;
}
});
EVT_BUTTON($self, $btn, sub {
my @presets = map $_->name, grep !$_->default && !$_->external,
@{wxTheApp->presets->{printer}};
my $dlg = Wx::MultiChoiceDialog->new($self,
"Select the printers this profile is compatible with.",
"Compatible printers", \@presets);
my @selections = ();
foreach my $preset_name (@{ $self->config->get('compatible_printers') }) {
push @selections, first { $presets[$_] eq $preset_name } 0..$#presets;
}
$dlg->SetSelections(@selections);
if ($dlg->ShowModal == wxID_OK) {
my $value = [ @presets[$dlg->GetSelections] ];
if (!@$value) {
$checkbox->SetValue(1);
$btn->Disable;
}
$self->config->set('compatible_printers', $value);
$self->_on_value_change('compatible_printers');
}
});
return $sizer;
};
}
sub _reload_compatible_printers_widget {
my ($self) = @_;
if (@{ $self->config->get('compatible_printers') }) {
$self->{compatible_printers_checkbox}->SetValue(0);
$self->{compatible_printers_btn}->Enable;
} else {
$self->{compatible_printers_checkbox}->SetValue(1);
$self->{compatible_printers_btn}->Disable;
}
}
sub options { die "Unimplemented options()"; }
sub overridable_options { () }
sub overriding_options { () }
package Slic3r::GUI::PresetEditor::Print;
use base 'Slic3r::GUI::PresetEditor';
use List::Util qw(first any);
use Wx qw(:icon :dialog :id :misc :button :sizer);
use Wx::Event qw(EVT_BUTTON EVT_CHECKLISTBOX);
sub name { 'print' }
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
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
infill_every_layers infill_only_where_needed
solid_infill_every_layers fill_angle solid_infill_below_area
only_retract_when_crossing_perimeters infill_first
max_print_speed max_volumetric_speed
perimeter_speed small_perimeter_speed external_perimeter_speed infill_speed
solid_infill_speed top_solid_infill_speed support_material_speed
support_material_interface_speed bridge_speed gap_fill_speed
travel_speed
first_layer_speed
perimeter_acceleration infill_acceleration bridge_acceleration
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_max_layers support_material_enforce_layers
raft_layers
support_material_pattern support_material_spacing support_material_angle
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
post_process
perimeter_extruder infill_extruder solid_infill_extruder
support_material_extruder support_material_interface_extruder
ooze_prevention standby_temperature_delta
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
support_material_interface_extrusion_width infill_overlap bridge_flow_ratio
xy_size_compensation resolution shortcuts compatible_printers
print_settings_id
)
}
sub build {
my $self = shift;
my $shortcuts_widget = sub {
my ($parent) = @_;
my $Options = $Slic3r::Config::Options;
my %options = (
map { $_ => sprintf('%s > %s', $Options->{$_}{category}, $Options->{$_}{full_label} // $Options->{$_}{label}) }
grep { exists $Options->{$_} && $Options->{$_}{category} } $self->options
);
my @opt_keys = sort { $options{$a} cmp $options{$b} } keys %options;
$self->{shortcuts_opt_keys} = [ @opt_keys ];
my $listbox = $self->{shortcuts_list} = Wx::CheckListBox->new($parent, -1,
wxDefaultPosition, [-1, 320], [ map $options{$_}, @opt_keys ]);
EVT_CHECKLISTBOX($self, $listbox, sub {
my $value = [ map $opt_keys[$_], grep $listbox->IsChecked($_), 0..$#opt_keys ];
$self->config->set('shortcuts', $value);
$self->_on_value_change('shortcuts');
});
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($listbox, 0, wxEXPAND);
return $sizer;
};
{
my $page = $self->add_options_page('Layers and perimeters', 'layers.png');
{
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('spiral_vase');
}
{
my $optgroup = $page->new_optgroup('Horizontal shells');
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Solid layers',
);
$line->append_option($optgroup->get_option('top_solid_layers'));
$line->append_option($optgroup->get_option('bottom_solid_layers'));
$optgroup->append_line($line);
}
{
my $optgroup = $page->new_optgroup('Quality (slower slicing)');
$optgroup->append_single_option_line('extra_perimeters');
$optgroup->append_single_option_line('avoid_crossing_perimeters');
$optgroup->append_single_option_line('thin_walls');
$optgroup->append_single_option_line('overhangs');
}
{
my $optgroup = $page->new_optgroup('Advanced');
$optgroup->append_single_option_line('seam_position');
$optgroup->append_single_option_line('external_perimeters_first');
}
}
{
my $page = $self->add_options_page('Infill', 'infill.png');
{
my $optgroup = $page->new_optgroup('Infill');
$optgroup->append_single_option_line('fill_density');
$optgroup->append_single_option_line('fill_pattern');
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'External infill pattern',
);
$line->append_option($optgroup->get_option('top_infill_pattern'));
$line->append_option($optgroup->get_option('bottom_infill_pattern'));
$optgroup->append_line($line);
}
}
{
my $optgroup = $page->new_optgroup('Reducing printing time');
$optgroup->append_single_option_line('infill_every_layers');
$optgroup->append_single_option_line('infill_only_where_needed');
}
{
my $optgroup = $page->new_optgroup('Advanced');
$optgroup->append_single_option_line('fill_gaps');
$optgroup->append_single_option_line('solid_infill_every_layers');
$optgroup->append_single_option_line('fill_angle');
$optgroup->append_single_option_line('solid_infill_below_area');
$optgroup->append_single_option_line('only_retract_when_crossing_perimeters');
$optgroup->append_single_option_line('infill_first');
}
}
{
my $page = $self->add_options_page('Skirt and brim', 'box.png');
{
my $optgroup = $page->new_optgroup('Skirt');
$optgroup->append_single_option_line('skirts');
$optgroup->append_single_option_line('skirt_distance');
$optgroup->append_single_option_line('skirt_height');
$optgroup->append_single_option_line('min_skirt_length');
}
{
my $optgroup = $page->new_optgroup('Brim');
$optgroup->append_single_option_line('brim_width');
$optgroup->append_single_option_line('interior_brim_width');
$optgroup->append_single_option_line('brim_connections_width');
}
}
{
my $page = $self->add_options_page('Support material', 'building.png');
{
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');
}
{
my $optgroup = $page->new_optgroup('Raft');
$optgroup->append_single_option_line('raft_layers');
}
{
my $optgroup = $page->new_optgroup('Options for support material and raft');
$optgroup->append_single_option_line('support_material_contact_distance');
$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_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');
{
my $optgroup = $page->new_optgroup('Speed for print moves');
$optgroup->append_single_option_line($_, undef, width => 100)
for qw(perimeter_speed small_perimeter_speed external_perimeter_speed
infill_speed solid_infill_speed top_solid_infill_speed
gap_fill_speed bridge_speed
support_material_speed support_material_interface_speed
);
}
{
my $optgroup = $page->new_optgroup('Speed for non-print moves');
$optgroup->append_single_option_line('travel_speed');
}
{
my $optgroup = $page->new_optgroup('Modifiers');
$optgroup->append_single_option_line('first_layer_speed');
}
{
my $optgroup = $page->new_optgroup('Acceleration control (advanced)');
$optgroup->append_single_option_line('perimeter_acceleration');
$optgroup->append_single_option_line('infill_acceleration');
$optgroup->append_single_option_line('bridge_acceleration');
$optgroup->append_single_option_line('first_layer_acceleration');
$optgroup->append_single_option_line('default_acceleration');
}
{
my $optgroup = $page->new_optgroup('Autospeed (advanced)');
$optgroup->append_single_option_line('max_print_speed');
$optgroup->append_single_option_line('max_volumetric_speed');
}
}
{
my $page = $self->add_options_page('Multiple extruders', 'funnel.png');
{
my $optgroup = $page->new_optgroup('Extruders');
$optgroup->append_single_option_line('perimeter_extruder');
$optgroup->append_single_option_line('infill_extruder');
$optgroup->append_single_option_line('solid_infill_extruder');
$optgroup->append_single_option_line('support_material_extruder');
$optgroup->append_single_option_line('support_material_interface_extruder');
}
{
my $optgroup = $page->new_optgroup('Ooze prevention');
$optgroup->append_single_option_line('ooze_prevention');
$optgroup->append_single_option_line('standby_temperature_delta');
}
{
my $optgroup = $page->new_optgroup('Advanced');
$optgroup->append_single_option_line('regions_overlap');
$optgroup->append_single_option_line('interface_shells');
}
}
{
my $page = $self->add_options_page('Advanced', 'wand.png');
{
my $optgroup = $page->new_optgroup('Extrusion width',
label_width => 180,
);
$optgroup->append_single_option_line($_, undef, width => 100)
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_interface_extrusion_width
support_material_extrusion_width);
}
{
my $optgroup = $page->new_optgroup('Overlap');
$optgroup->append_single_option_line('infill_overlap');
}
{
my $optgroup = $page->new_optgroup('Flow');
$optgroup->append_single_option_line('bridge_flow_ratio');
}
{
my $optgroup = $page->new_optgroup('Other');
$optgroup->append_single_option_line('xy_size_compensation');
$optgroup->append_single_option_line('resolution');
}
}
{
my $page = $self->add_options_page('Output options', 'page_white_go.png');
{
my $optgroup = $page->new_optgroup('Sequential printing');
$optgroup->append_single_option_line('complete_objects');
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Extruder clearance (mm)',
);
foreach my $opt_key (qw(extruder_clearance_radius extruder_clearance_height)) {
my $option = $optgroup->get_option($opt_key);
$option->width(60);
$line->append_option($option);
}
$optgroup->append_line($line);
}
{
my $optgroup = $page->new_optgroup('Output file');
$optgroup->append_single_option_line('gcode_comments');
{
my $option = $optgroup->get_option('output_filename_format');
$option->full_width(1);
$optgroup->append_single_option_line($option);
}
}
{
my $optgroup = $page->new_optgroup('Post-processing scripts',
label_width => 0,
);
my $option = $optgroup->get_option('post_process');
$option->full_width(1);
$option->height(50);
$optgroup->append_single_option_line($option);
}
}
{
my $page = $self->add_options_page('Notes', 'note.png');
{
my $optgroup = $page->new_optgroup('Notes',
label_width => 0,
);
my $option = $optgroup->get_option('notes');
$option->full_width(1);
$option->height(250);
$optgroup->append_single_option_line($option);
}
}
{
my $page = $self->add_options_page('Shortcuts', 'wrench.png');
{
my $optgroup = $page->new_optgroup('Profile preferences');
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Compatible printers',
widget => $self->_compatible_printers_widget,
);
$optgroup->append_line($line);
}
}
{
my $optgroup = $page->new_optgroup('Show shortcuts for the following settings');
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
widget => $shortcuts_widget,
full_width => 1,
);
$optgroup->append_line($line);
}
}
}
}
sub reload_config {
my ($self) = @_;
$self->_reload_compatible_printers_widget;
{
my %shortcuts = map { $_ => 1 } @{ $self->config->get('shortcuts') };
for my $i (0..$#{$self->{shortcuts_opt_keys}}) {
$self->{shortcuts_list}->Check($i, $shortcuts{ $self->{shortcuts_opt_keys}[$i] });
}
}
$self->SUPER::reload_config;
}
sub _update {
my ($self, $key) = @_;
my $opt_key = $key;
$opt_key = "all_keys" if (length($key // '') == 0);
my $config = $self->{config};
if (any { /$opt_key/ } qw(all_keys spiral_vase perimeters top_solid_layers fill_density support_material)) {
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);
} 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;
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_perimeters = $config->perimeters > 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);
}
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_infill = $config->fill_density > 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);
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;
$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);
$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);
my $have_brim = $config->brim_width > 0 || $config->interior_brim_width
|| $config->brim_connections_width;
# perimeter_extruder uses the same logic as in Print::extruders()
$self->get_field('perimeter_extruder')->toggle($have_perimeters || $have_brim);
my $have_support_material = $config->support_material || $config->raft_layers > 0;
my $have_support_interface = $config->support_material_interface_layers > 0;
$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_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);
$self->get_field($_)->toggle($have_support_material && $have_support_interface)
for qw(support_material_interface_spacing support_material_interface_extruder
support_material_interface_speed);
$self->get_field('perimeter_extrusion_width')->toggle($have_perimeters || $have_skirt || $have_brim);
$self->get_field('support_material_extruder')->toggle($have_support_material || $have_skirt);
$self->get_field('support_material_speed')->toggle($have_support_material || $have_brim || $have_skirt);
my $have_sequential_printing = $config->complete_objects;
$self->get_field($_)->toggle($have_sequential_printing)
for qw(extruder_clearance_radius extruder_clearance_height);
my $have_ooze_prevention = $config->ooze_prevention;
$self->get_field($_)->toggle($have_ooze_prevention)
for qw(standby_temperature_delta);
}
package Slic3r::GUI::PresetEditor::Filament;
use base 'Slic3r::GUI::PresetEditor';
use Wx qw(wxTheApp);
sub name { 'filament' }
sub title { 'Filament Settings' }
sub options {
return qw(
filament_colour filament_diameter filament_notes filament_max_volumetric_speed extrusion_multiplier filament_density filament_cost
temperature first_layer_temperature bed_temperature first_layer_bed_temperature
fan_always_on cooling compatible_printers
min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers
fan_below_layer_time slowdown_below_layer_time min_print_speed
start_filament_gcode end_filament_gcode
filament_settings_id
);
}
sub overriding_options {
return (
Slic3r::GUI::PresetEditor::Printer->overridable_options,
);
}
sub build {
my $self = shift;
{
my $page = $self->add_options_page('Filament', 'spool.png');
{
my $optgroup = $page->new_optgroup('Filament');
$optgroup->append_single_option_line('filament_colour', 0);
$optgroup->append_single_option_line('filament_diameter', 0);
$optgroup->append_single_option_line('extrusion_multiplier', 0);
}
{
my $optgroup = $page->new_optgroup('Temperature (°C)');
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Extruder',
);
$line->append_option($optgroup->get_option('first_layer_temperature', 0));
$line->append_option($optgroup->get_option('temperature', 0));
$optgroup->append_line($line);
}
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Bed',
);
$line->append_option($optgroup->get_option('first_layer_bed_temperature'));
$line->append_option($optgroup->get_option('bed_temperature'));
$optgroup->append_line($line);
}
}
{
my $optgroup = $page->new_optgroup('Optional information');
$optgroup->append_single_option_line('filament_density', 0);
$optgroup->append_single_option_line('filament_cost', 0);
}
}
{
my $page = $self->add_options_page('Cooling', 'hourglass.png');
{
my $optgroup = $page->new_optgroup('Enable');
$optgroup->append_single_option_line('fan_always_on');
$optgroup->append_single_option_line('cooling');
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => '',
full_width => 1,
widget => sub {
my ($parent) = @_;
return $self->{description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent);
},
);
$optgroup->append_line($line);
}
{
my $optgroup = $page->new_optgroup('Fan settings');
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Fan speed',
);
$line->append_option($optgroup->get_option('min_fan_speed'));
$line->append_option($optgroup->get_option('max_fan_speed'));
$optgroup->append_line($line);
}
$optgroup->append_single_option_line('bridge_fan_speed');
$optgroup->append_single_option_line('disable_fan_first_layers');
}
{
my $optgroup = $page->new_optgroup('Cooling thresholds',
label_width => 250,
);
$optgroup->append_single_option_line('fan_below_layer_time');
$optgroup->append_single_option_line('slowdown_below_layer_time');
$optgroup->append_single_option_line('min_print_speed');
}
}
{
my $page = $self->add_options_page('Custom G-code', 'script.png');
{
my $optgroup = $page->new_optgroup('Start G-code',
label_width => 0,
);
my $option = $optgroup->get_option('start_filament_gcode', 0);
$option->full_width(1);
$option->height(150);
$optgroup->append_single_option_line($option);
}
{
my $optgroup = $page->new_optgroup('End G-code',
label_width => 0,
);
my $option = $optgroup->get_option('end_filament_gcode', 0);
$option->full_width(1);
$option->height(150);
$optgroup->append_single_option_line($option);
}
}
{
my $page = $self->add_options_page('Notes', 'note.png');
{
my $optgroup = $page->new_optgroup('Notes',
label_width => 0,
);
my $option = $optgroup->get_option('filament_notes', 0);
$option->full_width(1);
$option->height(250);
$optgroup->append_single_option_line($option);
}
}
{
my $page = $self->add_options_page('Overrides', 'wrench.png');
{
my $optgroup = $page->new_optgroup('Profile preferences');
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Compatible printers',
widget => $self->_compatible_printers_widget,
);
$optgroup->append_line($line);
}
{
my $optgroup = $page->new_optgroup('Overrides');
$optgroup->append_single_option_line('filament_max_volumetric_speed', 0);
# Populate the overrides config.
my @overridable = $self->overriding_options;
$self->{overrides_config} = Slic3r::Config->new;
# Populate the defaults with the current preset.
$self->{overrides_default_config} = Slic3r::Config->new;
$self->{overrides_default_config}->apply_only
(wxTheApp->{mainframe}->{plater}->config, \@overridable);
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => '',
full_width => 1,
widget => sub {
my ($parent) = @_;
$self->{overrides_panel} = my $panel = Slic3r::GUI::Plater::OverrideSettingsPanel->new($parent,
size => [-1, 300],
on_change => sub {
my ($opt_key) = @_;
$self->config->erase($_) for @overridable;
$self->current_preset->_dirty_config->erase($_) for @overridable;
$self->config->apply($self->{overrides_config});
$self->_on_value_change($opt_key);
});
$panel->set_editable(1);
$panel->set_default_config($self->{overrides_default_config});
$panel->set_config($self->{overrides_config});
$panel->set_opt_keys([@overridable]);
return $panel;
},
);
$optgroup->append_line($line);
}
}
}
sub reload_config {
my ($self) = @_;
$self->_reload_compatible_printers_widget;
{
$self->{overrides_config}->clear;
foreach my $opt_key (@{$self->{overrides_default_config}->get_keys}) {
if ($self->config->has($opt_key)) {
$self->{overrides_config}->set($opt_key, $self->config->get($opt_key));
}
}
$self->{overrides_panel}->update_optgroup;
}
$self->SUPER::reload_config;
}
sub _update {
my ($self) = @_;
$self->_update_description;
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)
for qw(min_fan_speed disable_fan_first_layers);
}
sub _update_description {
my $self = shift;
my $config = $self->config;
my $msg = "";
my $fan_other_layers = $config->fan_always_on
? sprintf "will always run at %d%%%s.", $config->min_fan_speed,
($config->disable_fan_first_layers > 1
? " except for the first " . $config->disable_fan_first_layers . " layers"
: $config->disable_fan_first_layers == 1
? " except for the first layer"
: "")
: "will be turned off.";
if ($config->cooling) {
$msg = sprintf "If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).",
$config->slowdown_below_layer_time, $config->max_fan_speed, $config->slowdown_below_layer_time, $config->min_print_speed;
if ($config->fan_below_layer_time > $config->slowdown_below_layer_time) {
$msg .= sprintf "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.",
$config->fan_below_layer_time, $config->max_fan_speed, $config->min_fan_speed;
}
$msg .= "\nDuring the other layers, fan $fan_other_layers"
} else {
$msg = "Fan $fan_other_layers";
}
$self->{description_line}->SetText($msg);
}
package Slic3r::GUI::PresetEditor::Printer;
use base 'Slic3r::GUI::PresetEditor';
use Wx qw(wxTheApp :sizer :button :bitmap :misc :id :icon :dialog);
use Wx::Event qw(EVT_BUTTON);
sub name { 'printer' }
sub title { 'Printer Settings' }
sub options {
return qw(
bed_shape z_offset z_steps_per_mm has_heatbed
gcode_flavor use_relative_e_distances
serial_port serial_speed
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
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
);
}
sub overridable_options {
return qw(
pressure_advance
retract_length retract_lift retract_speed retract_restart_extra
retract_before_travel retract_layer_change wipe
);
}
sub build {
my $self = shift;
$self->{extruders_count} = 1;
{
my $page = $self->add_options_page('General', 'printer_empty.png');
{
my $optgroup = $page->new_optgroup('Size and coordinates');
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Bed shape',
);
$line->append_button("Set…", "cog.png", sub {
my $dlg = Slic3r::GUI::BedShapeDialog->new($self, $self->config->bed_shape);
if ($dlg->ShowModal == wxID_OK) {
my $value = $dlg->GetValue;
$self->config->set('bed_shape', $value);
$self->_on_value_change('bed_shape');
}
});
$optgroup->append_line($line);
$optgroup->append_single_option_line('z_offset');
}
{
my $optgroup = $page->new_optgroup('Capabilities');
{
my $option = Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'extruders_count',
type => 'i',
default => 1,
label => 'Extruders',
tooltip => 'Number of extruders of the printer.',
min => 1,
);
$optgroup->append_single_option_line($option);
}
$optgroup->append_single_option_line('has_heatbed');
$optgroup->on_change(sub {
my ($opt_id) = @_;
if ($opt_id eq 'extruders_count') {
wxTheApp->CallAfter(sub {
$self->_extruders_count_changed($optgroup->get_value('extruders_count'));
});
} else {
wxTheApp->CallAfter(sub {
$self->_on_value_change($opt_id);
});
}
});
}
{
my $optgroup = $page->new_optgroup('USB/Serial connection');
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Serial port',
);
my $serial_port = $optgroup->get_option('serial_port');
$serial_port->side_widget(sub {
my ($parent) = @_;
my $btn = Wx::BitmapButton->new($parent, -1, Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE);
$btn->SetToolTipString("Rescan serial ports")
if $btn->can('SetToolTipString');
EVT_BUTTON($self, $btn, \&_update_serial_ports);
return $btn;
});
$line->append_option($serial_port);
$line->append_option($optgroup->get_option('serial_speed'));
$line->append_button("Test", "wrench.png", sub {
my $sender = Slic3r::GCode::Sender->new;
my $res = $sender->connect(
$self->config->serial_port,
$self->config->serial_speed,
);
if ($res && $sender->wait_connected) {
Slic3r::GUI::show_info($self, "Connection to printer works correctly.", "Success!");
} else {
Slic3r::GUI::show_error($self, "Connection failed.");
}
}, \$self->{serial_test_btn});
$optgroup->append_line($line);
}
{
my $optgroup = $page->new_optgroup('Print server upload');
$optgroup->append_single_option_line('host_type');
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('octoprint');
$res->discover;
$entries = [ $res->entries ];
}
if (@{$entries}) {
my $dlg = Slic3r::GUI::BonjourBrowser->new($self, $entries);
if ($dlg->ShowModal == wxID_OK) {
my $value = $dlg->GetValue . ":" . $dlg->GetPort;
$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;
}
}, \$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->print_host . "/api/version",
'X-Api-Key' => $self->config->octoprint_apikey,
);
if ($res->is_success) {
Slic3r::GUI::show_info($self, "Connection to OctoPrint works correctly.", "Success!");
} else {
Slic3r::GUI::show_error($self,
"I wasn't able to connect to OctoPrint (" . $res->status_line . "). "
. "Check hostname and OctoPrint version (at least 1.1.0 is required).");
}
}, \$self->{print_host_test_btn});
$optgroup->append_line($host_line);
$optgroup->append_single_option_line('octoprint_apikey');
}
{
my $optgroup = $page->new_optgroup('Firmware');
$optgroup->append_single_option_line('gcode_flavor');
}
{
my $optgroup = $page->new_optgroup('Advanced');
$optgroup->append_single_option_line('use_relative_e_distances');
$optgroup->append_single_option_line('use_firmware_retraction');
$optgroup->append_single_option_line('use_volumetric_e');
$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');
}
}
{
my $page = $self->add_options_page('Custom G-code', 'script.png');
{
my $optgroup = $page->new_optgroup('Start G-code',
label_width => 0,
);
my $option = $optgroup->get_option('start_gcode');
$option->full_width(1);
$option->height(150);
$optgroup->append_single_option_line($option);
}
{
my $optgroup = $page->new_optgroup('End G-code',
label_width => 0,
);
my $option = $optgroup->get_option('end_gcode');
$option->full_width(1);
$option->height(150);
$optgroup->append_single_option_line($option);
}
{
my $optgroup = $page->new_optgroup('Before layer change G-code',
label_width => 0,
);
my $option = $optgroup->get_option('before_layer_gcode');
$option->full_width(1);
$option->height(150);
$optgroup->append_single_option_line($option);
}
{
my $optgroup = $page->new_optgroup('After layer change G-code',
label_width => 0,
);
my $option = $optgroup->get_option('layer_gcode');
$option->full_width(1);
$option->height(150);
$optgroup->append_single_option_line($option);
}
{
my $optgroup = $page->new_optgroup('Tool change G-code',
label_width => 0,
);
my $option = $optgroup->get_option('toolchange_gcode');
$option->full_width(1);
$option->height(150);
$optgroup->append_single_option_line($option);
}
{
my $optgroup = $page->new_optgroup('Between objects G-code (for sequential printing)',
label_width => 0,
);
my $option = $optgroup->get_option('between_objects_gcode');
$option->full_width(1);
$option->height(150);
$optgroup->append_single_option_line($option);
}
}
$self->{extruder_pages} = [];
$self->_build_extruder_pages;
{
my $page = $self->add_options_page('Notes', 'note.png');
{
my $optgroup = $page->new_optgroup('Notes',
label_width => 0,
);
my $option = $optgroup->get_option('printer_notes');
$option->full_width(1);
$option->height(250);
$optgroup->append_single_option_line($option);
}
}
$self->_update_serial_ports;
}
sub _update_serial_ports {
my ($self) = @_;
$self->get_field('serial_port')->set_values([ wxTheApp->scan_serial_ports ]);
}
sub _extruders_count_changed {
my ($self, $extruders_count) = @_;
$self->{extruders_count} = $extruders_count;
$self->_build_extruder_pages;
$self->_on_value_change('extruders_count');
$self->_update;
}
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 {
my $self = shift;
my $default_config = Slic3r::Config::Full->new;
foreach my $extruder_idx (@{$self->{extruder_pages}} .. $self->{extruders_count}-1) {
# extend options
foreach my $opt_key ($self->_extruder_options) {
my $values = $self->config->get($opt_key);
if (!defined $values) {
$values = [ $default_config->get_at($opt_key, 0) ];
} else {
# use last extruder's settings for the new one
my $last_value = $values->[-1];
$values->[$extruder_idx] //= $last_value;
}
$self->config->set($opt_key, $values)
or die "Unable to extend $opt_key";
}
# build page
my $page = $self->{extruder_pages}[$extruder_idx] = $self->add_options_page("Extruder " . ($extruder_idx + 1), 'funnel.png');
{
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);
}
{
my $optgroup = $page->new_optgroup('Retraction');
$optgroup->append_single_option_line($_, $extruder_idx)
for qw(retract_length retract_lift);
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Only lift Z',
);
$line->append_option($optgroup->get_option('retract_lift_above', $extruder_idx));
$line->append_option($optgroup->get_option('retract_lift_below', $extruder_idx));
$optgroup->append_line($line);
}
$optgroup->append_single_option_line($_, $extruder_idx)
for qw(retract_speed retract_restart_extra retract_before_travel retract_layer_change wipe);
}
{
my $optgroup = $page->new_optgroup('Retraction when tool is disabled (advanced settings for multi-extruder setups)');
$optgroup->append_single_option_line($_, $extruder_idx)
for qw(retract_length_toolchange retract_restart_extra_toolchange);
}
}
# remove extra pages
if ($self->{extruders_count} <= $#{$self->{extruder_pages}}) {
$_->Destroy for @{$self->{extruder_pages}}[$self->{extruders_count}..$#{$self->{extruder_pages}}];
splice @{$self->{extruder_pages}}, $self->{extruders_count};
}
# remove extra config values
foreach my $opt_key ($self->_extruder_options) {
my $values = $self->config->get($opt_key);
splice @$values, $self->{extruders_count} if $self->{extruders_count} <= $#$values;
$self->config->set($opt_key, $values)
or die "Unable to truncate $opt_key";
}
# rebuild page list
@{$self->{pages}} = (
(grep $_->{title} !~ /^Extruder \d+/, @{$self->{pages}}),
@{$self->{extruder_pages}}[ 0 .. $self->{extruders_count}-1 ],
);
$self->update_tree;
}
sub _update {
my ($self) = @_;
my $config = $self->{config};
my $serial_speed = $self->get_field('serial_speed');
if ($serial_speed) {
$self->get_field('serial_speed')->toggle($config->get('serial_port'));
if ($config->get('serial_speed') && $config->get('serial_port')) {
$self->{serial_test_btn}->Enable;
} else {
$self->{serial_test_btn}->Disable;
}
}
if (($config->get('host_type') eq 'octoprint')) {
$self->{print_host_browse_btn}->Enable;
}else{
$self->{print_host_browse_btn}->Disable;
}
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);
for my $i (0 .. ($self->{extruders_count}-1)) {
my $have_retract_length = $config->get_at('retract_length', $i) > 0;
# 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_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)
for qw(retract_lift_above retract_lift_below);
# some options only apply when not using firmware retraction
$self->get_field($_, $i)->toggle($retraction && !$config->use_firmware_retraction)
for qw(retract_restart_extra wipe);
# retraction speed is also used by auto-speed pressure regulator, even when
# user enabled firmware retraction
$self->get_field('retract_speed', $i)->toggle($retraction);
if ($config->use_firmware_retraction && $config->get_at('wipe', $i)) {
my $dialog = Wx::MessageDialog->new($self,
"The Wipe 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->wipe;
$wipe->[$i] = 0;
$new_conf->set("wipe", $wipe);
} else {
$new_conf->set("use_firmware_retraction", 0);
}
$self->_load_config($new_conf);
}
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 &&
!($config->use_firmware_retraction && ($config->gcode_flavor eq 'reprap' || $config->gcode_flavor eq 'repetier')));
}
}
# this gets executed after preset is loaded and before GUI fields are updated
sub on_preset_loaded {
my $self = shift;
# update the extruders count field
{
# update the GUI field according to the number of nozzle diameters supplied
my $extruders_count = scalar @{ $self->config->nozzle_diameter };
$self->set_value('extruders_count', $extruders_count);
$self->_extruders_count_changed($extruders_count);
}
}
sub load_config_file {
my $self = shift;
$self->SUPER::load_config_file(@_);
Slic3r::GUI::warning_catcher($self)->(
"Your configuration was imported. However, Slic3r is currently only able to import settings "
. "for the first defined filament. We recommend you don't use exported configuration files "
. "for multi-extruder setups and rely on the built-in preset management system instead.")
if @{ $self->config->nozzle_diameter } > 1;
}
package Slic3r::GUI::PresetEditor::Page;
use Wx qw(wxTheApp :misc :panel :sizer);
use base 'Wx::ScrolledWindow';
sub new {
my $class = shift;
my ($parent, $title, $iconID) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
$self->{optgroups} = [];
$self->{title} = $title;
$self->{iconID} = $iconID;
$self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL);
$self->SetSizer($self->{vsizer});
# http://docs.wxwidgets.org/3.0/classwx_scrolled.html#details
$self->SetScrollRate($Slic3r::GUI::scroll_step, $Slic3r::GUI::scroll_step);
return $self;
}
sub new_optgroup {
my ($self, $title, %params) = @_;
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
parent => $self,
title => $title,
config => $self->GetParent->{config},
label_width => $params{label_width} // 200,
on_change => sub {
my ($opt_key, $value) = @_;
wxTheApp->CallAfter(sub {
$self->GetParent->_on_value_change($opt_key);
});
},
);
push @{$self->{optgroups}}, $optgroup;
$self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10);
return $optgroup;
}
sub reload_config {
my ($self) = @_;
$_->reload_config for @{$self->{optgroups}};
}
sub get_field {
my ($self, $opt_key, $opt_index) = @_;
foreach my $optgroup (@{ $self->{optgroups} }) {
my $field = $optgroup->get_fieldc($opt_key, $opt_index);
return $field if defined $field;
}
return undef;
}
sub set_value {
my $self = shift;
my ($opt_key, $value) = @_;
my $changed = 0;
foreach my $optgroup (@{$self->{optgroups}}) {
$changed = 1 if $optgroup->set_value($opt_key, $value);
}
return $changed;
}
package Slic3r::GUI::SavePresetWindow;
use Wx qw(:combobox :dialog :id :misc :sizer);
use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER);
use base 'Wx::Dialog';
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, "Save preset", wxDefaultPosition, wxDefaultSize);
my @values = @{$params{values}};
my $text = Wx::StaticText->new($self, -1, "Save profile as:", wxDefaultPosition, wxDefaultSize);
$self->{combo} = Wx::ComboBox->new($self, -1, $params{default}, wxDefaultPosition, wxDefaultSize, \@values,
wxTE_PROCESS_ENTER);
my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($text, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
$sizer->Add($self->{combo}, 0, wxEXPAND | wxLEFT | wxRIGHT, 10);
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
EVT_BUTTON($self, wxID_OK, \&accept);
EVT_TEXT_ENTER($self, $self->{combo}, \&accept);
$self->SetSizer($sizer);
$sizer->SetSizeHints($self);
return $self;
}
sub accept {
my ($self, $event) = @_;
if (($self->{chosen_name} = $self->{combo}->GetValue)) {
if ($self->{chosen_name} !~ /^[^<>:\/\\|?*\"]+$/i) {
Slic3r::GUI::show_error($self, "The supplied name is not valid; the following characters are not allowed: <>:/\|?*\"");
} elsif ($self->{chosen_name} eq '- default -') {
Slic3r::GUI::show_error($self, "The supplied name is not available.");
} else {
$self->EndModal(wxID_OK);
}
}
}
sub get_name {
my $self = shift;
return $self->{chosen_name};
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/PresetEditorDialog.pm 0000664 0000000 0000000 00000003652 13274424355 0021311 0 ustar 00root root 0000000 0000000 package Slic3r::GUI::PresetEditorDialog;
use strict;
use warnings;
use Wx qw(:dialog :id :misc :sizer :button :icon wxTheApp WXK_ESCAPE);
use Wx::Event qw(EVT_CLOSE EVT_CHAR_HOOK);
use base qw(Wx::Dialog Class::Accessor);
use utf8;
__PACKAGE__->mk_accessors(qw(preset_editor));
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new($parent, -1, "Settings", wxDefaultPosition, [900,500],
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxDIALOG_EX_METAL);
$self->preset_editor($self->preset_editor_class->new($self));
$self->SetTitle($self->preset_editor->title);
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add($self->preset_editor, 1, wxEXPAND);
$self->SetSizer($sizer);
#$sizer->SetSizeHints($self);
if (0) {
my $buttons = $self->CreateStdDialogButtonSizer(wxCLOSE);
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
}
wxTheApp->restore_window_pos($self, "preset_editor");
EVT_CLOSE($self, sub {
my (undef, $event) = @_;
# save window size
wxTheApp->save_window_pos($self, "preset_editor");
# propagate event
$event->Skip;
});
EVT_CHAR_HOOK($self, sub {
my (undef, $event) = @_;
if ($event->GetKeyCode == WXK_ESCAPE) {
$self->Close;
} else {
$event->Skip;
}
});
return $self;
}
package Slic3r::GUI::PresetEditorDialog::Printer;
use base qw(Slic3r::GUI::PresetEditorDialog);
sub preset_editor_class { "Slic3r::GUI::PresetEditor::Printer" }
package Slic3r::GUI::PresetEditorDialog::Filament;
use base qw(Slic3r::GUI::PresetEditorDialog);
sub preset_editor_class { "Slic3r::GUI::PresetEditor::Filament" }
package Slic3r::GUI::PresetEditorDialog::Print;
use base qw(Slic3r::GUI::PresetEditorDialog);
sub preset_editor_class { "Slic3r::GUI::PresetEditor::Print" }
1;
Slic3r-1.3.0/lib/Slic3r/GUI/ProgressStatusBar.pm 0000664 0000000 0000000 00000006107 13274424355 0021213 0 ustar 00root root 0000000 0000000 # Status bar at the bottom of the main screen.
package Slic3r::GUI::ProgressStatusBar;
use strict;
use warnings;
use Wx qw(:gauge :misc);
use base 'Wx::StatusBar';
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->{busy} = 0;
$self->{timer} = Wx::Timer->new($self);
$self->{prog} = Wx::Gauge->new($self, wxGA_HORIZONTAL, 100, wxDefaultPosition, wxDefaultSize);
$self->{prog}->Hide;
$self->{cancelbutton} = Wx::Button->new($self, -1, "Cancel", wxDefaultPosition, wxDefaultSize);
$self->{cancelbutton}->Hide;
$self->SetFieldsCount(3);
$self->SetStatusWidths(-1, 150, 155);
Wx::Event::EVT_TIMER($self, \&OnTimer, $self->{timer});
Wx::Event::EVT_SIZE($self, \&OnSize);
Wx::Event::EVT_BUTTON($self, $self->{cancelbutton}, sub {
$self->{cancel_cb}->();
$self->{cancelbutton}->Hide;
});
return $self;
}
sub DESTROY {
my $self = shift;
$self->{timer}->Stop if $self->{timer} && $self->{timer}->IsRunning;
}
sub OnSize {
my ($self, $event) = @_;
my %fields = (
# 0 is reserved for status text
1 => $self->{cancelbutton},
2 => $self->{prog},
);
foreach (keys %fields) {
my $rect = $self->GetFieldRect($_);
my $offset = &Wx::wxGTK ? 1 : 0; # add a cosmetic 1 pixel offset on wxGTK
my $pos = [$rect->GetX + $offset, $rect->GetY + $offset];
$fields{$_}->Move($pos);
$fields{$_}->SetSize($rect->GetWidth - $offset, $rect->GetHeight);
}
$event->Skip;
}
sub OnTimer {
my ($self, $event) = @_;
if ($self->{prog}->IsShown) {
$self->{timer}->Stop;
}
$self->{prog}->Pulse if $self->{_busy};
}
sub SetCancelCallback {
my $self = shift;
my ($cb) = @_;
$self->{cancel_cb} = $cb;
$cb ? $self->{cancelbutton}->Show : $self->{cancelbutton}->Hide;
}
sub Run {
my $self = shift;
my $rate = shift || 100;
if (!$self->{timer}->IsRunning) {
$self->{timer}->Start($rate);
}
}
sub GetProgress {
my $self = shift;
return $self->{prog}->GetValue;
}
sub SetProgress {
my $self = shift;
my ($val) = @_;
if (!$self->{prog}->IsShown) {
$self->ShowProgress(1);
}
if ($val == $self->{prog}->GetRange) {
$self->{prog}->SetValue(0);
$self->ShowProgress(0);
} else {
$self->{prog}->SetValue($val);
}
}
sub SetRange {
my $self = shift;
my ($val) = @_;
if ($val != $self->{prog}->GetRange) {
$self->{prog}->SetRange($val);
}
}
sub ShowProgress {
my $self = shift;
my ($show) = @_;
$self->{prog}->Show($show);
$self->{prog}->Pulse;
}
sub StartBusy {
my $self = shift;
my $rate = shift || 100;
$self->{_busy} = 1;
$self->ShowProgress(1);
if (!$self->{timer}->IsRunning) {
$self->{timer}->Start($rate);
}
}
sub StopBusy {
my $self = shift;
$self->{timer}->Stop;
$self->ShowProgress(0);
$self->{prog}->SetValue(0);
$self->{_busy} = 0;
}
sub IsBusy {
my $self = shift;
return $self->{_busy};
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/Projector.pm 0000664 0000000 0000000 00000102300 13274424355 0017515 0 ustar 00root root 0000000 0000000 # DLP Projector screen for the SLA (stereolitography) print process
package Slic3r::GUI::Projector;
use strict;
use warnings;
use File::Basename qw(basename dirname);
use Wx qw(:dialog :id :misc :sizer :systemsettings :bitmap :button :icon :filedialog wxTheApp);
use Wx::Event qw(EVT_BUTTON EVT_CLOSE EVT_TEXT_ENTER EVT_SPINCTRL EVT_SLIDER);
use base qw(Wx::Dialog Class::Accessor);
use utf8;
__PACKAGE__->mk_accessors(qw(config config2 manual_control_config screen controller _optgroups));
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new($parent, -1, "Projector for DLP", wxDefaultPosition, wxDefaultSize);
$self->config2({
display => 0,
show_bed => 1,
invert_y => 0,
zoom => 100,
exposure_time => 2,
bottom_exposure_time => 7,
settle_time => 1.5,
bottom_layers => 3,
z_lift => 5,
z_lift_speed => 8,
offset => [0,0],
});
$self->manual_control_config({
xy_travel_speed => 130,
z_travel_speed => 10,
temperature => '',
bed_temperature => '',
});
my $ini = eval { Slic3r::Config->read_ini("$Slic3r::GUI::datadir/DLP.ini") };
if ($ini) {
foreach my $opt_id (keys %{$ini->{_}}) {
my $value = $ini->{_}{$opt_id};
if ($opt_id eq 'offset') {
$value = [ split /,/, $value ];
}
$self->config2->{$opt_id} = $value;
}
}
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$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}->{slaconfig});
my @optgroups = ();
{
push @optgroups, my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
parent => $self,
title => 'USB/Serial connection',
config => $self->config,
label_width => 200,
);
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Serial port',
);
my $serial_port = $optgroup->get_option('serial_port');
$serial_port->side_widget(sub {
my ($parent) = @_;
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new($Slic3r::var->("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE);
$btn->SetToolTipString("Rescan serial ports")
if $btn->can('SetToolTipString');
EVT_BUTTON($self, $btn, sub {
$optgroup->get_field('serial_port')->set_values([ wxTheApp->scan_serial_ports ]);
});
return $btn;
});
$line->append_option($serial_port);
$line->append_option($optgroup->get_option('serial_speed'));
$line->append_button("Test", "wrench.png", sub {
my $sender = Slic3r::GCode::Sender->new;
my $res = $sender->connect(
$self->{config}->serial_port,
$self->{config}->serial_speed,
);
if ($res && $sender->wait_connected) {
Slic3r::GUI::show_info($self, "Connection to printer works correctly.", "Success!");
} else {
Slic3r::GUI::show_error($self, "Connection failed.");
}
}, \$self->{serial_test_btn});
$optgroup->append_line($line);
}
}
{
push @optgroups, my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
parent => $self,
title => 'G-code',
config => $self->config,
label_width => 200,
);
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
{
my $option = $optgroup->get_option('start_gcode');
$option->height(50);
$option->full_width(1);
$optgroup->append_single_option_line($option);
}
{
my $option = $optgroup->get_option('end_gcode');
$option->height(50);
$option->full_width(1);
$optgroup->append_single_option_line($option);
}
}
my $on_change = sub {
my ($opt_id, $value) = @_;
$self->config2->{$opt_id} = $value;
$self->screen->reposition;
$self->show_print_time;
my $serialized = {};
foreach my $opt_id (keys %{$self->config2}) {
my $value = $self->config2->{$opt_id};
if (ref($value) eq 'ARRAY') {
$value = join ',', @$value;
}
$serialized->{$opt_id} = $value;
}
Slic3r::Config->write_ini(
"$Slic3r::GUI::datadir/DLP.ini",
{ _ => $serialized });
};
{
push @optgroups, my $optgroup = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Projection',
on_change => $on_change,
label_width => 200,
);
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Display',
);
my @displays = 0 .. (Wx::Display::GetCount()-1);
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'display',
type => 'select',
label => 'Display',
tooltip => '',
labels => [@displays],
values => [@displays],
default => $self->config2->{display},
));
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'zoom',
type => 'percent',
label => 'Zoom %',
tooltip => '',
default => $self->config2->{zoom},
min => 0.1,
max => 100,
));
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'offset',
type => 'point',
label => 'Offset',
tooltip => '',
default => $self->config2->{offset},
));
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'invert_y',
type => 'bool',
label => 'Invert Y',
tooltip => '',
default => $self->config2->{invert_y},
));
$optgroup->append_line($line);
}
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'show_bed',
type => 'bool',
label => 'Show bed',
tooltip => '',
default => $self->config2->{show_bed},
));
}
{
push @optgroups, my $optgroup = Slic3r::GUI::OptionsGroup->new(
parent => $self,
title => 'Print',
on_change => $on_change,
label_width => 200,
);
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Time (seconds)',
);
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'bottom_exposure_time',
type => 'f',
label => 'Bottom exposure',
tooltip => '',
default => $self->config2->{bottom_exposure_time},
));
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'exposure_time',
type => 'f',
label => 'Exposure',
tooltip => '',
default => $self->config2->{exposure_time},
));
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'settle_time',
type => 'f',
label => 'Settle',
tooltip => '',
default => $self->config2->{settle_time},
));
$optgroup->append_line($line);
}
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'bottom_layers',
type => 'i',
label => 'Bottom layers',
tooltip => '',
default => $self->config2->{bottom_layers},
));
{
my $line = Slic3r::GUI::OptionsGroup::Line->new(
label => 'Z Lift',
);
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'z_lift',
type => 'f',
label => 'Distance',
sidetext => 'mm',
tooltip => '',
default => $self->config2->{z_lift},
));
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
opt_id => 'z_lift_speed',
type => 'f',
label => 'Speed',
sidetext => 'mm/s',
tooltip => '',
default => $self->config2->{z_lift_speed},
));
$optgroup->append_line($line);
}
}
$self->_optgroups([@optgroups]);
{
my $sizer1 = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($sizer1, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
{
my $btn = $self->{btn_manual_control} = Wx::Button->new($self, -1, 'Manual Control', wxDefaultPosition, wxDefaultSize);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG));
}
$sizer1->Add($btn, 0);
EVT_BUTTON($self, $btn, sub {
my $sender = Slic3r::GCode::Sender->new;
my $res = $sender->connect(
$self->config->serial_port,
$self->config->serial_speed,
);
if (!$res || !$sender->wait_connected) {
Slic3r::GUI::show_error(undef, "Connection failed. Check serial port and speed.");
return;
}
my $dlg = Slic3r::GUI::Controller::ManualControlDialog->new
($self, $self->config, $sender, $self->manual_control_config);
$dlg->ShowModal;
$sender->disconnect;
});
}
{
my $btn = $self->{btn_print} = Wx::Button->new($self, -1, 'Print', wxDefaultPosition, wxDefaultSize);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_play.png"), wxBITMAP_TYPE_PNG));
}
$sizer1->Add($btn, 0);
EVT_BUTTON($self, $btn, sub {
$self->controller->start_print;
$self->_update_buttons;
$self->_set_status('');
});
}
{
my $btn = $self->{btn_stop} = Wx::Button->new($self, -1, 'Stop/Black', wxDefaultPosition, wxDefaultSize);
if ($Slic3r::GUI::have_button_icons) {
$btn->SetBitmap(Wx::Bitmap->new($Slic3r::var->("control_stop.png"), wxBITMAP_TYPE_PNG));
}
$sizer1->Add($btn, 0);
EVT_BUTTON($self, $btn, sub {
$self->controller->stop_print;
$self->_update_buttons;
$self->_set_status('');
});
}
{
{
my $text = Wx::StaticText->new($self, -1, "Layer:", wxDefaultPosition, wxDefaultSize);
$text->SetFont($Slic3r::GUI::small_font);
$sizer1->Add($text, 0, wxEXPAND | wxLEFT, 10);
}
{
my $spin = $self->{layers_spinctrl} = Wx::SpinCtrl->new($self, -1, 0, wxDefaultPosition, [60,-1],
0, 0, 300, 0);
$sizer1->Add($spin, 0);
EVT_SPINCTRL($self, $spin, sub {
my $value = $spin->GetValue;
$self->{layers_slider}->SetValue($value);
$self->controller->project_layer($value);
$self->_update_buttons;
});
}
{
my $slider = $self->{layers_slider} = Wx::Slider->new(
$self, -1,
0, # default
0, # min
300, #Â max
wxDefaultPosition,
wxDefaultSize,
);
$sizer1->Add($slider, 1);
EVT_SLIDER($self, $slider, sub {
my $value = $slider->GetValue;
$self->{layers_spinctrl}->SetValue($value);
$self->controller->project_layer($value);
$self->_update_buttons;
});
}
}
my $sizer2 = Wx::BoxSizer->new(wxHORIZONTAL);
$sizer->Add($sizer2, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
{
$self->{status_text} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, wxDefaultSize);
$self->{status_text}->SetFont($Slic3r::GUI::small_font);
$sizer2->Add($self->{status_text}, 1 | wxEXPAND);
}
}
{
# should be wxCLOSE but it crashes on Linux, maybe it's a Wx bug
my $buttons = Wx::BoxSizer->new(wxHORIZONTAL);
{
my $btn = Wx::Button->new($self, -1, "Export SVG…");
EVT_BUTTON($self, $btn, sub {
$self->_export_svg;
});
$buttons->Add($btn, 0);
}
$buttons->AddStretchSpacer(1);
{
my $btn = Wx::Button->new($self, -1, "Close");
$btn->SetDefault;
EVT_BUTTON($self, $btn, sub {
$self->_close;
});
$buttons->Add($btn, 0);
}
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
}
EVT_CLOSE($self, sub {
$self->_close;
});
$self->SetSizer($sizer);
$sizer->SetSizeHints($self);
# reuse existing screen if any
if ($Slic3r::GUI::DLP_projection_screen) {
$self->screen($Slic3r::GUI::DLP_projection_screen);
$self->screen->config($self->config);
$self->screen->config2($self->config2);
} else {
$self->screen(Slic3r::GUI::Projector::Screen->new($parent, $self->config, $self->config2));
$Slic3r::GUI::DLP_projection_screen = $self->screen;
}
$self->screen->reposition;
$self->screen->Show;
wxTheApp->{mainframe}->Hide;
# initialize controller
$self->controller(Slic3r::GUI::Projector::Controller->new(
config => $self->config,
config2 => $self->config2,
screen => $self->screen,
on_project_layer => sub {
my ($layer_num) = @_;
$self->{layers_spinctrl}->SetValue($layer_num);
$self->{layers_slider}->SetValue($layer_num);
my $duration = $self->controller->remaining_print_time;
$self->_set_status(sprintf "Printing layer %d/%d (z = %.2f); %d minutes and %d seconds left",
$layer_num, $self->controller->_print->layer_count,
$self->controller->current_layer_height,
int($duration/60), ($duration - int($duration/60)*60)); # % truncates to integer
},
on_print_completed => sub {
$self->_update_buttons;
$self->_set_status('');
Wx::Bell();
},
));
{
my $max = $self->controller->_print->layer_count-1;
$self->{layers_spinctrl}->SetRange(0, $max);
$self->{layers_slider}->SetRange(0, $max);
}
$self->_update_buttons;
$self->show_print_time;
return $self;
}
sub _update_buttons {
my ($self) = @_;
my $is_printing = $self->controller->is_printing;
my $is_projecting = $self->controller->is_projecting;
$self->{btn_manual_control}->Show(!$is_printing);
$self->{btn_print}->Show(!$is_printing && !$is_projecting);
$self->{btn_stop}->Show($is_printing || $is_projecting);
$self->{layers_spinctrl}->Enable(!$is_printing);
$self->{layers_slider}->Enable(!$is_printing);
if ($is_printing) {
$_->disable for @{$self->_optgroups};
} else {
$_->enable for @{$self->_optgroups};
}
$self->Layout;
}
sub _export_svg {
my ($self) = @_;
my $output_file = 'print.svg';
my $dlg = Wx::FileDialog->new(
$self,
'Save SVG file as:',
wxTheApp->output_path(dirname($output_file)),
basename($output_file),
&Slic3r::GUI::FILE_WILDCARDS->{svg},
wxFD_SAVE | wxFD_OVERWRITE_PROMPT,
);
if ($dlg->ShowModal != wxID_OK) {
$dlg->Destroy;
return;
}
$output_file = Slic3r::decode_path($dlg->GetPath);
$self->controller->_print->write_svg($output_file);
}
sub _set_status {
my ($self, $status) = @_;
$self->{status_text}->SetLabel($status // '');
$self->{status_text}->Wrap($self->{status_text}->GetSize->GetWidth);
$self->{status_text}->Refresh;
$self->Layout;
}
sub show_print_time {
my ($self) = @_;
my $duration = $self->controller->print_time;
$self->_set_status(sprintf "Estimated print time: %d minutes and %d seconds - %.2f liters",
int($duration/60), ($duration - int($duration/60)*60), # % truncates to integer
$self->controller->total_resin);
}
sub _close {
my $self = shift;
# if projection screen is not on the same display as our dialog,
# ask the user whether they want to keep it open
my $keep_screen = 0;
my $display_area = Wx::Display->new($self->config2->{display})->GetGeometry;
if (!$display_area->Contains($self->GetScreenPosition)) {
my $res = Wx::MessageDialog->new($self, "Do you want to keep the black screen open?", 'Black screen', wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION)->ShowModal;
$keep_screen = ($res == wxID_YES);
}
if ($keep_screen) {
$self->screen->config(undef);
$self->screen->config2(undef);
$self->screen->Refresh;
} else {
$self->screen->Destroy;
$self->screen(undef);
$Slic3r::GUI::DLP_projection_screen = undef;
}
wxTheApp->{mainframe}->Show;
$self->EndModal(wxID_OK);
}
package Slic3r::GUI::Projector::Controller;
use Moo;
use Wx qw(wxTheApp :id :timer);
use Wx::Event qw(EVT_TIMER);
use Slic3r::Geometry qw(unscale);
use Slic3r::Print::State ':steps';
use Time::HiRes qw(gettimeofday tv_interval);
has 'config' => (is => 'ro', required => 1);
has 'config2' => (is => 'ro', required => 1);
has 'screen' => (is => 'ro', required => 1);
has 'on_project_layer' => (is => 'rw');
has 'on_print_completed' => (is => 'rw');
has 'sender' => (is => 'rw');
has 'timer' => (is => 'rw');
has 'is_printing' => (is => 'rw', default => sub { 0 });
has '_print' => (is => 'rw');
has '_heights' => (is => 'rw');
has '_layer_num' => (is => 'rw');
has '_timer_cb' => (is => 'rw');
sub BUILD {
my ($self) = @_;
Slic3r::GUI::disable_screensaver();
# init print
{
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);
# make sure layers were sliced
{
my $progress_dialog = Wx::ProgressDialog->new('Slicing…', "Processing layers…", 100, undef, 0);
$progress_dialog->Pulse;
$print->slice;
$progress_dialog->Destroy;
}
$self->_heights($print->heights);
}
# projection timer
my $timer_id = &Wx::NewId();
$self->timer(Wx::Timer->new($self->screen, $timer_id));
EVT_TIMER($self->screen, $timer_id, sub {
my $cb = $self->_timer_cb;
$self->_timer_cb(undef);
$cb->();
});
}
sub delay {
my ($self, $wait, $cb) = @_;
$self->_timer_cb($cb);
$self->timer->Start($wait * 1000, wxTIMER_ONE_SHOT);
}
sub current_layer_height {
my ($self) = @_;
return $self->_heights->[$self->_layer_num];
}
sub start_print {
my ($self) = @_;
{
$self->sender(Slic3r::GCode::Sender->new);
my $res = $self->sender->connect(
$self->config->serial_port,
$self->config->serial_speed,
);
if (!$res || !$self->sender->wait_connected) {
Slic3r::GUI::show_error(undef, "Connection failed. Check serial port and speed.");
return;
}
Slic3r::debugf "connected to " . $self->config->serial_port . "\n";
# TODO: this wait should be handled by GCodeSender
sleep 4;
# send custom start G-code
$self->sender->send($_, 1) for grep !/^;/, split /\n/, $self->config->start_gcode;
$self->sender->("G90", 1); # set absolute positioning
}
$self->is_printing(1);
# TODO: block until the G1 command has been performed
# we could do this with M400 + M115 but maybe it's not portable
$self->delay(5, sub {
# start with black
Slic3r::debugf "starting black projection\n";
$self->_layer_num(-1);
$self->screen->project_layer(undef);
$self->delay($self->config2->{settle_time}, sub {
$self->project_next_layer;
});
});
}
sub stop_print {
my ($self) = @_;
if ($self->sender) {
$self->sender->disconnect;
}
$self->is_printing(0);
$self->timer->Stop;
$self->_timer_cb(undef);
$self->screen->project_layer(undef);
}
sub print_completed {
my ($self) = @_;
# send custom end G-code
if ($self->sender) {
$self->sender->send($_, 1) for grep !/^;/, split /\n/, $self->config->end_gcode;
}
# call this before the on_print_completed callback otherwise buttons
# won't be updated correctly
$self->stop_print;
$self->on_print_completed->()
if $self->is_printing && $self->on_print_completed;
}
sub is_projecting {
my ($self) = @_;
return defined $self->screen->layer_num;
}
sub project_layer {
my ($self, $layer_num) = @_;
if (!defined $layer_num || $layer_num >= $self->_print->layer_count) {
$self->screen->project_layer(undef);
return;
}
$self->screen->project_layer($layer_num);
}
sub project_next_layer {
my ($self) = @_;
$self->_layer_num($self->_layer_num + 1);
Slic3r::debugf "projecting layer %d\n", $self->_layer_num;
if ($self->_layer_num >= $self->_print->layer_count) {
$self->print_completed;
return;
}
$self->on_project_layer->($self->_layer_num) if $self->on_project_layer;
if ($self->sender) {
my $z = $self->current_layer_height + $self->config->z_offset;
my $F = $self->config2->{z_lift_speed} * 60;
if ($self->config2->{z_lift} != 0) {
$self->sender->send(sprintf("G1 Z%.5f F%d", $z + $self->config2->{z_lift}, $F), 1);
}
$self->sender->send(sprintf("G1 Z%.5f F%d", $z, $F), 1);
}
# TODO: we should block until G1 commands have been performed, see note below
$self->delay($self->config2->{settle_time}, sub {
$self->project_layer($self->_layer_num);
# get exposure time
my $time = $self->config2->{exposure_time};
if ($self->_layer_num < $self->config2->{bottom_layers}) {
$time = $self->config2->{bottom_exposure_time};
}
$self->delay($time, sub {
$self->screen->project_layer(undef);
$self->project_next_layer;
});
});
}
sub remaining_print_time {
my ($self) = @_;
my $remaining_layers = @{$self->_heights} - $self->_layer_num;
my $remaining_bottom_layers = $self->_layer_num >= $self->config2->{bottom_layers}
? 0
: $self->config2->{bottom_layers} - $self->_layer_num;
return $remaining_bottom_layers * $self->config2->{bottom_exposure_time}
+ ($remaining_layers - $remaining_bottom_layers) * $self->config2->{exposure_time}
+ $remaining_layers * $self->config2->{settle_time};
}
sub print_time {
my ($self) = @_;
return $self->config2->{bottom_layers} * $self->config2->{bottom_exposure_time}
+ ($self->_print->layer_count - $self->config2->{bottom_layers}) * $self->config2->{exposure_time}
+ $self->_print->layer_count * $self->config2->{settle_time};
}
sub total_resin {
my ($self) = @_;
my $vol = 0; # mm^3
for my $i (0..($self->_print->layer_count-1)) {
my $lh = $self->_heights->[$i] - ($i == 0 ? 0 : $self->_heights->[$i-1]);
$vol += unscale(unscale($_->area)) * $lh for @{ $self->_print->layer_slices($i) };
}
return $vol/1000/1000; # liters
}
sub DESTROY {
my ($self) = @_;
$self->timer->Stop if $self->timer;
$self->sender->disconnect if $self->sender;
Slic3r::GUI::enable_screensaver();
}
package Slic3r::GUI::Projector::Screen;
use Wx qw(:dialog :id :misc :sizer :colour :pen :brush :font wxBG_STYLE_CUSTOM);
use Wx::Event qw(EVT_PAINT EVT_SIZE);
use base qw(Wx::Dialog Class::Accessor);
use List::Util qw(min);
use Slic3r::Geometry qw(X Y unscale scale);
use Slic3r::Geometry::Clipper qw(intersection_pl union_ex);
__PACKAGE__->mk_accessors(qw(config config2 scaling_factor bed_origin print layer_num));
sub new {
my ($class, $parent, $config, $config2) = @_;
my $self = $class->SUPER::new($parent, -1, "Projector", wxDefaultPosition, wxDefaultSize, 0);
$self->config($config);
$self->config2($config2);
$self->SetBackgroundStyle(wxBG_STYLE_CUSTOM);
EVT_SIZE($self, \&_resize);
EVT_PAINT($self, \&_repaint);
$self->_resize;
return $self;
}
sub reposition {
my ($self) = @_;
my $display = Wx::Display->new($self->config2->{display});
my $area = $display->GetGeometry;
$self->Move($area->GetPosition);
# ShowFullScreen doesn't use the right screen
#$self->ShowFullScreen($self->config2->{fullscreen});
$self->SetSize($area->GetSize);
$self->_resize;
$self->Refresh;
}
sub _resize {
my ($self) = @_;
return if !$self->config;
my ($cw, $ch) = $self->GetSizeWH;
# get bed shape polygon
my $bed_polygon = Slic3r::Polygon->new_scale(@{$self->config->bed_shape});
my $bb = $bed_polygon->bounding_box;
my $size = $bb->size;
my $center = $bb->center;
# calculate the scaling factor needed for constraining print bed area inside preview
# scaling_factor is expressed in pixel / mm
$self->scaling_factor(min($cw / unscale($size->x), $ch / unscale($size->y))); #)
# apply zoom to scaling factor
if ($self->config2->{zoom} != 0) {
# TODO: make sure min and max in the option config are enforced
$self->scaling_factor($self->scaling_factor * ($self->config2->{zoom}/100));
}
# calculate the displacement needed for centering bed on screen
$self->bed_origin([
$cw/2 - (unscale($center->x) - $self->config2->{offset}->[X]) * $self->scaling_factor,
$ch/2 - (unscale($center->y) - $self->config2->{offset}->[Y]) * $self->scaling_factor, #))
]);
$self->Refresh;
}
sub project_layer {
my ($self, $layer_num) = @_;
$self->layer_num($layer_num);
$self->Refresh;
}
sub _repaint {
my ($self) = @_;
my $dc = Wx::AutoBufferedPaintDC->new($self);
my ($cw, $ch) = $self->GetSizeWH;
return if $cw == 0; # when canvas is not rendered yet, size is 0,0
$dc->SetPen(Wx::Pen->new(wxBLACK, 1, wxSOLID));
$dc->SetBrush(Wx::Brush->new(wxBLACK, wxSOLID));
$dc->DrawRectangle(0, 0, $cw, $ch);
return if !$self->config;
# turn size into max visible coordinates
# TODO: or should we use ClientArea?
$cw--;
$ch--;
# draw bed
if ($self->config2->{show_bed}) {
$dc->SetPen(Wx::Pen->new(wxRED, 2, wxSOLID));
$dc->SetBrush(Wx::Brush->new(wxWHITE, wxTRANSPARENT));
# draw contour
my $bed_polygon = Slic3r::Polygon->new_scale(@{$self->config->bed_shape});
$dc->DrawPolygon($self->scaled_points_to_pixel($bed_polygon), 0, 0);
# draw grid
$dc->SetPen(Wx::Pen->new(wxRED, 1, wxSOLID));
{
my $bb = $bed_polygon->bounding_box;
my $step = scale 10; # 1cm grid
my @polylines = ();
for (my $x = $bb->x_min - ($bb->x_min % $step) + $step; $x < $bb->x_max; $x += $step) {
push @polylines, Slic3r::Polyline->new([$x, $bb->y_min], [$x, $bb->y_max]);
}
for (my $y = $bb->y_min - ($bb->y_min % $step) + $step; $y < $bb->y_max; $y += $step) {
push @polylines, Slic3r::Polyline->new([$bb->x_min, $y], [$bb->x_max, $y]);
}
$dc->DrawLine(map @$_, @$_)
for map $self->scaled_points_to_pixel([ @$_[0,-1] ]),
@{intersection_pl(\@polylines, [$bed_polygon])};
}
# draw axes orientation
$dc->SetPen(Wx::Pen->new(wxWHITE, 4, wxSOLID));
{
foreach my $endpoint ([10, 0], [0, 10]) {
$dc->DrawLine(
map @{$self->unscaled_point_to_pixel($_)}, [0,0], $endpoint
);
}
$dc->SetTextForeground(wxWHITE);
$dc->SetFont(Wx::Font->new(20, wxDEFAULT, wxNORMAL, wxNORMAL));
my $p = $self->unscaled_point_to_pixel([10, 0]);
$dc->DrawText("X", $p->[X], $p->[Y]);
$p = $self->unscaled_point_to_pixel([0, 10]);
$dc->DrawText("Y", $p->[X]-20, $p->[Y]-10);
}
}
# get layers at this height
# draw layers
$dc->SetPen(Wx::Pen->new(wxWHITE, 1, wxSOLID));
return if !$self->print || !defined $self->layer_num;
if ($self->print->layer_solid($self->layer_num)) {
$self->_paint_expolygon($_, $dc) for @{$self->print->layer_slices($self->layer_num)};
} else {
# perimeters first, because their "hole" is painted black
$self->_paint_expolygon($_, $dc) for
@{$self->print->layer_perimeters($self->layer_num)},
@{$self->print->layer_solid_infill($self->layer_num)};
$self->_paint_expolygon($_, $dc)
for @{union_ex($self->print->layer_infill($self->layer_num)->grow)};
}
#Â draw support material
my $sm_radius = $self->print->config->get_abs_value_over('support_material_extrusion_width', $self->print->config->layer_height)/2;
$dc->SetBrush(Wx::Brush->new(wxWHITE, wxSOLID));
foreach my $pillar (@{$self->print->sm_pillars}) {
next unless $pillar->{top_layer} >= $self->layer_num
&& $pillar->{bottom_layer} <= $self->layer_num;
my $radius = min(
$sm_radius,
($pillar->{top_layer} - $self->layer_num + 1) * $self->print->config->layer_height,
);
$dc->DrawCircle(
@{$self->scaled_points_to_pixel([$pillar->{point}])->[0]},
$radius * $self->scaling_factor,
);
}
}
sub _paint_expolygon {
my ($self, $expolygon, $dc) = @_;
my @polygons = sort { $a->contains_point($b->first_point) ? -1 : 1 } @$expolygon;
$self->_paint_polygon($_, $dc) for @polygons;
}
sub _paint_polygon {
my ($self, $polygon, $dc) = @_;
if ($polygon->is_counter_clockwise) {
$dc->SetBrush(Wx::Brush->new(wxWHITE, wxSOLID));
} else {
$dc->SetBrush(Wx::Brush->new(wxBLACK, wxSOLID));
}
$dc->DrawPolygon($self->scaled_points_to_pixel($polygon->pp), 0, 0);
}
# convert a model coordinate into a pixel coordinate
sub unscaled_point_to_pixel {
my ($self, $point) = @_;
my $zero = $self->bed_origin;
my $p = [
$point->[X] * $self->scaling_factor + $zero->[X],
$point->[Y] * $self->scaling_factor + $zero->[Y],
];
if (!$self->config2->{invert_y}) {
my $ch = $self->GetSize->GetHeight;
$p->[Y] = $ch - $p->[Y];
}
return $p;
}
sub scaled_points_to_pixel {
my ($self, $points) = @_;
return [
map $self->unscaled_point_to_pixel($_),
map Slic3r::Pointf->new_unscale(@$_),
@$points
];
}
1;
Slic3r-1.3.0/lib/Slic3r/GUI/ReloadDialog.pm 0000664 0000000 0000000 00000003727 13274424355 0020111 0 ustar 00root root 0000000 0000000 # 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;
Slic3r-1.3.0/lib/Slic3r/GUI/SLAPrintOptions.pm 0000664 0000000 0000000 00000011222 13274424355 0020560 0 ustar 00root root 0000000 0000000 package Slic3r::GUI::SLAPrintOptions;
use Wx qw(:dialog :id :misc :sizer :systemsettings wxTheApp);
use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER);
use base qw(Wx::Dialog Class::Accessor);
__PACKAGE__->mk_accessors(qw(config));
sub new {
my ($class, $parent) = @_;
my $self = $class->SUPER::new($parent, -1, "SLA/DLP Print", wxDefaultPosition, wxDefaultSize);
$self->config(Slic3r::Config::SLAPrint->new);
$self->config->apply_dynamic(wxTheApp->{mainframe}->{plater}->config);
# 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);
my $new_optgroup = sub {
my ($title) = @_;
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
parent => $self,
title => $title,
config => $self->config,
label_width => 200,
);
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
return $optgroup;
};
{
my $optgroup = $new_optgroup->('Layers');
$optgroup->append_single_option_line('layer_height');
$optgroup->append_single_option_line('first_layer_height');
}
{
my $optgroup = $new_optgroup->('Infill');
$optgroup->append_single_option_line('fill_density');
$optgroup->append_single_option_line('fill_pattern');
{
my $line = $optgroup->create_single_option_line('perimeter_extrusion_width');
$line->label('Shell thickness');
my $opt = $line->get_options->[0];
$opt->sidetext('mm');
$opt->tooltip('Thickness of the external shell (both horizontal and vertical).');
$optgroup->append_line($line);
}
{
my $line = $optgroup->create_single_option_line('infill_extrusion_width');
$line->label('Infill thickness');
my $opt = $line->get_options->[0];
$opt->sidetext('mm');
$opt->tooltip('Thickness of the infill lines.');
$optgroup->append_line($line);
}
$optgroup->append_single_option_line('fill_angle');
}
{
my $optgroup = $new_optgroup->('Raft');
$optgroup->append_single_option_line('raft_layers');
$optgroup->append_single_option_line('raft_offset');
}
{
my $optgroup = $new_optgroup->('Support Material');
$optgroup->append_single_option_line('support_material');
{
my $line = $optgroup->create_single_option_line('support_material_spacing');
$line->label('Pillars spacing');
my $opt = $line->get_options->[0];
$opt->tooltip('Max spacing between support material pillars.');
$optgroup->append_line($line);
}
{
my $line = $optgroup->create_single_option_line('support_material_extrusion_width');
$line->label('Pillars diameter');
my $opt = $line->get_options->[0];
$opt->sidetext('mm');
$opt->tooltip('Diameter of the cylindrical support pillars. 0.4mm and higher is supported.');
$optgroup->append_line($line);
}
}
my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
EVT_BUTTON($self, wxID_OK, sub { $self->_accept });
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
$self->SetSizer($sizer);
$sizer->SetSizeHints($self);
return $self;
}
sub _accept {
my $self = shift;
# validate config
eval {
die "Invalid shell thickness (must be greater than 0).\n"
if $self->config->fill_density < 100 && $self->config->perimeter_extrusion_width == 0;
die "Invalid infill thickness (must be greater than 0).\n"
if $self->config->fill_density < 100 && $self->config->infill_extrusion_width == 0;
};
if ($@) {
Slic3r::GUI::show_error($self, $@);
return;
}
wxTheApp->{mainframe}->{slaconfig}->apply_static($self->config);
$self->EndModal(wxID_OK);
$self->Close; # needed on Linux
my $projector = Slic3r::GUI::Projector->new($self->GetParent);
# this double invocation is needed for properly hiding the MainFrame
$projector->Show;
$projector->ShowModal;
# TODO: diff the new config with the selected presets and prompt the user for
# applying the changes to them.
}
1;
Slic3r-1.3.0/lib/Slic3r/Geometry.pm 0000664 0000000 0000000 00000044610 13274424355 0016726 0 ustar 00root root 0000000 0000000 package Slic3r::Geometry;
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(
PI X Y Z A B X1 Y1 X2 Y2 Z1 Z2 MIN MAX epsilon slope
line_point_belongs_to_segment points_coincide distance_between_points
normalize tan move_points_3D
point_in_polygon point_in_segment segment_in_segment
polyline_lines polygon_lines
point_along_segment polygon_segment_having_point polygon_has_subsegment
deg2rad rad2deg
rotate_points move_points
dot perp
line_intersection bounding_box bounding_box_intersect
angle3points
chained_path chained_path_from collinear scale unscale
rad2deg_dir bounding_box_center line_intersects_any douglas_peucker
polyline_remove_short_segments normal triangle_normal polygon_is_convex
scaled_epsilon bounding_box_3D size_3D size_2D
convex_hull directions_parallel directions_parallel_within
);
use constant PI => 4 * atan2(1, 1);
use constant A => 0;
use constant B => 1;
use constant X1 => 0;
use constant Y1 => 1;
use constant X2 => 2;
use constant Y2 => 3;
use constant Z1 => 4;
use constant Z2 => 5;
use constant MIN => 0;
use constant MAX => 1;
our $parallel_degrees_limit = abs(deg2rad(0.1));
sub epsilon () { 1E-4 }
sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR }
sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR }
sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR }
sub tan {
my ($angle) = @_;
return (sin $angle) / (cos $angle);
}
sub slope {
my ($line) = @_;
return undef if abs($line->[B][X] - $line->[A][X]) < epsilon; # line is vertical
return ($line->[B][Y] - $line->[A][Y]) / ($line->[B][X] - $line->[A][X]);
}
# this subroutine checks whether a given point may belong to a given
# segment given the hypothesis that it belongs to the line containing
# the segment
sub line_point_belongs_to_segment {
my ($point, $segment) = @_;
#printf " checking whether %f,%f may belong to segment %f,%f - %f,%f\n",
# @$point, map @$_, @$segment;
my @segment_extents = (
[ sort { $a <=> $b } map $_->[X], @$segment ],
[ sort { $a <=> $b } map $_->[Y], @$segment ],
);
return 0 if $point->[X] < ($segment_extents[X][0] - epsilon) || $point->[X] > ($segment_extents[X][1] + epsilon);
return 0 if $point->[Y] < ($segment_extents[Y][0] - epsilon) || $point->[Y] > ($segment_extents[Y][1] + epsilon);
return 1;
}
sub points_coincide {
my ($p1, $p2) = @_;
return 1 if abs($p2->[X] - $p1->[X]) < epsilon && abs($p2->[Y] - $p1->[Y]) < epsilon;
return 0;
}
sub distance_between_points {
my ($p1, $p2) = @_;
return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2);
}
# this will check whether a point is in a polygon regardless of polygon orientation
sub point_in_polygon {
my ($point, $polygon) = @_;
my ($x, $y) = @$point;
my $n = @$polygon;
my @x = map $_->[X], @$polygon;
my @y = map $_->[Y], @$polygon;
# Derived from the comp.graphics.algorithms FAQ,
# courtesy of Wm. Randolph Franklin
my ($i, $j);
my $side = 0; # 0 = outside; 1 = inside
for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) {
if (
# If the y is between the (y-) borders...
($y[$i] <= $y && $y < $y[$j]) || ($y[$j] <= $y && $y < $y[$i])
and
# ...the (x,y) to infinity line crosses the edge
# from the ith point to the jth point...
($x < ($x[$j] - $x[$i]) * ($y - $y[$i]) / ($y[$j] - $y[$i]) + $x[$i])
) {
$side = not $side; # Jump the fence
}
}
# if point is not in polygon, let's check whether it belongs to the contour
if (!$side && 0) {
return 1 if polygon_segment_having_point($polygon, $point);
}
return $side;
}
sub point_in_segment {
my ($point, $line) = @_;
my ($x, $y) = @$point;
my $line_p = $line->pp;
my @line_x = sort { $a <=> $b } $line_p->[A][X], $line_p->[B][X];
my @line_y = sort { $a <=> $b } $line_p->[A][Y], $line_p->[B][Y];
# check whether the point is in the segment bounding box
return 0 unless $x >= ($line_x[0] - epsilon) && $x <= ($line_x[1] + epsilon)
&& $y >= ($line_y[0] - epsilon) && $y <= ($line_y[1] + epsilon);
# if line is vertical, check whether point's X is the same as the line
if ($line_p->[A][X] == $line_p->[B][X]) {
return abs($x - $line_p->[A][X]) < epsilon ? 1 : 0;
}
# calculate the Y in line at X of the point
my $y3 = $line_p->[A][Y] + ($line_p->[B][Y] - $line_p->[A][Y])
* ($x - $line_p->[A][X]) / ($line_p->[B][X] - $line_p->[A][X]);
return abs($y3 - $y) < epsilon ? 1 : 0;
}
sub segment_in_segment {
my ($needle, $haystack) = @_;
# a segment is contained in another segment if its endpoints are contained
return point_in_segment($needle->[A], $haystack) && point_in_segment($needle->[B], $haystack);
}
sub polyline_lines {
my ($polyline) = @_;
my @points = @$polyline;
return map Slic3r::Line->new(@points[$_, $_+1]), 0 .. $#points-1;
}
sub polygon_lines {
my ($polygon) = @_;
return polyline_lines([ @$polygon, $polygon->[0] ]);
}
# given a segment $p1-$p2, get the point at $distance from $p1 along segment
sub point_along_segment {
my ($p1, $p2, $distance) = @_;
my $point = [ @$p1 ];
my $line_length = sqrt( (($p2->[X] - $p1->[X])**2) + (($p2->[Y] - $p1->[Y])**2) );
for (X, Y) {
if ($p1->[$_] != $p2->[$_]) {
$point->[$_] = $p1->[$_] + ($p2->[$_] - $p1->[$_]) * $distance / $line_length;
}
}
return Slic3r::Point->new(@$point);
}
# given a $polygon, return the (first) segment having $point
sub polygon_segment_having_point {
my ($polygon, $point) = @_;
foreach my $line (@{ $polygon->lines }) {
return $line if point_in_segment($point, $line);
}
return undef;
}
# return true if the given segment is contained in any edge of the polygon
sub polygon_has_subsegment {
my ($polygon, $segment) = @_;
foreach my $line (polygon_lines($polygon)) {
return 1 if segment_in_segment($segment, $line);
}
return 0;
}
# polygon must be simple (non complex) and ccw
sub polygon_is_convex {
my ($points) = @_;
for (my $i = 0; $i <= $#$points; $i++) {
my $angle = angle3points($points->[$i-1], $points->[$i-2], $points->[$i]);
return 0 if $angle < PI;
}
return 1;
}
sub rotate_points {
my ($radians, $center, @points) = @_;
$center //= [0,0];
return map {
[
$center->[X] + cos($radians) * ($_->[X] - $center->[X]) - sin($radians) * ($_->[Y] - $center->[Y]),
$center->[Y] + cos($radians) * ($_->[Y] - $center->[Y]) + sin($radians) * ($_->[X] - $center->[X]),
]
} @points;
}
sub move_points {
my ($shift, @points) = @_;
return map {
my @p = @$_;
Slic3r::Point->new($shift->[X] + $p[X], $shift->[Y] + $p[Y]);
} @points;
}
sub move_points_3D {
my ($shift, @points) = @_;
return map [
$shift->[X] + $_->[X],
$shift->[Y] + $_->[Y],
$shift->[Z] + $_->[Z],
], @points;
}
sub normal {
my ($line1, $line2) = @_;
return [
($line1->[Y] * $line2->[Z]) - ($line1->[Z] * $line2->[Y]),
-($line2->[Z] * $line1->[X]) + ($line2->[X] * $line1->[Z]),
($line1->[X] * $line2->[Y]) - ($line1->[Y] * $line2->[X]),
];
}
sub triangle_normal {
my ($v1, $v2, $v3) = @_;
my $u = [ map +($v2->[$_] - $v1->[$_]), (X,Y,Z) ];
my $v = [ map +($v3->[$_] - $v1->[$_]), (X,Y,Z) ];
return normal($u, $v);
}
sub normalize {
my ($line) = @_;
my $len = sqrt( ($line->[X]**2) + ($line->[Y]**2) + ($line->[Z]**2) )
or return [0, 0, 0]; # to avoid illegal division by zero
return [ map $_ / $len, @$line ];
}
# 2D dot product
sub dot {
my ($u, $v) = @_;
return $u->[X] * $v->[X] + $u->[Y] * $v->[Y];
}
# 2D perp product
sub perp {
my ($u, $v) = @_;
return $u->[X] * $v->[Y] - $u->[Y] * $v->[X];
}
sub line_intersects_any {
my ($line, $lines) = @_;
for (@$lines) {
return 1 if line_intersection($line, $_, 1);
}
return 0;
}
sub line_intersection {
my ($line1, $line2, $require_crossing) = @_;
$require_crossing ||= 0;
my $intersection = _line_intersection(map @$_, @$line1, @$line2);
return (ref $intersection && $intersection->[1] == $require_crossing)
? $intersection->[0]
: undef;
}
sub collinear {
my ($line1, $line2, $require_overlapping) = @_;
my $intersection = _line_intersection(map @$_, @$line1, @$line2);
return 0 unless !ref($intersection)
&& ($intersection eq 'parallel collinear'
|| ($intersection eq 'parallel vertical' && abs($line1->[A][X] - $line2->[A][X]) < epsilon));
if ($require_overlapping) {
my @box_a = bounding_box([ $line1->[0], $line1->[1] ]);
my @box_b = bounding_box([ $line2->[0], $line2->[1] ]);
return 0 unless bounding_box_intersect( 2, @box_a, @box_b );
}
return 1;
}
sub _line_intersection {
my ( $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3 ) = @_;
my ($x, $y); # The as-yet-undetermined intersection point.
my $dy10 = $y1 - $y0; # dyPQ, dxPQ are the coordinate differences
my $dx10 = $x1 - $x0; # between the points P and Q.
my $dy32 = $y3 - $y2;
my $dx32 = $x3 - $x2;
my $dy10z = abs( $dy10 ) < epsilon; # Is the difference $dy10 "zero"?
my $dx10z = abs( $dx10 ) < epsilon;
my $dy32z = abs( $dy32 ) < epsilon;
my $dx32z = abs( $dx32 ) < epsilon;
my $dyx10; # The slopes.
my $dyx32;
$dyx10 = $dy10 / $dx10 unless $dx10z;
$dyx32 = $dy32 / $dx32 unless $dx32z;
# Now we know all differences and the slopes;
# we can detect horizontal/vertical special cases.
# E.g., slope = 0 means a horizontal line.
unless ( defined $dyx10 or defined $dyx32 ) {
return "parallel vertical";
}
elsif ( $dy10z and not $dy32z ) { # First line horizontal.
$y = $y0;
$x = $x2 + ( $y - $y2 ) * $dx32 / $dy32;
}
elsif ( not $dy10z and $dy32z ) { # Second line horizontal.
$y = $y2;
$x = $x0 + ( $y - $y0 ) * $dx10 / $dy10;
}
elsif ( $dx10z and not $dx32z ) { # First line vertical.
$x = $x0;
$y = $y2 + $dyx32 * ( $x - $x2 );
}
elsif ( not $dx10z and $dx32z ) { # Second line vertical.
$x = $x2;
$y = $y0 + $dyx10 * ( $x - $x0 );
}
elsif ( abs( $dyx10 - $dyx32 ) < epsilon ) {
# The slopes are suspiciously close to each other.
# Either we have parallel collinear or just parallel lines.
# The bounding box checks have already weeded the cases
# "parallel horizontal" and "parallel vertical" away.
my $ya = $y0 - $dyx10 * $x0;
my $yb = $y2 - $dyx32 * $x2;
return "parallel collinear" if abs( $ya - $yb ) < epsilon;
return "parallel";
}
else {
# None of the special cases matched.
# We have a "honest" line intersection.
$x = ($y2 - $y0 + $dyx10*$x0 - $dyx32*$x2)/($dyx10 - $dyx32);
$y = $y0 + $dyx10 * ($x - $x0);
}
my $h10 = $dx10 ? ($x - $x0) / $dx10 : ($dy10 ? ($y - $y0) / $dy10 : 1);
my $h32 = $dx32 ? ($x - $x2) / $dx32 : ($dy32 ? ($y - $y2) / $dy32 : 1);
return [Slic3r::Point->new($x, $y), $h10 >= 0 && $h10 <= 1 && $h32 >= 0 && $h32 <= 1];
}
# http://paulbourke.net/geometry/lineline2d/
sub _line_intersection2 {
my ($line1, $line2) = @_;
my $denom = ($line2->[B][Y] - $line2->[A][Y]) * ($line1->[B][X] - $line1->[A][X])
- ($line2->[B][X] - $line2->[A][X]) * ($line1->[B][Y] - $line1->[A][Y]);
my $numerA = ($line2->[B][X] - $line2->[A][X]) * ($line1->[A][Y] - $line2->[A][Y])
- ($line2->[B][Y] - $line2->[A][Y]) * ($line1->[A][X] - $line2->[A][X]);
my $numerB = ($line1->[B][X] - $line1->[A][X]) * ($line1->[A][Y] - $line2->[A][Y])
- ($line1->[B][Y] - $line1->[A][Y]) * ($line1->[A][X] - $line2->[A][X]);
# are the lines coincident?
if (abs($numerA) < epsilon && abs($numerB) < epsilon && abs($denom) < epsilon) {
return Slic3r::Point->new(
($line1->[A][X] + $line1->[B][X]) / 2,
($line1->[A][Y] + $line1->[B][Y]) / 2,
);
}
# are the lines parallel?
if (abs($denom) < epsilon) {
return undef;
}
# is the intersection along the segments?
my $muA = $numerA / $denom;
my $muB = $numerB / $denom;
if ($muA < 0 || $muA > 1 || $muB < 0 || $muB > 1) {
return undef;
}
return Slic3r::Point->new(
$line1->[A][X] + $muA * ($line1->[B][X] - $line1->[A][X]),
$line1->[A][Y] + $muA * ($line1->[B][Y] - $line1->[A][Y]),
);
}
# 2D
sub bounding_box {
my ($points) = @_;
my @x = map $_->x, @$points;
my @y = map $_->y, @$points; #,,
my @bb = (undef, undef, undef, undef);
for (0..$#x) {
$bb[X1] = $x[$_] if !defined $bb[X1] || $x[$_] < $bb[X1];
$bb[X2] = $x[$_] if !defined $bb[X2] || $x[$_] > $bb[X2];
$bb[Y1] = $y[$_] if !defined $bb[Y1] || $y[$_] < $bb[Y1];
$bb[Y2] = $y[$_] if !defined $bb[Y2] || $y[$_] > $bb[Y2];
}
return @bb[X1,Y1,X2,Y2];
}
sub bounding_box_center {
my ($bounding_box) = @_;
return Slic3r::Point->new(
($bounding_box->[X2] + $bounding_box->[X1]) / 2,
($bounding_box->[Y2] + $bounding_box->[Y1]) / 2,
);
}
sub size_2D {
my @bounding_box = bounding_box(@_);
return (
($bounding_box[X2] - $bounding_box[X1]),
($bounding_box[Y2] - $bounding_box[Y1]),
);
}
# bounding_box_intersect($d, @a, @b)
# Return true if the given bounding boxes @a and @b intersect
# in $d dimensions. Used by line_intersection().
sub bounding_box_intersect {
my ( $d, @bb ) = @_; # Number of dimensions and box coordinates.
my @aa = splice( @bb, 0, 2 * $d ); # The first box.
# (@bb is the second one.)
# Must intersect in all dimensions.
for ( my $i_min = 0; $i_min < $d; $i_min++ ) {
my $i_max = $i_min + $d; # The index for the maximum.
return 0 if ( $aa[ $i_max ] + epsilon ) < $bb[ $i_min ];
return 0 if ( $bb[ $i_max ] + epsilon ) < $aa[ $i_min ];
}
return 1;
}
# 3D
sub bounding_box_3D {
my ($points) = @_;
my @extents = (map [undef, undef], X,Y,Z);
foreach my $point (@$points) {
for (X,Y,Z) {
$extents[$_][MIN] = $point->[$_] if !defined $extents[$_][MIN] || $point->[$_] < $extents[$_][MIN];
$extents[$_][MAX] = $point->[$_] if !defined $extents[$_][MAX] || $point->[$_] > $extents[$_][MAX];
}
}
return @extents;
}
sub size_3D {
my ($points) = @_;
my @extents = bounding_box_3D($points);
return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z);
}
# this assumes a CCW rotation from $p2 to $p3 around $p1
sub angle3points {
my ($p1, $p2, $p3) = @_;
# p1 is the center
my $angle = atan2($p2->[X] - $p1->[X], $p2->[Y] - $p1->[Y])
- atan2($p3->[X] - $p1->[X], $p3->[Y] - $p1->[Y]);
# we only want to return only positive angles
return $angle <= 0 ? $angle + 2*PI() : $angle;
}
sub polyline_remove_short_segments {
my ($points, $min_length, $isPolygon) = @_;
for (my $i = $isPolygon ? 0 : 1; $i < $#$points; $i++) {
if (distance_between_points($points->[$i-1], $points->[$i]) < $min_length) {
# we can remove $points->[$i]
splice @$points, $i, 1;
$i--;
}
}
}
sub douglas_peucker {
my ($points, $tolerance) = @_;
no warnings "recursion";
my $results = [];
my $dmax = 0;
my $index = 0;
for my $i (1..$#$points) {
my $d = $points->[$i]->distance_to(Slic3r::Line->new($points->[0], $points->[-1]));
if ($d > $dmax) {
$index = $i;
$dmax = $d;
}
}
if ($dmax >= $tolerance) {
my $dp1 = douglas_peucker([ @$points[0..$index] ], $tolerance);
$results = [
@$dp1[0..($#$dp1-1)],
@{douglas_peucker([ @$points[$index..$#$points] ], $tolerance)},
];
} else {
$results = [ $points->[0], $points->[-1] ];
}
return $results;
}
sub douglas_peucker2 {
my ($points, $tolerance) = @_;
my $anchor = 0;
my $floater = $#$points;
my @stack = ();
my %keep = ();
push @stack, [$anchor, $floater];
while (@stack) {
($anchor, $floater) = @{pop @stack};
# initialize line segment
my ($anchor_x, $anchor_y, $seg_len);
if (grep $points->[$floater][$_] != $points->[$anchor][$_], X, Y) {
$anchor_x = $points->[$floater][X] - $points->[$anchor][X];
$anchor_y = $points->[$floater][Y] - $points->[$anchor][Y];
$seg_len = sqrt(($anchor_x ** 2) + ($anchor_y ** 2));
# get the unit vector
$anchor_x /= $seg_len;
$anchor_y /= $seg_len;
} else {
$anchor_x = $anchor_y = $seg_len = 0;
}
# inner loop:
my $max_dist = 0;
my $farthest = $anchor + 1;
for my $i (($anchor + 1) .. $floater) {
my $dist_to_seg = 0;
# compare to anchor
my $vecX = $points->[$i][X] - $points->[$anchor][X];
my $vecY = $points->[$i][Y] - $points->[$anchor][Y];
$seg_len = sqrt(($vecX ** 2) + ($vecY ** 2));
# dot product:
my $proj = $vecX * $anchor_x + $vecY * $anchor_y;
if ($proj < 0) {
$dist_to_seg = $seg_len;
} else {
# compare to floater
$vecX = $points->[$i][X] - $points->[$floater][X];
$vecY = $points->[$i][Y] - $points->[$floater][Y];
$seg_len = sqrt(($vecX ** 2) + ($vecY ** 2));
# dot product:
$proj = $vecX * (-$anchor_x) + $vecY * (-$anchor_y);
if ($proj < 0) {
$dist_to_seg = $seg_len
} else { # calculate perpendicular distance to line (pythagorean theorem):
$dist_to_seg = sqrt(abs(($seg_len ** 2) - ($proj ** 2)));
}
if ($max_dist < $dist_to_seg) {
$max_dist = $dist_to_seg;
$farthest = $i;
}
}
}
if ($max_dist <= $tolerance) { # use line segment
$keep{$_} = 1 for $anchor, $floater;
} else {
push @stack, [$anchor, $farthest];
push @stack, [$farthest, $floater];
}
}
return [ map $points->[$_], sort keys %keep ];
}
1;
Slic3r-1.3.0/lib/Slic3r/Geometry/ 0000775 0000000 0000000 00000000000 13274424355 0016363 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/lib/Slic3r/Geometry/Clipper.pm 0000664 0000000 0000000 00000000571 13274424355 0020322 0 ustar 00root root 0000000 0000000 package Slic3r::Geometry::Clipper;
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(offset offset_ex
diff_ex diff union_ex intersection_ex JT_ROUND JT_MITER
JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex
intersection intersection_pl diff_pl union CLIPPER_OFFSET_SCALE
union_pt_chained intersection_ppl);
1;
Slic3r-1.3.0/lib/Slic3r/Layer.pm 0000664 0000000 0000000 00000001307 13274424355 0016203 0 ustar 00root root 0000000 0000000 # Extends the C++ class Slic3r::Layer.
package Slic3r::Layer;
use strict;
use warnings;
# the following two were previously generated by Moo
sub print {
my $self = shift;
return $self->object->print;
}
sub config {
my $self = shift;
return $self->object->config;
}
sub region {
my $self = shift;
my ($region_id) = @_;
while ($self->region_count <= $region_id) {
$self->add_region($self->object->print->get_region($self->region_count));
}
return $self->get_region($region_id);
}
sub regions {
my ($self) = @_;
return [ map $self->get_region($_), 0..($self->region_count-1) ];
}
package Slic3r::Layer::Support;
our @ISA = qw(Slic3r::Layer);
1;
Slic3r-1.3.0/lib/Slic3r/Line.pm 0000664 0000000 0000000 00000000557 13274424355 0016024 0 ustar 00root root 0000000 0000000 package Slic3r::Line;
use strict;
use warnings;
# a line is a two-points line
use parent 'Slic3r::Polyline';
sub intersection {
my $self = shift;
my ($line, $require_crossing) = @_;
return Slic3r::Geometry::line_intersection($self, $line, $require_crossing);
}
sub grow {
my $self = shift;
return Slic3r::Polyline->new(@$self)->grow(@_);
}
1;
Slic3r-1.3.0/lib/Slic3r/Model.pm 0000664 0000000 0000000 00000010674 13274424355 0016176 0 ustar 00root root 0000000 0000000 # extends C++ class Slic3r::Model
package Slic3r::Model;
use List::Util qw(first max any);
use Slic3r::Geometry qw(X Y Z move_points);
sub merge {
my $class = shift;
my @models = @_;
my $new_model = ref($class)
? $class
: $class->new;
$new_model->add_object($_) for map @{$_->objects}, @models;
return $new_model;
}
sub add_object {
my $self = shift;
if (@_ == 1) {
# we have a Model::Object
my ($object) = @_;
return $self->_add_object_clone($object);
} else {
my (%args) = @_;
my $new_object = $self->_add_object;
$new_object->set_name($args{name})
if defined $args{name};
$new_object->set_input_file($args{input_file})
if defined $args{input_file};
$new_object->config->apply($args{config})
if defined $args{config};
$new_object->set_layer_height_ranges($args{layer_height_ranges})
if defined $args{layer_height_ranges};
$new_object->set_origin_translation($args{origin_translation})
if defined $args{origin_translation};
return $new_object;
}
}
sub set_material {
my $self = shift;
my ($material_id, $attributes) = @_;
my $material = $self->add_material($material_id);
$material->apply($attributes // {});
return $material;
}
# Extends C++ class Slic3r::ModelMaterial
package Slic3r::Model::Material;
sub apply {
my ($self, $attributes) = @_;
$self->set_attribute($_, $attributes{$_}) for keys %$attributes;
}
# Extends C++ class Slic3r::ModelObject
package Slic3r::Model::Object;
use File::Basename qw(basename);
use List::Util qw(first sum);
use Slic3r::Geometry qw(X Y Z rad2deg);
sub add_volume {
my $self = shift;
my $new_volume;
if (@_ == 1) {
# we have a Model::Volume
my ($volume) = @_;
$new_volume = $self->_add_volume_clone($volume);
if ($volume->material_id ne '') {
# merge material attributes and config (should we rename materials in case of duplicates?)
if (my $material = $volume->object->model->get_material($volume->material_id)) {
my %attributes = %{ $material->attributes };
if ($self->model->has_material($volume->material_id)) {
%attributes = (%attributes, %{ $self->model->get_material($volume->material_id)->attributes })
}
my $new_material = $self->model->set_material($volume->material_id, {%attributes});
$new_material->config->apply($material->config);
}
}
} else {
my %args = @_;
$new_volume = $self->_add_volume($args{mesh});
$new_volume->set_name($args{name})
if defined $args{name};
$new_volume->set_material_id($args{material_id})
if defined $args{material_id};
$new_volume->set_modifier($args{modifier})
if defined $args{modifier};
$new_volume->config->apply($args{config})
if defined $args{config};
}
if ($new_volume->material_id ne '' && !defined $self->model->get_material($new_volume->material_id)) {
# TODO: this should be a trigger on Volume::material_id
$self->model->set_material($new_volume->material_id);
}
$self->invalidate_bounding_box;
return $new_volume;
}
sub add_instance {
my $self = shift;
my %params = @_;
if (@_ == 1) {
# we have a Model::Instance
my ($instance) = @_;
return $self->_add_instance_clone($instance);
} else {
my (%args) = @_;
my $new_instance = $self->_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;
}
}
1;
Slic3r-1.3.0/lib/Slic3r/Point.pm 0000664 0000000 0000000 00000001050 13274424355 0016213 0 ustar 00root root 0000000 0000000 package Slic3r::Point;
use strict;
use warnings;
sub new_scale {
my $class = shift;
return $class->new(map Slic3r::Geometry::scale($_), @_);
}
sub dump_perl {
my $self = shift;
return sprintf "[%s,%s]", @$self;
}
package Slic3r::Pointf;
use strict;
use warnings;
sub new_unscale {
my $class = shift;
return $class->new(map Slic3r::Geometry::unscale($_), @_);
}
package Slic3r::Pointf3;
use strict;
use warnings;
sub new_unscale {
my $class = shift;
return $class->new(map Slic3r::Geometry::unscale($_), @_);
}
1;
Slic3r-1.3.0/lib/Slic3r/Polygon.pm 0000664 0000000 0000000 00000001720 13274424355 0016555 0 ustar 00root root 0000000 0000000 package Slic3r::Polygon;
use strict;
use warnings;
# a polygon is a closed polyline.
use parent 'Slic3r::Polyline';
use Slic3r::Geometry qw(PI);
sub grow {
my $self = shift;
return $self->split_at_first_point->grow(@_);
}
# this method subdivides the polygon segments to that no one of them
# is longer than the length provided
sub subdivide {
my $self = shift;
my ($max_length) = @_;
my @points = @$self;
push @points, $points[0]; # append first point as this is a polygon
my @new_points = shift @points;
while (@points) {
while ($new_points[-1]->distance_to($points[0]) > $max_length) {
push @new_points, map Slic3r::Point->new(@$_),
Slic3r::Geometry::point_along_segment($new_points[-1], $points[0], $max_length);
}
push @new_points, shift @points;
}
pop @new_points; # remove last point as it coincides with first one
return Slic3r::Polygon->new(@new_points);
}
1; Slic3r-1.3.0/lib/Slic3r/Polyline.pm 0000664 0000000 0000000 00000000643 13274424355 0016724 0 ustar 00root root 0000000 0000000 package Slic3r::Polyline;
use strict;
use warnings;
use Slic3r::Geometry qw(X Y);
sub new_scale {
my $class = shift;
my @points = map { ref($_) eq 'Slic3r::Point' ? $_->pp : $_ } @_;
return $class->new(map [ Slic3r::Geometry::scale($_->[X]), Slic3r::Geometry::scale($_->[Y]) ], @points);
}
sub dump_perl {
my $self = shift;
return sprintf "[%s]", join ',', map "[$_->[0],$_->[1]]", @$self;
}
1;
Slic3r-1.3.0/lib/Slic3r/Print.pm 0000664 0000000 0000000 00000036540 13274424355 0016232 0 ustar 00root root 0000000 0000000 # The slicing work horse.
# Extends C++ class Slic3r::Print
package Slic3r::Print;
use strict;
use warnings;
use File::Basename qw(basename fileparse);
use File::Spec;
use List::Util qw(min max first sum);
use Slic3r::ExtrusionLoop ':roles';
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale convex_hull);
use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex intersection offset
offset2 union union_pt_chained JT_ROUND JT_SQUARE diff_pl);
use Slic3r::Print::State ':steps';
use Slic3r::Surface qw(S_TYPE_BOTTOM);
our $status_cb;
sub set_status_cb {
my ($class, $cb) = @_;
$status_cb = $cb;
}
sub status_cb {
return $status_cb // sub {};
}
# this value is not supposed to be compared with $layer->id
# since they have different semantics
sub total_layer_count {
my $self = shift;
return max(map $_->total_layer_count, @{$self->objects});
}
sub size {
my $self = shift;
return $self->bounding_box->size;
}
sub process {
my ($self) = @_;
### No need to call this as we call it as part of prepare_infill()
### until we fix the idempotency issue.
###$self->status_cb->(20, "Generating perimeters");
###$_->make_perimeters for @{$self->objects};
$self->status_cb->(70, "Infilling layers");
$_->infill for @{$self->objects};
$_->generate_support_material for @{$self->objects};
$self->make_skirt;
$self->make_brim; # must come after make_skirt
# time to make some statistics
if (0) {
eval "use Devel::Size";
print "MEMORY USAGE:\n";
printf " meshes = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->meshes), @{$self->objects})/1024/1024;
printf " layer slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->layers}, @{$self->objects})/1024/1024;
printf " region slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
printf " perimeters = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->perimeters), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
printf " fills = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->fills), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
printf " print object = %.1fMb\n", Devel::Size::total_size($self)/1024/1024;
}
if (0) {
eval "use Slic3r::Test::SectionCut";
Slic3r::Test::SectionCut->new(print => $self)->export_svg("section_cut.svg");
}
}
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 = @_;
# prerequisites
$self->process;
# output everything to a G-code file
my $output_file = $self->output_filepath($params{output_file} // '');
$self->status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : ""));
{
# open output gcode file if we weren't supplied a file-handle
my ($fh, $tempfile);
if ($params{output_fh}) {
$fh = $params{output_fh};
} else {
$tempfile = "$output_file.tmp";
Slic3r::open(\$fh, ">", $tempfile)
or die "Failed to open $tempfile for writing\n";
# enable UTF-8 output since user might have entered Unicode characters in fields like notes
binmode $fh, ':utf8';
}
Slic3r::Print::GCode->new(
print => $self,
fh => $fh,
)->export;
# close our gcode file
close $fh;
if ($tempfile) {
my $renamed = 0;
for my $i (1..5) {
last if $renamed = rename Slic3r::encode_path($tempfile), Slic3r::encode_path($output_file);
# Wait for 1/4 seconds and try to rename once again.
select(undef, undef, undef, 0.25);
}
Slic3r::debugf "Failed to remove the output G-code file from $tempfile to $output_file. Is $tempfile locked?\n"
if !$renamed;
}
}
# run post-processing scripts
if (@{$self->config->post_process}) {
$self->status_cb->(95, "Running post-processing scripts");
$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 $executable) : !(-x $executable)) {
die "The configured post-processing script is not executable: check permissions or escape whitespace/exclamation points. ($executable) \n";
}
system($executable, @parsed_script);
}
}
}
# Export SVG slices for the offline SLA printing.
sub export_svg {
my $self = shift;
my %params = @_;
$_->slice for @{$self->objects};
my $fh = $params{output_fh};
if (!$fh) {
my $output_file = $self->output_filepath($params{output_file});
$output_file =~ s/\.gcode$/.svg/i;
Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n";
print "Exporting to $output_file..." unless $params{quiet};
}
my $print_bb = $self->bounding_box;
my $print_size = $print_bb->size;
print $fh sprintf <<"EOF", unscale($print_size->[X]), unscale($print_size->[Y]);
\n";
close $fh;
print "Done.\n" unless $params{quiet};
}
sub make_skirt {
my $self = shift;
# prerequisites
$_->make_perimeters for @{$self->objects};
$_->infill for @{$self->objects};
$_->generate_support_material for @{$self->objects};
return if $self->step_done(STEP_SKIRT);
$self->set_step_started(STEP_SKIRT);
# since this method must be idempotent, we clear skirt paths *before*
# checking whether we need to generate them
$self->skirt->clear;
if (!$self->has_skirt) {
$self->set_step_done(STEP_SKIRT);
return;
}
$self->status_cb->(88, "Generating skirt");
# First off we need to decide how tall the skirt must be.
# The skirt_height option from config is expressed in layers, but our
# object might have different layer heights, so we need to find the print_z
# of the highest layer involved.
# Note that unless has_infinite_skirt() == true
# the actual skirt might not reach this $skirt_height_z value since the print
# order of objects on each layer is not guaranteed and will not generally
# include the thickest object first. It is just guaranteed that a skirt is
# prepended to the first 'n' layers (with 'n' = skirt_height).
# $skirt_height_z in this case is the highest possible skirt height for safety.
my $skirt_height_z = -1;
foreach my $object (@{$self->objects}) {
my $skirt_height = $self->has_infinite_skirt
? $object->layer_count
: min($self->config->skirt_height, $object->layer_count);
my $highest_layer = $object->get_layer($skirt_height - 1);
$skirt_height_z = max($skirt_height_z, $highest_layer->print_z);
}
# collect points from all layers contained in skirt height
my @points = ();
foreach my $object (@{$self->objects}) {
my @object_points = ();
# get object layers up to $skirt_height_z
foreach my $layer (@{$object->layers}) {
last if $layer->print_z > $skirt_height_z;
push @object_points, map @$_, map @$_, @{$layer->slices};
}
# get support layers up to $skirt_height_z
foreach my $layer (@{$object->support_layers}) {
last if $layer->print_z > $skirt_height_z;
push @object_points, map @{$_->polyline}, @{$layer->support_fills} if $layer->support_fills;
push @object_points, map @{$_->polyline}, @{$layer->support_interface_fills} if $layer->support_interface_fills;
}
# repeat points for each object copy
foreach my $copy (@{$object->_shifted_copies}) {
my @copy_points = map $_->clone, @object_points;
$_->translate(@$copy) for @copy_points;
push @points, @copy_points;
}
}
return if @points < 3; # at least three points required for a convex hull
# find out convex hull
my $convex_hull = convex_hull(\@points);
my @extruded_length = (); # for each extruder
# skirt may be printed on several layers, having distinct layer heights,
# but loops must be aligned so can't vary width/spacing
# TODO: use each extruder's own flow
my $first_layer_height = $self->skirt_first_layer_height;
my $flow = $self->skirt_flow;
my $spacing = $flow->spacing;
my $mm3_per_mm = $flow->mm3_per_mm;
my @extruders_e_per_mm = ();
my $extruder_idx = 0;
my $skirts = $self->config->skirts;
$skirts ||= 1 if $self->has_infinite_skirt;
# draw outlines from outside to inside
# loop while we have less skirts than required or any extruder hasn't reached the min length if any
my $distance = scale max($self->config->skirt_distance, $self->config->brim_width);
for (my $i = $skirts; $i > 0; $i--) {
$distance += scale $spacing;
my $loop = offset([$convex_hull], $distance, 1, JT_ROUND, scale(0.1))->[0];
my $eloop = Slic3r::ExtrusionLoop->new_from_paths(
Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polygon->new(@$loop)->split_at_first_point,
role => EXTR_ROLE_SKIRT,
mm3_per_mm => $mm3_per_mm, # this will be overridden at G-code export time
width => $flow->width,
height => $first_layer_height, # this will be overridden at G-code export time
),
);
$eloop->role(EXTRL_ROLE_SKIRT);
$self->skirt->append($eloop);
if ($self->config->min_skirt_length > 0) {
$extruded_length[$extruder_idx] ||= 0;
if (!$extruders_e_per_mm[$extruder_idx]) {
my $config = Slic3r::Config::GCode->new;
$config->apply_static($self->config);
my $extruder = Slic3r::Extruder->new($extruder_idx, $config);
$extruders_e_per_mm[$extruder_idx] = $extruder->e_per_mm($mm3_per_mm);
}
$extruded_length[$extruder_idx] += unscale $loop->length * $extruders_e_per_mm[$extruder_idx];
$i++ if defined first { ($extruded_length[$_] // 0) < $self->config->min_skirt_length } 0 .. $#{$self->extruders};
if ($extruded_length[$extruder_idx] >= $self->config->min_skirt_length) {
if ($extruder_idx < $#{$self->extruders}) {
$extruder_idx++;
next;
}
}
}
}
$self->skirt->reverse;
$self->set_step_done(STEP_SKIRT);
}
sub make_brim {
my $self = shift;
# prerequisites
$_->make_perimeters for @{$self->objects};
$_->infill for @{$self->objects};
$_->generate_support_material for @{$self->objects};
$self->make_skirt;
$self->status_cb->(88, "Generating brim");
$self->_make_brim;
}
# Wrapper around the C++ Slic3r::Print::validate()
# to produce a Perl exception without a hang-up on some Strawberry perls.
sub validate
{
my $self = shift;
my $err = $self->_validate;
die $err . "\n" if (defined($err) && $err ne '');
}
1;
Slic3r-1.3.0/lib/Slic3r/Print/ 0000775 0000000 0000000 00000000000 13274424355 0015664 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/lib/Slic3r/Print/GCode.pm 0000664 0000000 0000000 00000111312 13274424355 0017202 0 ustar 00root root 0000000 0000000 package Slic3r::Print::GCode;
use Moo;
has 'print' => (is => 'ro', required => 1, handles => [qw(objects placeholder_parser config)]);
has 'fh' => (is => 'ro', required => 1);
has '_gcodegen' => (is => 'rw');
has '_cooling_buffer' => (is => 'rw');
has '_spiral_vase' => (is => 'rw');
has '_vibration_limit' => (is => 'rw');
has '_arc_fitting' => (is => 'rw');
has '_pressure_regulator' => (is => 'rw');
has '_skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1
has '_brim_done' => (is => 'rw');
has '_second_layer_things_done' => (is => 'rw');
has '_last_obj_copy' => (is => 'rw');
has '_autospeed' => (is => 'rw', default => sub { 0 }); # boolean
use List::Util qw(first sum min max);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(X Y scale unscale chained_path convex_hull);
use Slic3r::Geometry::Clipper qw(JT_SQUARE union_ex offset);
sub BUILD {
my ($self) = @_;
{
# estimate the total number of layer changes
# TODO: only do this when M73 is enabled
my $layer_count;
if ($self->config->complete_objects) {
$layer_count = sum(map { $_->total_layer_count * @{$_->copies} } @{$self->objects});
} else {
# if sequential printing is not enable, all copies of the same object share the same layer change command(s)
$layer_count = sum(map { $_->total_layer_count } @{$self->objects});
}
# set up our helper object: This is a C++ Slic3r::GCode instance.
my $gcodegen = Slic3r::GCode->new;
$self->_gcodegen($gcodegen);
$gcodegen->set_placeholder_parser($self->placeholder_parser);
$gcodegen->set_layer_count($layer_count);
$gcodegen->set_enable_cooling_markers(1);
$gcodegen->apply_print_config($self->config);
$gcodegen->set_extruders($self->print->extruders);
}
$self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new($self->_gcodegen));
$self->_spiral_vase(Slic3r::GCode::SpiralVase->new($self->config))
if $self->config->spiral_vase;
$self->_vibration_limit(Slic3r::GCode::VibrationLimit->new(config => $self->config))
if $self->config->vibration_limit != 0;
$self->_arc_fitting(Slic3r::GCode::ArcFitting->new(config => $self->config))
if $self->config->gcode_arcs;
$self->_pressure_regulator(Slic3r::GCode::PressureRegulator->new(config => $self->config))
if $self->config->pressure_advance > 0;
}
# Export a G-code for the complete print.
sub export {
my ($self) = @_;
my $fh = $self->fh;
my $gcodegen = $self->_gcodegen;
# Write information on the generator.
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 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;
for my $region_id (0..$#{$self->print->regions}) {
my $region = $self->print->regions->[$region_id];
{
my $flow = $region->flow(FLOW_ROLE_EXTERNAL_PERIMETER, $layer_height, 0, 0, -1, $first_object);
my $vol_speed = $flow->mm3_per_mm * $region->config->get_abs_value('external_perimeter_speed');
$vol_speed = min($vol_speed, $self->config->max_volumetric_speed) if $self->config->max_volumetric_speed > 0;
printf $fh "; external perimeters extrusion width = %.2fmm (%.2fmm^3/s)\n",
$flow->width, $vol_speed;
}
{
my $flow = $region->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 0, -1, $first_object);
my $vol_speed = $flow->mm3_per_mm * $region->config->get_abs_value('perimeter_speed');
$vol_speed = min($vol_speed, $self->config->max_volumetric_speed) if $self->config->max_volumetric_speed > 0;
printf $fh "; perimeters extrusion width = %.2fmm (%.2fmm^3/s)\n",
$flow->width, $vol_speed;
}
{
my $flow = $region->flow(FLOW_ROLE_INFILL, $layer_height, 0, 0, -1, $first_object);
my $vol_speed = $flow->mm3_per_mm * $region->config->get_abs_value('infill_speed');
$vol_speed = min($vol_speed, $self->config->max_volumetric_speed) if $self->config->max_volumetric_speed > 0;
printf $fh "; infill extrusion width = %.2fmm (%.2fmm^3/s)\n",
$flow->width, $vol_speed;
}
{
my $flow = $region->flow(FLOW_ROLE_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object);
my $vol_speed = $flow->mm3_per_mm * $region->config->get_abs_value('solid_infill_speed');
$vol_speed = min($vol_speed, $self->config->max_volumetric_speed) if $self->config->max_volumetric_speed > 0;
printf $fh "; solid infill extrusion width = %.2fmm (%.2fmm^3/s)\n",
$flow->width, $vol_speed;
}
{
my $flow = $region->flow(FLOW_ROLE_TOP_SOLID_INFILL, $layer_height, 0, 0, -1, $first_object);
my $vol_speed = $flow->mm3_per_mm * $region->config->get_abs_value('top_solid_infill_speed');
$vol_speed = min($vol_speed, $self->config->max_volumetric_speed) if $self->config->max_volumetric_speed > 0;
printf $fh "; top infill extrusion width = %.2fmm (%.2fmm^3/s)\n",
$flow->width, $vol_speed;
}
if ($self->print->has_support_material) {
my $object0 = $self->objects->[0];
my $flow = $object0->support_material_flow;
my $vol_speed = $flow->mm3_per_mm * $object0->config->get_abs_value('support_material_speed');
$vol_speed = min($vol_speed, $self->config->max_volumetric_speed) if $self->config->max_volumetric_speed > 0;
printf $fh "; support material extrusion width = %.2fmm (%.2fmm^3/s)\n",
$flow->width, $vol_speed;
}
printf $fh "; first layer extrusion width = %.2fmm (%.2fmm^3/s)\n",
$region->flow(FLOW_ROLE_PERIMETER, $layer_height, 0, 1, -1, $self->objects->[0])->width
if $region->config->first_layer_extrusion_width;
print $fh "\n";
}
#Â prepare the helper object for replacing placeholders in custom G-code and output filename
$self->placeholder_parser->update_timestamp;
# GCode sets this automatically whenever we call change_layer(),
# but we need it for skirt/brim too
$gcodegen->set_first_layer(1);
# disable fan
print $fh $gcodegen->writer->set_fan(0, 1)
if $self->config->cooling && $self->config->disable_fan_first_layers;
# set bed temperature
if ($self->config->has_heatbed && (my $temp = $self->config->first_layer_bed_temperature) && $self->config->start_gcode !~ /M(?:190|140)/i) {
printf $fh $gcodegen->writer->set_bed_temperature($temp, 1);
}
# set extruder(s) temperature before and after start G-code
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
$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 $include_start_extruder_temp;
# set other general things
print $fh $gcodegen->preamble;
# initialize a motion planner for object-to-object travel moves
if ($self->config->avoid_crossing_perimeters) {
my $distance_from_objects = scale 1;
# compute the offsetted convex hull for each object and repeat it for each copy.
my @islands_p = ();
foreach my $object (@{$self->objects}) {
# discard objects only containing thin walls (offset would fail on an empty polygon)
my @polygons = map $_->contour, map @{$_->slices}, @{$object->layers};
next if !@polygons;
# translate convex hull for each object copy and append it to the islands array
foreach my $copy (@{ $object->_shifted_copies }) {
my @copy_islands_p = map $_->clone, @polygons;
$_->translate(@$copy) for @copy_islands_p;
push @islands_p, @copy_islands_p;
}
}
$gcodegen->avoid_crossing_perimeters->init_external_mp(union_ex(\@islands_p));
}
# calculate wiping points if needed
if ($self->config->ooze_prevention && (my @extruders = @{$self->print->extruders}) > 1) {
my @skirt_points = map @$_, map @$_, @{$self->print->skirt};
if (@skirt_points) {
my $outer_skirt = convex_hull(\@skirt_points);
my @skirts = ();
foreach my $extruder_id (@extruders) {
my $extruder_offset = $self->config->get_at('extruder_offset', $extruder_id);
push @skirts, my $s = $outer_skirt->clone;
$s->translate(-scale($extruder_offset->x), -scale($extruder_offset->y)); #)
}
my $convex_hull = convex_hull([ map @$_, @skirts ]);
$gcodegen->ooze_prevention->set_enable(1);
$gcodegen->ooze_prevention->set_standby_points(
[ map @{$_->equally_spaced_points(scale 10)}, @{offset([$convex_hull], scale 3)} ]
);
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"ooze_prevention.svg",
red_polygons => \@skirts,
polygons => [$outer_skirt],
points => $gcodegen->ooze_prevention->standby_points,
);
}
}
}
# set initial extruder only after custom start G-code
print $fh $gcodegen->set_extruder($self->print->extruders->[0]);
# do all objects for each layer
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]->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) {
my $object = $self->objects->[$obj_idx];
for my $copy (@{ $self->objects->[$obj_idx]->_shifted_copies }) {
# move to the origin position for the copy we're going to print.
# this happens before Z goes down to layer 0 again, so that
# no collision happens hopefully.
if ($finished_objects > 0) {
$gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
$gcodegen->set_enable_cooling_markers(0); # we're not filtering these moves through CoolingBuffer
$gcodegen->avoid_crossing_perimeters->set_use_external_mp_once(1);
print $fh $gcodegen->retract;
print $fh $gcodegen->travel_to(
Slic3r::Point->new(0,0),
EXTR_ROLE_NONE,
'move to origin position for next object',
);
$gcodegen->set_enable_cooling_markers(1);
# disable motion planner when traveling to first object point
$gcodegen->avoid_crossing_perimeters->set_disable_once(1);
}
my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers};
for my $layer (@layers) {
# if we are printing the bottom layer of an object, and we have already finished
# another one, set first layer temperatures. this happens before the Z move
# is triggered, so machine has more time to reach such temperatures
if ($layer->id == 0 && $finished_objects > 0) {
printf $fh $gcodegen->writer->set_bed_temperature($self->config->first_layer_bed_temperature),
if $self->config->first_layer_bed_temperature
&& $self->config->has_heatbed
&& $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", Slic3r::ConditionalGCode::apply_math($gcodegen->placeholder_parser->process($self->config->between_objects_gcode));
}
$self->process_layer($layer, [$copy]);
}
$self->flush_filters;
$finished_objects++;
$self->_second_layer_things_done(0);
}
}
} else {
# order objects using a nearest neighbor search
my @obj_idx = @{chained_path([ map Slic3r::Point->new(@{$_->_shifted_copies->[0]}), @{$self->objects} ])};
# sort layers by Z
my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx
foreach my $obj_idx (0 .. ($self->print->object_count - 1)) {
my $object = $self->objects->[$obj_idx];
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
$layers{ $layer->print_z } ||= [];
$layers{ $layer->print_z }[$obj_idx] ||= [];
push @{$layers{ $layer->print_z }[$obj_idx]}, $layer;
}
}
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->flush_filters;
}
# write end commands to file
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", Slic3r::ConditionalGCode::apply_math($gcodegen->placeholder_parser->process($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;
# get filament stats
$self->print->clear_filament_stats;
$self->print->total_used_filament(0);
$self->print->total_extruded_volume(0);
$self->print->total_weight(0);
$self->print->total_cost(0);
foreach my $extruder (@{$gcodegen->writer->extruders}) {
my $used_filament = $extruder->used_filament;
my $extruded_volume = $extruder->extruded_volume;
my $filament_weight = $extruded_volume * $extruder->filament_density / 1000;
my $filament_cost = $filament_weight * ($extruder->filament_cost / 1000);
$self->print->set_filament_stats($extruder->id, $used_filament);
printf $fh "; filament used = %.1fmm (%.1fcm3)\n",
$used_filament, $extruded_volume/1000;
if ($filament_weight > 0) {
$self->print->total_weight($self->print->total_weight + $filament_weight);
printf $fh "; filament used = %.1fg\n",
$filament_weight;
if ($filament_cost > 0) {
$self->print->total_cost($self->print->total_cost + $filament_cost);
printf $fh "; filament cost = %.1f\n",
$filament_cost;
}
}
$self->print->total_used_filament($self->print->total_used_filament + $used_filament);
$self->print->total_extruded_volume($self->print->total_extruded_volume + $extruded_volume);
}
printf $fh "; total filament cost = %.1f\n",
$self->print->total_cost;
# append full config
print $fh "\n";
foreach my $config ($self->print->config, $self->print->default_object_config, $self->print->default_region_config) {
foreach my $opt_key (sort @{$config->get_keys}) {
next if $Slic3r::Config::Options->{$opt_key}{shortcut};
printf $fh "; %s = %s\n", $opt_key, $config->serialize($opt_key);
}
}
}
sub _print_first_layer_temperature {
my ($self, $wait) = @_;
for my $t (@{$self->print->extruders}) {
my $temp = $self->config->get_at('first_layer_temperature', $t);
$temp += $self->config->standby_temperature_delta if $self->config->ooze_prevention;
printf {$self->fh} $self->_gcodegen->writer->set_temperature($temp, $wait, $t) if $temp > 0;
}
}
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 $gcode = "";
my $object = $layer->object;
$self->_gcodegen->config->apply_static($object->config);
# check whether we're going to apply spiralvase logic
if (defined $self->_spiral_vase) {
$self->_spiral_vase->set_enable(
$layer->id > 0
&& ($self->print->config->skirts == 0
|| ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt))
&& !defined(first { $_->region->config->bottom_solid_layers > $layer->id } @{$layer->regions})
&& !defined(first { $_->perimeters->items_count > 1 } @{$layer->regions})
&& !defined(first { $_->fills->items_count > 0 } @{$layer->regions})
);
}
# if we're going to apply spiralvase to this layer, disable loop clipping
$self->_gcodegen->set_enable_loop_clipping(!defined $self->_spiral_vase || !$self->_spiral_vase->enable);
# initialize autospeed
{
# get the minimum cross-section used in the layer
my @mm3_per_mm = ();
foreach my $region_id (0..$#{$self->print->regions}) {
my $region = $self->print->get_region($region_id);
my $layerm = $layer->region($region_id);
if ($region->config->get_abs_value('perimeter_speed') == 0
|| $region->config->get_abs_value('small_perimeter_speed') == 0
|| $region->config->get_abs_value('external_perimeter_speed') == 0
|| $region->config->get_abs_value('bridge_speed') == 0) {
push @mm3_per_mm, $layerm->perimeters->min_mm3_per_mm;
}
if ($region->config->get_abs_value('infill_speed') == 0
|| $region->config->get_abs_value('solid_infill_speed') == 0
|| $region->config->get_abs_value('top_solid_infill_speed') == 0
|| $region->config->get_abs_value('bridge_speed') == 0
|| $region->config->get_abs_value('gap_fill_speed') == 0) {
push @mm3_per_mm, $layerm->fills->min_mm3_per_mm;
}
}
if ($layer->isa('Slic3r::Layer::Support')) {
if ($object->config->get_abs_value('support_material_speed') == 0
|| $object->config->get_abs_value('support_material_interface_speed') == 0) {
push @mm3_per_mm, $layer->support_fills->min_mm3_per_mm;
push @mm3_per_mm, $layer->support_interface_fills->min_mm3_per_mm;
}
}
# ignore too thin segments
@mm3_per_mm = grep $_ > 0.01, @mm3_per_mm;
if (@mm3_per_mm) {
my $min_mm3_per_mm = min(@mm3_per_mm);
# In order to honor max_print_speed we need to find a target volumetric
#Â speed that we can use throughout the print. So we define this target
# volumetric speed as the volumetric speed produced by printing the
# smallest cross-section at the maximum speed: any larger cross-section
# will need slower feedrates.
my $volumetric_speed = $min_mm3_per_mm * $self->config->max_print_speed;
# limit such volumetric speed with max_volumetric_speed if set
if ($self->config->max_volumetric_speed > 0) {
$volumetric_speed = min(
$volumetric_speed,
$self->config->max_volumetric_speed,
);
}
$self->_gcodegen->set_volumetric_speed($volumetric_speed);
}
}
if (!$self->_second_layer_things_done && $layer->id == 1) {
for my $extruder (@{$self->_gcodegen->writer->extruders}) {
my $temperature = $self->config->get_at('temperature', $extruder->id);
$gcode .= $self->_gcodegen->writer->set_temperature($temperature, 0, $extruder->id)
if $temperature && $temperature != $self->config->get_at('first_layer_temperature', $extruder->id);
}
$gcode .= $self->_gcodegen->writer->set_bed_temperature($self->print->config->bed_temperature)
if $self->config->has_heatbed && $self->print->config->first_layer_bed_temperature && $self->print->config->bed_temperature != $self->print->config->first_layer_bed_temperature;
$self->_second_layer_things_done(1);
}
# set new layer - this will change Z and force a retraction if retract_layer_change is enabled
if ($self->print->config->before_layer_gcode) {
my $pp = $self->_gcodegen->placeholder_parser->clone;
$pp->set('layer_num' => $self->_gcodegen->layer_index + 1);
$pp->set('layer_z' => $layer->print_z);
$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);
$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
# (not along interlaced support material layers)
if (((values %{$self->_skirt_done}) < $self->print->config->skirt_height || $self->print->has_infinite_skirt)
&& !$self->_skirt_done->{$layer->print_z}
&& (!$layer->isa('Slic3r::Layer::Support') || $layer->id < $object->config->raft_layers)) {
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(1);
my @extruder_ids = map { $_->id } @{$self->_gcodegen->writer->extruders};
$gcode .= $self->_gcodegen->set_extruder($extruder_ids[0]);
# skip skirt if we have a large brim
if ($layer->id < $self->print->config->skirt_height || $self->print->has_infinite_skirt) {
my $skirt_flow = $self->print->skirt_flow;
# distribute skirt loops across all extruders
my @skirt_loops = @{$self->print->skirt};
for my $i (0 .. $#skirt_loops) {
# when printing layers > 0 ignore 'min_skirt_length' and
# just use the 'skirts' setting; also just use the current extruder
last if ($layer->id > 0) && ($i >= $self->print->config->skirts);
my $extruder_id = $extruder_ids[($i/@extruder_ids) % @extruder_ids];
$gcode .= $self->_gcodegen->set_extruder($extruder_id)
if $layer->id == 0;
# adjust flow according to this layer's layer height
my $loop = $skirt_loops[$i]->clone;
{
my $layer_skirt_flow = $skirt_flow->clone;
$layer_skirt_flow->set_height($layer->height);
my $mm3_per_mm = $layer_skirt_flow->mm3_per_mm;
foreach my $path (@$loop) {
$path->height($layer->height);
$path->mm3_per_mm($mm3_per_mm);
}
}
$gcode .= $self->_gcodegen->extrude_loop($loop, 'skirt', $object->config->support_material_speed);
}
}
$self->_skirt_done->{$layer->print_z} = 1;
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(0);
# allow a straight travel move to the first object point if this is the first layer
# (but don't in next layers)
if ($layer->id == 0) {
$self->_gcodegen->avoid_crossing_perimeters->set_disable_once(1);
}
}
# extrude brim
if (!$self->_brim_done) {
$gcode .= $self->_gcodegen->set_extruder($self->print->brim_extruder-1);
$self->_gcodegen->set_origin(Slic3r::Pointf->new(0,0));
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(1);
$gcode .= $self->_gcodegen->extrude($_, 'brim', $object->config->support_material_speed)
for @{$self->print->brim};
$self->_brim_done(1);
$self->_gcodegen->avoid_crossing_perimeters->set_use_external_mp(0);
# allow a straight travel move to the first object point
$self->_gcodegen->avoid_crossing_perimeters->set_disable_once(1);
}
for my $copy (@$object_copies) {
# 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");
$self->_gcodegen->set_origin(Slic3r::Pointf->new(map unscale $copy->[$_], X,Y));
# extrude support material before other things because it might use a lower Z
# and also because we avoid travelling on other things when printing it
if ($layer->isa('Slic3r::Layer::Support')) {
if ($layer->support_interface_fills->count > 0) {
$gcode .= $self->_gcodegen->set_extruder($object->config->support_material_interface_extruder-1);
$gcode .= $self->_gcodegen->extrude_path($_, 'support material interface', $object->config->get_abs_value('support_material_interface_speed'))
for @{$layer->support_interface_fills->chained_path_from($self->_gcodegen->last_pos, 0)};
}
if ($layer->support_fills->count > 0) {
$gcode .= $self->_gcodegen->set_extruder($object->config->support_material_extruder-1);
$gcode .= $self->_gcodegen->extrude_path($_, 'support material', $object->config->get_abs_value('support_material_speed'))
for @{$layer->support_fills->chained_path_from($self->_gcodegen->last_pos, 0)};
}
}
# We now define a strategy for building perimeters and fills. The separation
# between regions doesn't matter in terms of printing order, as we follow
# another logic instead:
# - we group all extrusions by extruder so that we minimize toolchanges
# - we start from the last used extruder
# - for each extruder, we group extrusions by island
# - for each island, we extrude perimeters first, unless user set the infill_first
# option
# (Still, we have to keep track of regions because we need to apply their config)
# group extrusions by extruder and then by island
my %by_extruder = (); # extruder_id => [ { perimeters => \@perimeters, infill => \@infill } ]
# cache bounding boxes of layer slices
my @layer_slices_bb = map $_->contour->bounding_box, @{$layer->slices};
my $point_inside_surface = sub {
my ($i, $point) = @_;
my $bbox = $layer_slices_bb[$i];
return $layer_slices_bb[$i]->contains_point($point)
&& $layer->slices->[$i]->contour->contains_point($point);
};
my $n_slices = $layer->slices->count - 1;
foreach my $region_id (0..($self->print->region_count-1)) {
my $layerm = $layer->regions->[$region_id] or next;
my $region = $self->print->get_region($region_id);
# process perimeters
{
my $extruder_id = $region->config->perimeter_extruder-1;
foreach my $perimeter_coll (@{$layerm->perimeters}) {
next if $perimeter_coll->empty; # this shouldn't happen but first_point() would fail
# init by_extruder item only if we actually use the extruder
$by_extruder{$extruder_id} //= [];
# $perimeter_coll is an ExtrusionPath::Collection object representing a single slice
for my $i (0 .. $n_slices) {
if (
# $perimeter_coll->first_point does not fit inside any slice
$i == $n_slices
# $perimeter_coll->first_point fits inside ith slice
|| $point_inside_surface->($i, $perimeter_coll->first_point)) {
$by_extruder{$extruder_id}[$i] //= { perimeters => {} };
$by_extruder{$extruder_id}[$i]{perimeters}{$region_id} //= [];
push @{ $by_extruder{$extruder_id}[$i]{perimeters}{$region_id} }, @$perimeter_coll;
last;
}
}
}
}
# process infill
# $layerm->fills is a collection of ExtrusionPath::Collection objects, each one containing
# the ExtrusionPath objects of a certain infill "group" (also called "surface"
# throughout the code). We can redefine the order of such Collections but we have to
# do each one completely at once.
foreach my $fill (@{$layerm->fills}) {
next if $fill->empty; # this shouldn't happen but first_point() would fail
# init by_extruder item only if we actually use the extruder
my $extruder_id = $fill->[0]->is_solid_infill
? $region->config->solid_infill_extruder-1
: $region->config->infill_extruder-1;
$by_extruder{$extruder_id} //= [];
# $fill is an ExtrusionPath::Collection object
for my $i (0 .. $n_slices) {
if ($i == $n_slices
|| $point_inside_surface->($i, $fill->first_point)) {
$by_extruder{$extruder_id}[$i] //= { infill => {} };
$by_extruder{$extruder_id}[$i]{infill}{$region_id} //= [];
push @{ $by_extruder{$extruder_id}[$i]{infill}{$region_id} }, $fill;
last;
}
}
}
}
# tweak extruder ordering to save toolchanges
my @extruders = sort { $a <=> $b } keys %by_extruder;
if (@extruders > 1) {
my $last_extruder_id = $self->_gcodegen->writer->extruder->id;
if (exists $by_extruder{$last_extruder_id}) {
@extruders = (
$last_extruder_id,
grep $_ != $last_extruder_id, @extruders,
);
}
}
foreach my $extruder_id (@extruders) {
$gcode .= $self->_gcodegen->set_extruder($extruder_id);
foreach my $island (@{ $by_extruder{$extruder_id} }) {
if ($self->print->config->infill_first) {
$gcode .= $self->_extrude_infill($island->{infill} // {});
$gcode .= $self->_extrude_perimeters($island->{perimeters} // {});
} else {
$gcode .= $self->_extrude_perimeters($island->{perimeters} // {});
$gcode .= $self->_extrude_infill($island->{infill} // {});
}
}
}
}
# apply spiral vase post-processing if this layer contains suitable geometry
# (we must feed all the G-code into the post-processor, including the first
# bottom non-spiral layers otherwise it will mess with positions)
# we apply spiral vase at this stage because it requires a full layer
$gcode = $self->_spiral_vase->process_layer($gcode)
if defined $self->_spiral_vase;
# apply cooling logic; this may alter speeds
$gcode = $self->_cooling_buffer->append(
$gcode,
$layer->object->ptr . ref($layer), # differentiate $obj_id between normal layers and support layers
$layer->id,
$layer->print_z,
) if defined $self->_cooling_buffer;
print {$self->fh} $self->filter($gcode);
}
# Extrude perimeters: Decide where to put seams (hide or align seams).
sub _extrude_perimeters {
my ($self, $entities_by_region) = @_;
my $gcode = "";
foreach my $region_id (sort keys %$entities_by_region) {
$self->_gcodegen->config->apply_static($self->print->get_region($region_id)->config);
$gcode .= $self->_gcodegen->extrude($_, 'perimeter', -1)
for @{ $entities_by_region->{$region_id} };
}
return $gcode;
}
# Chain the paths hierarchically by a greedy algorithm to minimize a travel distance.
sub _extrude_infill {
my ($self, $entities_by_region) = @_;
my $gcode = "";
foreach my $region_id (sort keys %$entities_by_region) {
$self->_gcodegen->config->apply_static($self->print->get_region($region_id)->config);
my $collection = Slic3r::ExtrusionPath::Collection->new(@{ $entities_by_region->{$region_id} });
for my $fill (@{$collection->chained_path_from($self->_gcodegen->last_pos, 0)}) {
if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
$gcode .= $self->_gcodegen->extrude($_, 'infill', -1)
for @{$fill->chained_path_from($self->_gcodegen->last_pos, 0)};
} else {
$gcode .= $self->_gcodegen->extrude($fill, 'infill', -1) ;
}
}
}
return $gcode;
}
sub flush_filters {
my ($self) = @_;
print {$self->fh} $self->filter($self->_cooling_buffer->flush, 1);
}
sub filter {
my ($self, $gcode, $flush) = @_;
# apply vibration limit if enabled;
# this injects pauses according to time (thus depends on actual speeds)
$gcode = $self->_vibration_limit->process($gcode)
if defined $self->_vibration_limit;
# apply pressure regulation if enabled;
# this depends on actual speeds
$gcode = $self->_pressure_regulator->process($gcode, $flush)
if defined $self->_pressure_regulator;
# apply arc fitting if enabled;
# this does not depend on speeds but changes G1 XY commands into G2/G2 IJ
$gcode = $self->_arc_fitting->process($gcode)
if defined $self->_arc_fitting;
return $gcode;
}
1;
Slic3r-1.3.0/lib/Slic3r/Print/Object.pm 0000664 0000000 0000000 00000074615 13274424355 0017445 0 ustar 00root root 0000000 0000000 package Slic3r::Print::Object;
# extends c++ class Slic3r::PrintObject (Print.xsp)
use strict;
use warnings;
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);
use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex
offset offset_ex offset2 offset2_ex intersection_ppl CLIPPER_OFFSET_SCALE JT_MITER);
use Slic3r::Print::State ':steps';
use Slic3r::Surface ':types';
# TODO: lazy
sub fill_maker {
my $self = shift;
return Slic3r::Fill->new(bounding_box => $self->bounding_box);
}
sub region_volumes {
my $self = shift;
return [ map $self->get_region_volumes($_), 0..($self->region_count - 1) ];
}
sub layers {
my $self = shift;
return [ map $self->get_layer($_), 0..($self->layer_count - 1) ];
}
sub support_layers {
my $self = shift;
return [ map $self->get_support_layer($_), 0..($self->support_layer_count - 1) ];
}
# 1) Decides Z positions of the layers,
# 2) Initializes layers and their regions
# 3) Slices the object meshes
# 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes
# 5) Applies size compensation (offsets the slices in XY plane)
# 6) Replaces bad slices by the slices reconstructed from the upper/lower layer
# Resulting expolygons of layer regions are marked as Internal.
#
# this should be idempotent
sub slice {
my $self = shift;
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)) {
my $layer = $self->get_layer($i);
next unless $layer->slicing_errors;
if (!$warning_thrown) {
warn "The model has overlapping or self-intersecting facets. I tried to repair it, "
. "however you might want to check the results or repair the input file and retry.\n";
$warning_thrown = 1;
}
# try to repair the layer surfaces by merging all contours and all holes from
# neighbor layers
Slic3r::debugf "Attempting to repair layer %d\n", $i;
foreach my $region_id (0 .. ($layer->region_count - 1)) {
my $layerm = $layer->region($region_id);
my (@upper_surfaces, @lower_surfaces);
for (my $j = $i+1; $j < $self->layer_count; $j++) {
if (!$self->get_layer($j)->slicing_errors) {
@upper_surfaces = @{$self->get_layer($j)->region($region_id)->slices};
last;
}
}
for (my $j = $i-1; $j >= 0; $j--) {
if (!$self->get_layer($j)->slicing_errors) {
@lower_surfaces = @{$self->get_layer($j)->region($region_id)->slices};
last;
}
}
my $union = union_ex([
map $_->expolygon->contour, @upper_surfaces, @lower_surfaces,
]);
my $diff = diff_ex(
[ map @$_, @$union ],
[ map @{$_->expolygon->holes}, @upper_surfaces, @lower_surfaces, ],
);
$layerm->slices->clear;
$layerm->slices->append($_)
for map Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL),
@$diff;
}
# update layer slices after repairing the single regions
$layer->make_slices;
}
# remove empty layers from bottom
while (@{$self->layers} && !@{$self->get_layer(0)->slices}) {
$self->delete_layer(0);
for (my $i = 0; $i <= $#{$self->layers}; $i++) {
$self->get_layer($i)->set_id( $self->get_layer($i)->id-1 );
}
}
# simplify slices if required
if ($self->print->config->resolution) {
$self->_simplify_slices(scale($self->print->config->resolution));
}
die "No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"
if !@{$self->layers};
$self->set_typed_slices(0);
$self->set_step_done(STEP_SLICE);
}
sub make_perimeters {
my ($self) = @_;
return if $self->step_done(STEP_PERIMETERS);
# Temporary workaround for detect_surfaces_type() not being idempotent (see #3764).
# We can remove this when idempotence is restored. This make_perimeters() method
# will just call merge_slices() to undo the typed slices and invalidate posDetectSurfaces.
if ($self->typed_slices) {
$self->invalidate_step(STEP_SLICE);
}
# prerequisites
$self->slice;
$self->_make_perimeters;
}
# This will assign a type (top/bottom/internal) to $layerm->slices
# and transform $layerm->fill_surfaces from expolygon
# to typed top/bottom/internal surfaces;
sub detect_surfaces_type {
my ($self) = @_;
# prerequisites
$self->slice;
$self->_detect_surfaces_type;
}
sub prepare_infill {
my ($self) = @_;
return if $self->step_done(STEP_PREPARE_INFILL);
# This prepare_infill() is not really idempotent.
# TODO: It should clear and regenerate fill_surfaces at every run
#Â instead of modifying it in place.
$self->invalidate_step(STEP_PERIMETERS);
$self->make_perimeters;
# Do this after invalidating STEP_PERIMETERS because that would re-invalidate STEP_PREPARE_INFILL
$self->set_step_started(STEP_PREPARE_INFILL);
# prerequisites
$self->detect_surfaces_type;
$self->print->status_cb->(30, "Preparing infill");
# decide what surfaces are to be filled
$_->prepare_fill_surfaces for map @{$_->regions}, @{$self->layers};
# this will detect bridges and reverse bridges
# and rearrange top/bottom/internal surfaces
$self->process_external_surfaces;
# detect which fill surfaces are near external layers
# they will be split in internal and internal-solid surfaces
$self->discover_horizontal_shells;
$self->clip_fill_surfaces;
# the following step needs to be done before combination because it may need
# to remove only half of the combined infill
$self->bridge_over_infill;
# combine fill surfaces to honor the "infill every N layers" option
$self->combine_infill;
$self->set_step_done(STEP_PREPARE_INFILL);
}
sub infill {
my ($self) = @_;
# prerequisites
$self->prepare_infill;
$self->_infill;
}
sub generate_support_material {
my $self = shift;
# prerequisites
$self->slice;
return if $self->step_done(STEP_SUPPORTMATERIAL);
$self->set_step_started(STEP_SUPPORTMATERIAL);
$self->clear_support_layers;
if ((!$self->config->support_material
&& $self->config->raft_layers == 0
&& $self->config->support_material_enforce_layers == 0)
|| scalar(@{$self->layers}) < 2
) {
$self->set_step_done(STEP_SUPPORTMATERIAL);
return;
}
$self->print->status_cb->(85, "Generating support material");
$self->_support_material->generate($self);
$self->set_step_done(STEP_SUPPORTMATERIAL);
my $stats = sprintf "Weight: %.1fg, Cost: %.1f" , $self->print->total_weight, $self->print->total_cost;
$self->print->status_cb->(85, $stats);
}
sub _support_material {
my ($self) = @_;
my $first_layer_flow = Slic3r::Flow->new_from_width(
width => ($self->print->config->first_layer_extrusion_width || $self->config->support_material_extrusion_width),
role => FLOW_ROLE_SUPPORT_MATERIAL,
nozzle_diameter => $self->print->config->nozzle_diameter->[ $self->config->support_material_extruder-1 ]
// $self->print->config->nozzle_diameter->[0],
layer_height => $self->config->get_abs_value('first_layer_height'),
bridge_flow_ratio => 0,
);
return Slic3r::Print::SupportMaterial->new(
print_config => $self->print->config,
object_config => $self->config,
first_layer_flow => $first_layer_flow,
flow => $self->support_material_flow,
interface_flow => $self->support_material_flow(FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE),
);
}
# Idempotence of this method is guaranteed by the fact that we don't remove things from
# fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries.
sub clip_fill_surfaces {
my $self = shift;
return unless $self->config->infill_only_where_needed
&& any { $_->config->fill_density > 0 } @{$self->print->regions};
# We only want infill under ceilings; this is almost like an
# internal support material.
# proceed top-down skipping bottom layer
my $upper_internal = [];
for my $layer_id (reverse 1..($self->layer_count - 1)) {
my $layer = $self->get_layer($layer_id);
my $lower_layer = $self->get_layer($layer_id-1);
# detect things that we need to support
my $overhangs = []; # Polygons
# we need to support any solid surface
push @$overhangs, map $_->p,
grep $_->is_solid, map @{$_->fill_surfaces}, @{$layer->regions};
# we also need to support perimeters when there's at least one full
# unsupported loop
{
# get perimeters area as the difference between slices and fill_surfaces
my $perimeters = diff(
[ map @$_, @{$layer->slices} ],
[ map $_->p, map @{$_->fill_surfaces}, @{$layer->regions} ],
);
# only consider the area that is not supported by lower perimeters
$perimeters = intersection(
$perimeters,
[ map $_->p, map @{$_->fill_surfaces}, @{$lower_layer->regions} ],
1,
);
# only consider perimeter areas that are at least one extrusion width thick
my $pw = min(map $_->flow(FLOW_ROLE_PERIMETER)->scaled_width, @{$layer->regions});
$perimeters = offset2($perimeters, -$pw, +$pw);
# append such thick perimeters to the areas that need support
push @$overhangs, @$perimeters;
}
# find new internal infill
$upper_internal = my $new_internal = intersection(
[
@$overhangs,
@$upper_internal,
],
[
# our current internal fill boundaries
map $_->p,
grep $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALVOID,
map @{$_->fill_surfaces}, @{$lower_layer->regions}
],
);
# apply new internal infill to regions
foreach my $layerm (@{$lower_layer->regions}) {
next if $layerm->region->config->fill_density == 0;
my (@internal, @other) = ();
foreach my $surface (map $_->clone, @{$layerm->fill_surfaces}) {
if ($surface->surface_type == S_TYPE_INTERNAL || $surface->surface_type == S_TYPE_INTERNALVOID) {
push @internal, $surface;
} else {
push @other, $surface;
}
}
my @new = map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNAL,
),
@{intersection_ex(
[ map $_->p, @internal ],
$new_internal,
1,
)};
push @other, map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNALVOID,
),
@{diff_ex(
[ map $_->p, @internal ],
$new_internal,
1,
)};
# If there are voids it means that our internal infill is not adjacent to
# perimeters. In this case it would be nice to add a loop around infill to
# make it more robust and nicer. TODO.
$layerm->fill_surfaces->clear;
$layerm->fill_surfaces->append($_) for (@new, @other);
}
}
}
sub discover_horizontal_shells {
my $self = shift;
Slic3r::debugf "==> DISCOVERING HORIZONTAL SHELLS\n";
for my $region_id (0 .. ($self->print->region_count-1)) {
for (my $i = 0; $i < $self->layer_count; $i++) {
my $layerm = $self->get_layer($i)->regions->[$region_id];
if ($layerm->region->config->solid_infill_every_layers && $layerm->region->config->fill_density > 0
&& ($i % $layerm->region->config->solid_infill_every_layers) == 0) {
my $type = $layerm->region->config->fill_density == 100 ? S_TYPE_INTERNALSOLID : S_TYPE_INTERNALBRIDGE;
$_->surface_type($type) for @{$layerm->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)};
}
EXTERNAL: foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM, S_TYPE_BOTTOMBRIDGE) {
# find slices of current type for current layer
# use slices instead of fill_surfaces because they also include the perimeter area
# which needs to be propagated in shells; we need to grow slices like we did for
# fill_surfaces though. Using both ungrown slices and grown fill_surfaces will
# not work in some situations, as there won't be any grown region in the perimeter
# area (this was seen in a model where the top layer had one extra perimeter, thus
# its fill_surfaces were thinner than the lower layer's infill), however it's the best
# solution so far. Growing the external slices by EXTERNAL_INFILL_MARGIN will put
# too much solid infill inside nearly-vertical slopes.
my $solid = [
(map $_->p, @{$layerm->slices->filter_by_type($type)}),
(map $_->p, @{$layerm->fill_surfaces->filter_by_type($type)}),
];
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;
NEIGHBOR: for (my $n = ($type == S_TYPE_TOP) ? $i-1 : $i+1;
abs($n - $i) <= $solid_layers-1;
($type == S_TYPE_TOP) ? $n-- : $n++) {
next if $n < 0 || $n >= $self->layer_count;
Slic3r::debugf " looking for neighbors on layer %d...\n", $n;
my $neighbor_layerm = $self->get_layer($n)->regions->[$region_id];
my $neighbor_fill_surfaces = $neighbor_layerm->fill_surfaces;
my @neighbor_fill_surfaces = map $_->clone, @$neighbor_fill_surfaces; # clone because we will use these surfaces even after clearing the collection
# find intersection between neighbor and current layer's surfaces
# intersections have contours and holes
my $new_internal_solid = intersection(
$solid,
[ map $_->p, grep { ($_->surface_type == S_TYPE_INTERNAL) || ($_->surface_type == S_TYPE_INTERNALSOLID) } @neighbor_fill_surfaces ],
1,
);
if (!@$new_internal_solid) {
# No internal solid needed on this layer. In order to decide whether to continue
# searching on the next neighbor (thus enforcing the configured number of solid
#Â layers, use different strategies according to configured infill density:
if ($layerm->region->config->fill_density == 0) {
# If user expects the object to be void (for example a hollow sloping vase),
# don't continue the search. In this case, we only generate the external solid
# shell if the object would otherwise show a hole (gap between perimeters of
# the two layers), and internal solid shells are a subset of the shells found
# on each previous layer.
next EXTERNAL;
} else {
# If we have internal infill, we can generate internal solid shells freely.
next NEIGHBOR;
}
}
if ($layerm->region->config->fill_density == 0) {
# if we're printing a hollow object we discard any solid shell thinner
# than a perimeter width, since it's probably just crossing a sloping wall
# and it's not wanted in a hollow print even if it would make sense when
# obeying the solid shell count option strictly (DWIM!)
my $margin = $neighbor_layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_width;
my $too_narrow = diff(
$new_internal_solid,
offset2($new_internal_solid, -$margin, +$margin, CLIPPER_OFFSET_SCALE, JT_MITER, 5),
1,
);
$new_internal_solid = $solid = diff(
$new_internal_solid,
$too_narrow,
) if @$too_narrow;
}
# make sure the new internal solid is wide enough, as it might get collapsed
# when spacing is added in Fill.pm
{
my $margin = 3 * $layerm->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width; # require at least this size
# we use a higher miterLimit here to handle areas with acute angles
# in those cases, the default miterLimit would cut the corner and we'd
# get a triangle in $too_narrow; if we grow it below then the shell
# would have a different shape from the external surface and we'd still
# have the same angle, so the next shell would be grown even more and so on.
my $too_narrow = diff(
$new_internal_solid,
offset2($new_internal_solid, -$margin, +$margin, CLIPPER_OFFSET_SCALE, JT_MITER, 5),
1,
);
if (@$too_narrow) {
# grow the collapsing parts and add the extra area to the neighbor layer
# as well as to our original surfaces so that we support this
# additional area in the next shell too
# make sure our grown surfaces don't exceed the fill area
my @grown = @{intersection(
offset($too_narrow, +$margin),
# Discard bridges as they are grown for anchoring and we can't
# remove such anchors. (This may happen when a bridge is being
# anchored onto a wall where little space remains after the bridge
# is grown, and that little space is an internal solid shell so
# it triggers this too_narrow logic.)
[ map $_->p, grep { $_->is_internal && !$_->is_bridge } @neighbor_fill_surfaces ],
)};
$new_internal_solid = $solid = [ @grown, @$new_internal_solid ];
}
}
# internal-solid are the union of the existing internal-solid surfaces
# and new ones
my $internal_solid = union_ex([
( map $_->p, grep $_->surface_type == S_TYPE_INTERNALSOLID, @neighbor_fill_surfaces ),
@$new_internal_solid,
]);
# subtract intersections from layer surfaces to get resulting internal surfaces
my $internal = diff_ex(
[ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ],
[ map @$_, @$internal_solid ],
1,
);
Slic3r::debugf " %d internal-solid and %d internal surfaces found\n",
scalar(@$internal_solid), scalar(@$internal);
# assign resulting internal surfaces to layer
$neighbor_fill_surfaces->clear;
$neighbor_fill_surfaces->append($_)
for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL),
@$internal;
# assign new internal-solid surfaces to layer
$neighbor_fill_surfaces->append($_)
for map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNALSOLID),
@$internal_solid;
# assign top and bottom surfaces to layer
foreach my $s (@{Slic3r::Surface::Collection->new(grep { ($_->surface_type == S_TYPE_TOP) || $_->is_bottom } @neighbor_fill_surfaces)->group}) {
my $solid_surfaces = diff_ex(
[ map $_->p, @$s ],
[ map @$_, @$internal_solid, @$internal ],
1,
);
$neighbor_fill_surfaces->append($_)
for map $s->[0]->clone(expolygon => $_), @$solid_surfaces;
}
}
}
}
}
}
# combine fill surfaces across layers
# Idempotence of this method is guaranteed by the fact that we don't remove things from
# fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries.
sub combine_infill {
my $self = shift;
# define the type used for voids
my %voidtype = (
&S_TYPE_INTERNAL() => S_TYPE_INTERNALVOID,
);
# work on each region separately
for my $region_id (0 .. ($self->print->region_count-1)) {
my $region = $self->print->get_region($region_id);
my $every = $region->config->infill_every_layers;
next unless $every > 1 && $region->config->fill_density > 0;
# limit the number of combined layers to the maximum height allowed by this regions' nozzle
my $nozzle_diameter = min(
$self->print->config->get_at('nozzle_diameter', $region->config->infill_extruder-1),
$self->print->config->get_at('nozzle_diameter', $region->config->solid_infill_extruder-1),
);
# define the combinations
my %combine = (); # layer_idx => number of additional combined lower layers
{
my $current_height = my $layers = 0;
for my $layer_idx (0 .. ($self->layer_count-1)) {
my $layer = $self->get_layer($layer_idx);
next if $layer->id == 0; # skip first print layer (which may not be first layer in array because of raft)
my $height = $layer->height;
# check whether the combination of this layer with the lower layers' buffer
# would exceed max layer height or max combined layer count
if ($current_height + $height >= $nozzle_diameter + epsilon || $layers >= $every) {
# append combination to lower layer
$combine{$layer_idx-1} = $layers;
$current_height = $layers = 0;
}
$current_height += $height;
$layers++;
}
# append lower layers (if any) to uppermost layer
$combine{$self->layer_count-1} = $layers;
}
# loop through layers to which we have assigned layers to combine
for my $layer_idx (sort keys %combine) {
next unless $combine{$layer_idx} > 1;
# get all the LayerRegion objects to be combined
my @layerms = map $self->get_layer($_)->get_region($region_id),
($layer_idx - ($combine{$layer_idx}-1) .. $layer_idx);
# only combine internal infill
for my $type (S_TYPE_INTERNAL) {
# we need to perform a multi-layer intersection, so let's split it in pairs
# initialize the intersection with the candidates of the lowest layer
my $intersection = [ map $_->expolygon, @{$layerms[0]->fill_surfaces->filter_by_type($type)} ];
# start looping from the second layer and intersect the current intersection with it
for my $layerm (@layerms[1 .. $#layerms]) {
$intersection = intersection_ex(
[ map @$_, @$intersection ],
[ map @{$_->expolygon}, @{$layerm->fill_surfaces->filter_by_type($type)} ],
);
}
my $area_threshold = $layerms[0]->infill_area_threshold;
@$intersection = grep $_->area > $area_threshold, @$intersection;
next if !@$intersection;
Slic3r::debugf " combining %d %s regions from layers %d-%d\n",
scalar(@$intersection),
($type == S_TYPE_INTERNAL ? 'internal' : 'internal-solid'),
$layer_idx-($every-1), $layer_idx;
# $intersection now contains the regions that can be combined across the full amount of layers
# so let's remove those areas from all layers
my @intersection_with_clearance = map @{$_->offset(
$layerms[-1]->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width / 2
+ $layerms[-1]->flow(FLOW_ROLE_PERIMETER)->scaled_width / 2
# Because fill areas for rectilinear and honeycomb are grown
# later to overlap perimeters, we need to counteract that too.
+ (($type == S_TYPE_INTERNALSOLID || $region->config->fill_pattern =~ /(rectilinear|grid|line|honeycomb)/)
? $layerms[-1]->flow(FLOW_ROLE_SOLID_INFILL)->scaled_width
: 0)
)}, @$intersection;
foreach my $layerm (@layerms) {
my @this_type = @{$layerm->fill_surfaces->filter_by_type($type)};
my @other_types = map $_->clone, grep $_->surface_type != $type, @{$layerm->fill_surfaces};
my @new_this_type = map Slic3r::Surface->new(expolygon => $_, surface_type => $type),
@{diff_ex(
[ map $_->p, @this_type ],
[ @intersection_with_clearance ],
)};
# apply surfaces back with adjusted depth to the uppermost layer
if ($layerm->layer->id == $self->get_layer($layer_idx)->id) {
push @new_this_type,
map Slic3r::Surface->new(
expolygon => $_,
surface_type => $type,
thickness => sum(map $_->layer->height, @layerms),
thickness_layers => scalar(@layerms),
),
@$intersection;
} else {
# save void surfaces
push @new_this_type,
map Slic3r::Surface->new(expolygon => $_, surface_type => $voidtype{$type}),
@{intersection_ex(
[ map @{$_->expolygon}, @this_type ],
[ @intersection_with_clearance ],
)};
}
$layerm->fill_surfaces->clear;
$layerm->fill_surfaces->append($_) for (@new_this_type, @other_types);
}
}
}
}
}
# Simplify the sliced model, if "resolution" configuration parameter > 0.
# The simplification is problematic, because it simplifies the slices independent from each other,
# which makes the simplified discretization visible on the object surface.
sub _simplify_slices {
my ($self, $distance) = @_;
foreach my $layer (@{$self->layers}) {
$layer->slices->simplify($distance);
$_->slices->simplify($distance) for @{$layer->regions};
}
}
sub support_material_flow {
my ($self, $role) = @_;
$role //= FLOW_ROLE_SUPPORT_MATERIAL;
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 => $width,
role => $role,
nozzle_diameter => $self->print->config->nozzle_diameter->[$extruder-1] // $self->print->config->nozzle_diameter->[0],
layer_height => $self->config->layer_height,
bridge_flow_ratio => 0,
);
}
1;
Slic3r-1.3.0/lib/Slic3r/Print/Simple.pm 0000664 0000000 0000000 00000010171 13274424355 0017453 0 ustar 00root root 0000000 0000000 # A simple wrapper to quickly print a single model without a GUI.
# Used by the command line slic3r.pl, by command line utilities pdf-slic3s.pl and view-toolpaths.pl,
# and by the quick slice menu of the Slic3r GUI.
#
# It creates and owns an instance of Slic3r::Print to perform the slicing
# and it accepts an instance of Slic3r::Model from the outside.
package Slic3r::Print::Simple;
use Moo;
use Slic3r::Geometry qw(X Y);
use Slic3r::Geometry::Clipper qw(diff);
has '_print' => (
is => 'ro',
default => sub { Slic3r::Print->new },
handles => [qw(apply_config config extruders output_filepath
total_used_filament total_extruded_volume
placeholder_parser process)],
);
has 'duplicate' => (
is => 'rw',
default => sub { 1 },
);
has 'scale' => (
is => 'rw',
default => sub { 1 },
);
has 'rotate' => (
is => 'rw',
default => sub { 0 },
);
has 'duplicate_grid' => (
is => 'rw',
default => sub { [1,1] },
);
has 'status_cb' => (
is => 'rw',
default => sub { sub {} },
);
has 'print_center' => (
is => 'rw',
);
has 'dont_arrange' => (
is => 'rw',
default => sub { 0 },
);
has 'output_file' => (
is => 'rw',
);
sub _bed_polygon {
my ($self) = @_;
my $bed_shape = $self->_print->config->bed_shape;
return Slic3r::Polygon->new_scale(@$bed_shape);
}
sub set_model {
# $model is of type Slic3r::Model
my ($self, $model) = @_;
# make method idempotent so that the object is reusable
$self->_print->clear_objects;
# make sure all objects have at least one defined instance
my $need_arrange = $model->add_default_instances && ! $self->dont_arrange;
# apply scaling and rotation supplied from command line if any
foreach my $instance (map @{$_->instances}, @{$model->objects}) {
$instance->set_scaling_factor($instance->scaling_factor * $self->scale);
$instance->set_rotation($instance->rotation + $self->rotate);
}
my $bed_shape = $self->_print->config->bed_shape;
my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($bed_shape);
if ($self->duplicate_grid->[X] > 1 || $self->duplicate_grid->[Y] > 1) {
$model->duplicate_objects_grid($self->duplicate_grid->[X], $self->duplicate_grid->[Y], $self->_print->config->duplicate_distance);
} elsif ($need_arrange) {
$model->duplicate_objects($self->duplicate, $self->_print->config->min_object_distance, $bb);
} elsif ($self->duplicate > 1) {
# 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} ;
if (!$self->dont_arrange) {
my $print_center = $self->print_center
// Slic3r::Pointf->new_unscale(@{ $self->_bed_polygon->centroid });
$model->center_instances_around_point($print_center);
}
foreach my $model_object (@{$model->objects}) {
$self->_print->auto_assign_extruders($model_object);
$self->_print->add_model_object($model_object);
}
}
sub _before_export {
my ($self) = @_;
$self->_print->set_status_cb($self->status_cb);
$self->_print->validate;
}
sub _after_export {
my ($self) = @_;
# check that all parts fit in bed shape, and warn if they don't
# TODO: use actual toolpaths instead of total bounding box
if (@{diff([$self->_print->bounding_box->polygon], [$self->_bed_polygon])}) {
warn "Warning: the supplied parts might not fit in the configured bed shape. "
. "You might want to review the result before printing.\n";
}
$self->_print->set_status_cb(undef);
}
sub export_gcode {
my ($self) = @_;
$self->_before_export;
$self->_print->export_gcode(output_file => $self->output_file);
$self->_after_export;
}
sub export_svg {
my ($self) = @_;
$self->_before_export;
$self->_print->export_svg(output_file => $self->output_file);
$self->_after_export;
}
1;
Slic3r-1.3.0/lib/Slic3r/Print/State.pm 0000664 0000000 0000000 00000000560 13274424355 0017303 0 ustar 00root root 0000000 0000000 # Wraps C++ enums Slic3r::PrintStep and Slic3r::PrintObjectStep
package Slic3r::Print::State;
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
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);
1;
Slic3r-1.3.0/lib/Slic3r/Print/SupportMaterial.pm 0000664 0000000 0000000 00000130763 13274424355 0021367 0 ustar 00root root 0000000 0000000 # Instantiated by Slic3r::Print::Object->_support_material()
# only generate() and contact_distance() are called from the outside of this module.
package Slic3r::Print::SupportMaterial;
use Moo;
use List::Util qw(sum min max);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(epsilon scale scaled_epsilon PI rad2deg deg2rad convex_hull);
use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2
intersection_pl offset2_ex diff_pl diff_ex);
use Slic3r::Surface ':types';
has 'print_config' => (is => 'rw', required => 1);
has 'object_config' => (is => 'rw', required => 1);
has 'flow' => (is => 'rw', required => 1);
has 'first_layer_flow' => (is => 'rw', required => 1);
has 'interface_flow' => (is => 'rw', required => 1);
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) = @_;
# Determine the top surfaces of the support, defined as:
# contact = overhangs - clearance + margin
# This method is responsible for identifying what contact surfaces
# should the support material expose to the object in order to guarantee
# that it will be effective, regardless of how it's built below.
my ($contact, $overhang) = $self->contact_area($object);
# Determine the top surfaces of the object. We need these to determine
# the layer heights of support material and to clip support to the object
# silhouette.
my ($top) = $self->object_top($object, $contact);
# We now know the upper and lower boundaries for our support material object
# (@$contact_z and @$top_z), so we can generate intermediate layers.
my $support_z = $self->support_layers_z(
[ sort keys %$contact ],
[ sort keys %$top ],
max(map $_->height, @{$object->layers})
);
# If we wanted to apply some special logic to the first support layers lying on
# object's top surfaces this is the place to detect them
my $shape = [];
if ($self->object_config->support_material_pattern eq 'pillars') {
$self->generate_pillars_shape($contact, $support_z, $shape);
}
# Propagate contact layers downwards to generate interface layers
my ($interface) = $self->generate_interface_layers($support_z, $contact, $top);
$self->clip_with_object($interface, $support_z, $object);
$self->clip_with_shape($interface, $shape) if @$shape;
# Propagate contact layers and interface layers downwards to generate
#Â the main support layers.
my ($base) = $self->generate_base_layers($support_z, $contact, $interface, $top);
$self->clip_with_object($base, $support_z, $object);
$self->clip_with_shape($base, $shape) if @$shape;
# Detect what part of base support layers are "reverse interfaces" because they
# lie above object's top surfaces.
$self->generate_bottom_interface_layers($support_z, $base, $top, $interface);
# Install support layers into object.
for my $i (0 .. $#$support_z) {
$object->add_support_layer(
$i, # id
($i == 0) ? $support_z->[$i] : ($support_z->[$i] - $support_z->[$i-1]), # height
$support_z->[$i], # print_z
);
if ($i >= 1) {
$object->support_layers->[-2]->set_upper_layer($object->support_layers->[-1]);
$object->support_layers->[-1]->set_lower_layer($object->support_layers->[-2]);
}
}
# Generate the actual toolpaths and save them into each layer.
$self->generate_toolpaths($object, $overhang, $contact, $interface, $base);
}
sub contact_area {
# $object is Slic3r::Print::Object
my ($self, $object) = @_;
my $conf = $self->object_config;
# if user specified a custom angle threshold, convert it to radians
my $threshold_rad;
if (!($conf->support_material_threshold =~ /%$/)) {
$threshold_rad = deg2rad($conf->support_material_threshold + 1); # +1 makes the threshold inclusive
Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad);
}
# Build support on a build plate only? If so, then collect top surfaces into $buildplate_only_top_surfaces
# and subtract $buildplate_only_top_surfaces from the contact surfaces, so
# there is no contact surface supported by a top surface.
my $buildplate_only =
( $conf->support_material || $conf->support_material_enforce_layers)
&& $conf->support_material_buildplate_only;
my $buildplate_only_top_surfaces = [];
# determine contact areas
my %contact = (); # contact_z => [ polygons ]
my %overhang = (); #Â contact_z => [ polygons ] - this stores the actual overhang supported by each contact layer
for my $layer_id (0 .. $#{$object->layers}) {
# note $layer_id might != $layer->id when raft_layers > 0
# so $layer_id == 0 means first object layer
# and $layer->id == 0 means first print layer (including raft)
# if no raft, and we're at layer 0, skip to layer 1
if ( $conf->raft_layers == 0 && $layer_id == 0 ) {
next;
}
# with or without raft, if we're above layer 1, we need to quit
# support generation if supports are disabled, or if we're at a high
# enough layer that enforce-supports no longer applies
if ( $layer_id > 0
&& !$conf->support_material
&& ($layer_id >= $conf->support_material_enforce_layers) ) {
# if we are only going to generate raft just check
# the 'overhangs' of the first object layer
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.
my $projection_new = [];
push @$projection_new, ( map $_->p, map @{$_->slices->filter_by_type(S_TYPE_TOP)}, @{$layer->regions} );
if (@$projection_new) {
# Merge the new top surfaces with the preceding top surfaces.
# Apply the safety offset to the newly added polygons, so they will connect
# with the polygons collected before,
# but don't apply the safety offset during the union operation as it would
# inflate the polygons over and over.
push @$buildplate_only_top_surfaces, @{ offset($projection_new, scale(0.01)) };
$buildplate_only_top_surfaces = union($buildplate_only_top_surfaces, 0);
}
}
# detect overhangs and contact areas needed to support them
my (@overhang, @contact) = ();
if ($layer_id == 0) {
# this is the first object layer, so we're here just to get the object
# footprint for the raft
# we only consider contours and discard holes to get a more continuous raft
push @overhang, map $_->clone, map $_->contour, @{$layer->slices};
push @contact, @{offset(\@overhang, scale +MARGIN)};
} else {
my $lower_layer = $object->get_layer($layer_id-1);
foreach my $layerm (@{$layer->regions}) {
my $fw = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_width;
my $diff;
# If a threshold angle was specified, use a different logic for detecting overhangs.
if (($conf->support_material && defined $threshold_rad)
|| $layer_id <= $conf->support_material_enforce_layers
|| ($conf->raft_layers > 0 && $layer_id == 0)) {
my $d = 0;
my $layer_threshold_rad = $threshold_rad;
if ($layer_id <= $conf->support_material_enforce_layers) {
# Use ~45 deg number for enforced supports if we are in auto
$layer_threshold_rad = deg2rad(89);
}
if (defined $layer_threshold_rad) {
$d = scale $lower_layer->height
* ((cos $layer_threshold_rad) / (sin $layer_threshold_rad));
}
$diff = diff(
[ map $_->p, @{$layerm->slices} ],
offset([ map @$_, @{$lower_layer->slices} ], +$d),
);
# only enforce spacing from the object ($fw/2) if the threshold angle
# is not too high: in that case, $d will be very small (as we need to catch
# very short overhangs), and such contact area would be eaten by the
# enforced spacing, resulting in high threshold angles to be almost ignored
$diff = diff(
offset($diff, $d - $fw/2),
[ map @$_, @{$lower_layer->slices} ],
) if $d > $fw/2;
} else {
$diff = diff(
[ map $_->p, @{$layerm->slices} ],
offset([ map @$_, @{$lower_layer->slices} ], +$conf->get_abs_value_over('support_material_threshold', $fw)),
);
# collapse very tiny spots
$diff = offset2($diff, -$fw/10, +$fw/10);
# $diff now contains the ring or stripe comprised between the boundary of
# lower slices and the centerline of the last perimeter in this overhanging layer.
# Void $diff means that there's no upper perimeter whose centerline is
# outside the lower slice boundary, thus no overhang
}
if ($conf->dont_support_bridges) {
# compute the area of bridging perimeters
my $bridged_perimeters; # Polygons
{
my $bridge_flow = $layerm->flow(FLOW_ROLE_PERIMETER, 1);
# Get the lower layer's slices and grow them by half the nozzle diameter
# because we will consider the upper perimeters supported even if half nozzle
# falls outside the lower slices.
my $lower_grown_slices;
{
my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $layerm->region->config->perimeter_extruder-1);
$lower_grown_slices = offset(
[ map @$_, @{$lower_layer->slices} ],
+scale($nozzle_diameter/2),
);
}
# Get all perimeters as polylines.
# TODO: split_at_first_point() (called by as_polyline() for ExtrusionLoops)
#Â could split a bridge mid-way
my @overhang_perimeters = map $_->as_polyline, @{$layerm->perimeters->flatten};
# Only consider the overhang parts of such perimeters,
# overhangs being those parts not supported by
# workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
$_->[0]->translate(1,0) for @overhang_perimeters;
@overhang_perimeters = @{diff_pl(
\@overhang_perimeters,
$lower_grown_slices,
)};
# only consider straight overhangs
@overhang_perimeters = grep $_->is_straight, @overhang_perimeters;
# only consider overhangs having endpoints inside layer's slices
foreach my $polyline (@overhang_perimeters) {
$polyline->extend_start($fw);
$polyline->extend_end($fw);
}
@overhang_perimeters = grep {
$layer->slices->contains_point($_->first_point) && $layer->slices->contains_point($_->last_point)
} @overhang_perimeters;
# convert bridging polylines into polygons by inflating them with their thickness
{
# For bridges we can't assume width is larger than spacing because they
# are positioned according to non-bridging perimeters spacing.
my $w = max(
$bridge_flow->scaled_width,
$bridge_flow->scaled_spacing,
$fw, # width of external perimeters
$layerm->flow(FLOW_ROLE_PERIMETER)->scaled_width,
);
$bridged_perimeters = union([
# Also apply safety offset to ensure no gaps are left in between.
map @{$_->grow($w/2 + 10)}, @overhang_perimeters
]);
}
}
if (1) {
# remove the entire bridges and only support the unsupported edges
my @bridges = map $_->expolygon,
grep $_->bridge_angle != -1,
@{$layerm->fill_surfaces->filter_by_type(S_TYPE_BOTTOMBRIDGE)};
$diff = diff(
$diff,
[
(map @$_, @bridges),
@$bridged_perimeters,
],
1,
);
push @$diff, @{intersection(
[ map @{$_->grow(+scale MARGIN)}, @{$layerm->unsupported_bridge_edges} ],
[ map @$_, @bridges ],
)};
} else {
# just remove bridged areas
$diff = diff(
$diff,
$layerm->bridged,
1,
);
}
} # if ($conf->dont_support_bridges)
if ($buildplate_only) {
# Don't support overhangs above the top surfaces.
# This step is done before the contact surface is calcuated by growing the overhang region.
$diff = diff($diff, $buildplate_only_top_surfaces);
}
next if !@$diff;
push @overhang, @$diff; # NOTE: this is not the full overhang as it misses the outermost half of the perimeter width!
# Let's define the required contact area by using a max gap of half the upper
# extrusion width and extending the area according to the configured margin.
# We increment the area in steps because we don't want our support to overflow
# on the other side of the object (if it's very thin).
{
my $slices_margin = offset([ map @$_, @{$lower_layer->slices} ], +$fw/2);
if ($buildplate_only) {
# Trim the inflated contact surfaces by the top surfaces as well.
push @$slices_margin, map $_->clone, @{$buildplate_only_top_surfaces};
$slices_margin = union($slices_margin);
}
for ($fw/2, map {scale MARGIN_STEP} 1..(MARGIN / MARGIN_STEP)) {
$diff = diff(
offset($diff, $_),
$slices_margin,
);
}
}
push @contact, @$diff;
}
}
next if !@contact;
# now apply the contact areas to the layer were they need to be made
{
# get the average nozzle diameter used on this layer
my @nozzle_diameters = map $self->print_config->get_at('nozzle_diameter', $_),
map { $_->config->perimeter_extruder-1, $_->config->infill_extruder-1, $_->config->solid_infill_extruder-1 }
map $_->region, @{$layer->regions};
my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters;
my $contact_z = $layer->print_z - $self->contact_distance($layer->height, $nozzle_diameter);
# ignore this contact area if it's too low
next if $contact_z < $conf->get_value('first_layer_height') - epsilon;
$contact{$contact_z} = [ @contact ];
$overhang{$contact_z} = [ @overhang ];
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("out\\contact_" . $contact_z . ".svg",
green_expolygons => union_ex($buildplate_only_top_surfaces),
blue_expolygons => union_ex(\@contact),
red_expolygons => union_ex(\@overhang),
);
}
}
}
return (\%contact, \%overhang);
}
sub object_top {
my ($self, $object, $contact) = @_;
# find object top surfaces
# we'll use them to clip our support and detect where does it stick
my %top = (); # print_z => [ expolygons ]
return \%top if ($self->object_config->support_material_buildplate_only);
my $projection = [];
foreach my $layer (reverse @{$object->layers}) {
if (my @top = map @{$_->slices->filter_by_type(S_TYPE_TOP)}, @{$layer->regions}) {
# compute projection of the contact areas above this top layer
# first add all the 'new' contact areas to the current projection
# ('new' means all the areas that are lower than the last top layer
# we considered)
my $min_top = min(keys %top) // max(keys %$contact);
# use <= instead of just < because otherwise we'd ignore any contact regions
#Â having the same Z of top layers
push @$projection, map @{$contact->{$_}}, grep { $_ > $layer->print_z && $_ <= $min_top } keys %$contact;
# now find whether any projection falls onto this top surface
my $touching = intersection($projection, [ map $_->p, @top ]);
if (@$touching) {
# grow top surfaces so that interface and support generation are generated
# with some spacing from object - it looks we don't need the actual
# top shapes so this can be done here
$top{ $layer->print_z } = offset($touching, $self->flow->scaled_width);
}
# remove the areas that touched from the projection that will continue on
# next, lower, top surfaces
$projection = diff($projection, $touching);
}
}
return \%top;
}
sub support_layers_z {
my ($self, $contact_z, $top_z, $max_object_layer_height) = @_;
# quick table to check whether a given Z is a top surface
my %top = map { $_ => 1 } @$top_z;
# determine layer height for any non-contact layer
# we use max() to prevent many ultra-thin layers to be inserted in case
# layer_height > nozzle_diameter * 0.75
my $nozzle_diameter = $self->print_config->get_at('nozzle_diameter', $self->object_config->support_material_extruder-1);
my $support_material_height = max($max_object_layer_height, $nozzle_diameter * 0.75);
my $contact_distance = $self->contact_distance($support_material_height, $nozzle_diameter);
# initialize known, fixed, support layers
my @z = sort { $a <=> $b }
@$contact_z,
@$top_z, # TODO: why we have this?
(map $_ + $contact_distance, @$top_z);
# enforce first layer height
my $first_layer_height = $self->object_config->get_value('first_layer_height');
shift @z while @z && $z[0] <= $first_layer_height;
unshift @z, $first_layer_height;
# add raft layers by dividing the space between first layer and
# first contact layer evenly
if ($self->object_config->raft_layers > 1 && @z >= 2) {
# $z[1] is last raft layer (contact layer for the first layer object)
my $height = ($z[1] - $z[0]) / ($self->object_config->raft_layers - 1);
# since we already have two raft layers ($z[0] and $z[1]) we need to insert
# raft_layers-2 more
splice @z, 1, 0,
map { sprintf "%.2f", $_ }
map { $z[0] + $height * $_ }
1..($self->object_config->raft_layers - 2);
}
# create other layers (skip raft layers as they're already done and use thicker layers)
for (my $i = $#z; $i >= $self->object_config->raft_layers; $i--) {
my $target_height = $support_material_height;
if ($i > 0 && $top{ $z[$i-1] }) {
$target_height = $nozzle_diameter;
}
# enforce first layer height
if (($i == 0 && $z[$i] > $target_height + $first_layer_height)
|| ($z[$i] - $z[$i-1] > $target_height + Slic3r::Geometry::epsilon)) {
splice @z, $i, 0, ($z[$i] - $target_height);
$i++;
}
}
# remove duplicates and make sure all 0.x values have the leading 0
{
my %sl = map { 1 * $_ => 1 } @z;
@z = sort { $a <=> $b } keys %sl;
}
return \@z;
}
sub generate_interface_layers {
my ($self, $support_z, $contact, $top) = @_;
# let's now generate interface layers below contact areas
my %interface = (); # layer_id => [ polygons ]
my $interface_layers_num = $self->object_config->support_material_interface_layers;
for my $layer_id (0 .. $#$support_z) {
my $z = $support_z->[$layer_id];
my $this = $contact->{$z} // next;
# count contact layer as interface layer
for (my $i = $layer_id-1; $i >= 0 && $i > $layer_id-$interface_layers_num; $i--) {
$z = $support_z->[$i];
my @overlapping_layers = $self->overlapping_layers($i, $support_z);
my @overlapping_z = map $support_z->[$_], @overlapping_layers;
# Compute interface area on this layer as diff of upper contact area
# (or upper interface area) and layer slices.
# This diff is responsible of the contact between support material and
# the top surfaces of the object. We should probably offset the top
# surfaces vertically before performing the diff, but this needs
# investigation.
$this = $interface{$i} = diff(
[
@$this, # clipped projection of the current contact regions
@{ $interface{$i} || [] }, # interface regions already applied to this layer
],
[
(map @$_, map $top->{$_}, grep exists $top->{$_}, @overlapping_z), # top slices on this layer
(map @$_, map $contact->{$_}, grep exists $contact->{$_}, @overlapping_z), # contact regions on this layer
],
1,
);
}
}
return \%interface;
}
sub generate_bottom_interface_layers {
my ($self, $support_z, $base, $top, $interface) = @_;
# If no interface layers are allowed, don't generate bottom interface layers.
return if $self->object_config->support_material_interface_layers == 0;
my $area_threshold = $self->interface_flow->scaled_spacing ** 2;
# loop through object's top surfaces
foreach my $top_z (sort keys %$top) {
my $this = $top->{$top_z};
# keep a count of the interface layers we generated for this top surface
my $interface_layers = 0;
# loop through support layers until we find the one(s) right above the top
#Â surface
foreach my $layer_id (0 .. $#$support_z) {
my $z = $support_z->[$layer_id];
next unless $z > $top_z;
if ($base->{$layer_id}) {
# get the support material area that should be considered interface
my $interface_area = intersection(
$base->{$layer_id},
$this,
);
# discard too small areas
$interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ];
# subtract new interface area from base
$base->{$layer_id} = diff(
$base->{$layer_id},
$interface_area,
);
# add new interface area to interface
push @{$interface->{$layer_id}}, @$interface_area;
}
$interface_layers++;
last if $interface_layers == $self->object_config->support_material_interface_layers;
}
}
}
sub generate_base_layers {
my ($self, $support_z, $contact, $interface, $top) = @_;
# let's now generate support layers under interface layers
my $base = {}; # layer_id => [ polygons ]
{
for my $i (reverse 0 .. $#$support_z-1) {
my $z = $support_z->[$i];
my @overlapping_layers = $self->overlapping_layers($i, $support_z);
my @overlapping_z = map $support_z->[$_], @overlapping_layers;
# in case we have no interface layers, look at upper contact
# (1 interface layer means we only have contact layer, so $interface->{$i+1} is empty)
my @upper_contact = ();
if ($self->object_config->support_material_interface_layers <= 1) {
@upper_contact = @{ $contact->{$support_z->[$i+1]} || [] };
}
$base->{$i} = diff(
[
@{ $base->{$i+1} || [] }, # support regions on upper layer
@{ $interface->{$i+1} || [] }, # interface regions on upper layer
@upper_contact, # contact regions on upper layer
],
[
(map @$_, map $top->{$_}, grep exists $top->{$_}, @overlapping_z), # top slices on this layer
(map @$_, map $interface->{$_}, grep exists $interface->{$_}, @overlapping_layers), # interface regions on this layer
(map @$_, map $contact->{$_}, grep exists $contact->{$_}, @overlapping_z), # contact regions on this layer
],
1,
);
}
}
return $base;
}
# This method removes object silhouette from support material
# (it's used with interface and base only). It removes a bit more,
#Â leaving a thin gap between object and support in the XY plane.
sub clip_with_object {
my ($self, $support, $support_z, $object) = @_;
foreach my $i (keys %$support) {
next if !@{$support->{$i}};
my $zmax = $support_z->[$i];
my $zmin = ($i == 0) ? 0 : $support_z->[$i-1];
my @layers = grep { $_->print_z > $zmin && ($_->print_z - $_->height) < $zmax }
@{$object->layers};
# $layer->slices contains the full shape of layer, thus including
# perimeter's width. $support contains the full shape of support
#Â material, thus including the width of its foremost extrusion.
# We leave a gap equal to a full extrusion width.
$support->{$i} = diff(
$support->{$i},
offset([ map @$_, map @{$_->slices}, @layers ], +$self->flow->scaled_width),
);
}
}
sub generate_toolpaths {
my ($self, $object, $overhang, $contact, $interface, $base) = @_;
my $flow = $self->flow;
my $interface_flow = $self->interface_flow;
# shape of contact area
my $contact_loops = 1;
my $circle_radius = 1.5 * $interface_flow->scaled_width;
my $circle_distance = 3 * $circle_radius;
my $circle = Slic3r::Polygon->new(map [ $circle_radius * cos $_, $circle_radius * sin $_ ],
(5*PI/3, 4*PI/3, PI, 2*PI/3, PI/3, 0));
Slic3r::debugf "Generating patterns\n";
# prepare fillers
my $pattern = $self->object_config->support_material_pattern;
my @angles = ($self->object_config->support_material_angle);
if ($pattern eq 'rectilinear-grid') {
$pattern = 'rectilinear';
push @angles, $angles[0] + 90;
} elsif ($pattern eq 'pillars') {
$pattern = 'honeycomb';
}
my $interface_angle = $self->object_config->support_material_angle + 90;
my $interface_spacing = $self->object_config->support_material_interface_spacing + $interface_flow->spacing;
my $interface_density = $interface_spacing == 0 ? 1 : $interface_flow->spacing / $interface_spacing;
my $support_spacing = $self->object_config->support_material_spacing + $flow->spacing;
my $support_density = $support_spacing == 0 ? 1 : $flow->spacing / $support_spacing;
my $process_layer = sub {
my ($layer_id) = @_;
my $layer = $object->support_layers->[$layer_id];
my $z = $layer->print_z;
# we redefine flows locally by applying this layer's height
my $_flow = $flow->clone;
my $_interface_flow = $interface_flow->clone;
$_flow->set_height($layer->height);
$_interface_flow->set_height($layer->height);
my $overhang = $overhang->{$z} || [];
my $contact = $contact->{$z} || [];
my $interface = $interface->{$layer_id} || [];
my $base = $base->{$layer_id} || [];
if (DEBUG_CONTACT_ONLY) {
$interface = [];
$base = [];
}
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("layer_" . $z . ".svg",
red_expolygons => union_ex($contact),
green_expolygons => union_ex($interface),
);
}
# islands
$layer->support_islands->append(@{union_ex([ @$interface, @$base, @$contact ])});
# contact
my $contact_infill = [];
if ($self->object_config->support_material_interface_layers == 0) {
# if no interface layers were requested we treat the contact layer
# exactly as a generic base layer
push @$base, @$contact;
} elsif (@$contact && $contact_loops > 0) {
# generate the outermost loop
# find centerline of the external loop (or any other kind of extrusions should the loop be skipped)
$contact = offset($contact, -$_interface_flow->scaled_width/2);
my @loops0 = ();
{
# find centerline of the external loop of the contours
my @external_loops = @$contact;
# only consider the loops facing the overhang
{
my $overhang_with_margin = offset($overhang, +$_interface_flow->scaled_width/2);
@external_loops = grep {
@{intersection_pl(
[ $_->split_at_first_point ],
$overhang_with_margin,
)}
} @external_loops;
}
# apply a pattern to the loop
my @positions = map @{Slic3r::Polygon->new(@$_)->equally_spaced_points($circle_distance)}, @external_loops;
@loops0 = @{diff(
[ @external_loops ],
[ map { my $c = $circle->clone; $c->translate(@$_); $c } @positions ],
)};
}
# make more loops
my @loops = @loops0;
for my $i (2..$contact_loops) {
my $d = ($i-1) * $_interface_flow->scaled_spacing;
push @loops, @{offset2(\@loops0, -$d -0.5*$_interface_flow->scaled_spacing, +0.5*$_interface_flow->scaled_spacing)};
}
# clip such loops to the side oriented towards the object
@loops = @{intersection_pl(
[ map $_->split_at_first_point, @loops ],
offset($overhang, +scale MARGIN),
)};
# add the contact infill area to the interface area
# note that growing loops by $circle_radius ensures no tiny
# extrusions are left inside the circles; however it creates
# a very large gap between loops and contact_infill, so maybe another
# solution should be found to achieve both goals
$contact_infill = diff(
$contact,
[ map @{$_->grow($circle_radius*1.1)}, @loops ],
);
# transform loops into ExtrusionPath objects
my $mm3_per_mm = $_interface_flow->mm3_per_mm;
@loops = map Slic3r::ExtrusionPath->new(
polyline => $_,
role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE,
mm3_per_mm => $mm3_per_mm,
width => $_interface_flow->width,
height => $layer->height,
), @loops;
$layer->support_interface_fills->append(@loops);
}
# Allocate the fillers exclusively in the worker threads! Don't allocate them at the main thread,
# as Perl copies the C++ pointers by default, so then the C++ objects are shared between threads!
my %fillers = (
interface => Slic3r::Filler->new_from_type('rectilinear'),
support => Slic3r::Filler->new_from_type($pattern),
);
my $bounding_box = $object->bounding_box;
$fillers{interface}->set_bounding_box($object->bounding_box);
$fillers{support}->set_bounding_box($object->bounding_box);
# interface and contact infill
if (@$interface || @$contact_infill) {
# 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
$interface = offset2($interface, +scaled_epsilon, -(scaled_epsilon + $_interface_flow->scaled_width/2));
# join regions by offsetting them to ensure they're merged
$interface = offset([ @$interface, @$contact_infill ], scaled_epsilon);
# turn base support into interface when it's contained in our holes
# (this way we get wider interface anchoring)
{
my @p = @$interface;
@$interface = ();
foreach my $p (@p) {
if ($p->is_clockwise) {
my $p2 = $p->clone;
$p2->make_counter_clockwise;
next if !@{diff([$p2], $base, 1)};
}
push @$interface, $p;
}
}
$base = diff($base, $interface);
my @paths = ();
foreach my $expolygon (@{union_ex($interface)}) {
my $p = $fillers{interface}->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL),
density => $interface_density,
layer_height => $layer->height,
complete => 1,
);
my $mm3_per_mm = $_interface_flow->mm3_per_mm;
push @paths, map Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(@$_),
role => EXTR_ROLE_SUPPORTMATERIAL_INTERFACE,
mm3_per_mm => $mm3_per_mm,
width => $_interface_flow->width,
height => $layer->height,
), @$p;
}
$layer->support_interface_fills->append(@paths);
}
# support or flange
if (@$base) {
my $filler = $fillers{support};
$filler->set_angle(deg2rad($angles[ ($layer_id) % @angles ]));
# We don't use $base_flow->spacing because we need a constant spacing
# value that guarantees that all layers are correctly aligned.
$filler->set_min_spacing($flow->spacing);
my $density = $support_density;
my $base_flow = $_flow;
# find centerline of the external loop/extrusions
my $to_infill = offset2($base, +scaled_epsilon, -(scaled_epsilon + $_flow->scaled_width/2));
my @paths = ();
# base flange
if ($layer_id == 0) {
$filler = $fillers{interface};
$filler->set_angle(deg2rad($self->object_config->support_material_angle + 90));
$density = 0.5;
$base_flow = $self->first_layer_flow;
# use the proper spacing for first layer as we don't need to align
#Â its pattern to the other layers
$filler->set_min_spacing($base_flow->spacing);
# subtract brim so that it goes around the object fully (and support gets its own brim)
if ($self->print_config->brim_width > 0) {
my $d = +scale $self->print_config->brim_width*2;
$to_infill = diff_ex(
$to_infill,
offset($object->get_layer(0)->slices->polygons, $d),
);
} else {
$to_infill = union_ex($to_infill);
}
} else {
# draw a perimeter all around support infill
# TODO: use brim ordering algorithm
my $mm3_per_mm = $_flow->mm3_per_mm;
push @paths, map Slic3r::ExtrusionPath->new(
polyline => $_->split_at_first_point,
role => EXTR_ROLE_SUPPORTMATERIAL,
mm3_per_mm => $mm3_per_mm,
width => $_flow->width,
height => $layer->height,
), @$to_infill;
# TODO: use offset2_ex()
$to_infill = offset_ex($to_infill, -$_flow->scaled_spacing);
}
my $mm3_per_mm = $base_flow->mm3_per_mm;
foreach my $expolygon (@$to_infill) {
my $p = $filler->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL),
density => $density,
layer_height => $layer->height,
complete => 1,
);
push @paths, map Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(@$_),
role => EXTR_ROLE_SUPPORTMATERIAL,
mm3_per_mm => $mm3_per_mm,
width => $base_flow->width,
height => $layer->height,
), @$p;
}
$layer->support_fills->append(@paths);
}
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("islands_" . $z . ".svg",
red_expolygons => union_ex($contact),
green_expolygons => union_ex($interface),
green_polylines => [ map $_->unpack->polyline, @{$layer->support_contact_fills} ],
polylines => [ map $_->unpack->polyline, @{$layer->support_fills} ],
);
}
};
Slic3r::parallelize(
threads => $self->print_config->threads,
items => [ 0 .. $#{$object->support_layers} ],
thread_cb => sub {
my $q = shift;
while (defined (my $layer_id = $q->dequeue)) {
$process_layer->($layer_id);
}
},
no_threads_cb => sub {
$process_layer->($_) for 0 .. $#{$object->support_layers};
},
);
}
sub generate_pillars_shape {
my ($self, $contact, $support_z, $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 $grid; # arrayref of polygons
{
my $pillar = Slic3r::Polygon->new(
[0,0],
[$pillar_size, 0],
[$pillar_size, $pillar_size],
[0, $pillar_size],
);
my @pillars = ();
my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, values %$contact ]);
for (my $x = $bb->x_min; $x <= $bb->x_max-$pillar_size; $x += $pillar_spacing) {
for (my $y = $bb->y_min; $y <= $bb->y_max-$pillar_size; $y += $pillar_spacing) {
push @pillars, my $p = $pillar->clone;
$p->translate($x, $y);
}
}
$grid = union(\@pillars);
}
# add pillars to every layer
for my $i (0..$#$support_z) {
$shape->[$i] = [ @$grid ];
}
# build capitals
for my $i (0..$#$support_z) {
my $z = $support_z->[$i];
my $capitals = intersection(
$grid,
$contact->{$z} // [],
);
# work on one pillar at time (if any) to prevent the capitals from being merged
# but store the contact area supported by the capital because we need to make
# sure nothing is left
my $contact_supported_by_capitals = [];
foreach my $capital (@$capitals) {
# enlarge capital tops
$capital = offset([$capital], +($pillar_spacing - $pillar_size)/2);
push @$contact_supported_by_capitals, @$capital;
for (my $j = $i-1; $j >= 0; $j--) {
my $jz = $support_z->[$j];
$capital = offset($capital, -$self->interface_flow->scaled_width/2);
last if !@$capitals;
push @{ $shape->[$j] }, @$capital;
}
}
# Capitals will not generally cover the whole contact area because there will be
# remainders. For now we handle this situation by projecting such unsupported
# areas to the ground, just like we would do with a normal support.
my $contact_not_supported_by_capitals = diff(
$contact->{$z} // [],
$contact_supported_by_capitals,
);
if (@$contact_not_supported_by_capitals) {
for (my $j = $i-1; $j >= 0; $j--) {
push @{ $shape->[$j] }, @$contact_not_supported_by_capitals;
}
}
}
}
sub clip_with_shape {
my ($self, $support, $shape) = @_;
foreach my $i (keys %$support) {
# don't clip bottom layer with shape so that we
# can generate a continuous base flange
# also don't clip raft layers
next if $i == 0;
next if $i < $self->object_config->raft_layers;
$support->{$i} = intersection(
$support->{$i},
$shape->[$i],
);
}
}
# this method returns the indices of the layers overlapping with the given one
sub overlapping_layers {
my ($self, $i, $support_z) = @_;
my $zmax = $support_z->[$i];
my $zmin = ($i == 0) ? 0 : $support_z->[$i-1];
return grep {
my $zmax2 = $support_z->[$_];
my $zmin2 = ($_ == 0) ? 0 : $support_z->[$_-1];
$zmax > $zmin2 && $zmin < $zmax2;
} 0..$#$support_z;
}
sub contact_distance {
my ($self, $layer_height, $nozzle_diameter) = @_;
my $extra = $self->object_config->support_material_contact_distance;
if ($extra == 0) {
return $layer_height;
} else {
return $nozzle_diameter + $extra;
}
}
1;
Slic3r-1.3.0/lib/Slic3r/SVG.pm 0000664 0000000 0000000 00000010160 13274424355 0015563 0 ustar 00root root 0000000 0000000 package Slic3r::SVG;
use strict;
use warnings;
use SVG;
use constant X => 0;
use constant Y => 1;
our $filltype = 'evenodd';
sub factor {
return &Slic3r::SCALING_FACTOR * 10;
}
sub svg {
my $svg = SVG->new(width => 200 * 10, height => 200 * 10);
my $marker_end = $svg->marker(
id => "endArrow",
viewBox => "0 0 10 10",
refX => "1",
refY => "5",
markerUnits => "strokeWidth",
orient => "auto",
markerWidth => "10",
markerHeight => "8",
);
$marker_end->polyline(
points => "0,0 10,5 0,10 1,5",
fill => "darkblue",
);
return $svg;
}
sub output {
my ($filename, @things) = @_;
my $svg = svg();
my $arrows = 1;
while (my $type = shift @things) {
my $value = shift @things;
if ($type eq 'no_arrows') {
$arrows = 0;
} elsif ($type =~ /^(?:(.+?)_)?expolygons$/) {
my $colour = $1;
$value = [ map $_->pp, @$value ];
my $g = $svg->group(
style => {
'stroke-width' => 0,
'stroke' => $colour || 'black',
'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')),
'fill-type' => $filltype,
},
);
foreach my $expolygon (@$value) {
my $points = join ' ', map "M $_ z", map join(" ", reverse map $_->[0]*factor() . " " . $_->[1]*factor(), @$_), @$expolygon;
$g->path(
d => $points,
);
}
} elsif ($type =~ /^(?:(.+?)_)?(polygon|polyline)s$/) {
my ($colour, $method) = ($1, $2);
$value = [ map $_->pp, @$value ];
my $g = $svg->group(
style => {
'stroke-width' => ($method eq 'polyline') ? 1 : 0,
'stroke' => $colour || 'black',
'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')),
},
);
foreach my $polygon (@$value) {
my $path = $svg->get_path(
'x' => [ map($_->[X] * factor(), @$polygon) ],
'y' => [ map($_->[Y] * factor(), @$polygon) ],
-type => 'polygon',
);
$g->$method(
%$path,
'marker-end' => !$arrows ? "" : "url(#endArrow)",
);
}
} elsif ($type =~ /^(?:(.+?)_)?points$/) {
my $colour = $1 // 'black';
my $r = $colour eq 'black' ? 1 : 3;
$value = [ map $_->pp, @$value ];
my $g = $svg->group(
style => {
'stroke-width' => 2,
'stroke' => $colour,
'fill' => $colour,
},
);
foreach my $point (@$value) {
$g->circle(
cx => $point->[X] * factor(),
cy => $point->[Y] * factor(),
r => $r,
);
}
} elsif ($type =~ /^(?:(.+?)_)?lines$/) {
my $colour = $1;
$value = [ map $_->pp, @$value ];
my $g = $svg->group(
style => {
'stroke-width' => 2,
},
);
foreach my $line (@$value) {
$g->line(
x1 => $line->[0][X] * factor(),
y1 => $line->[0][Y] * factor(),
x2 => $line->[1][X] * factor(),
y2 => $line->[1][Y] * factor(),
style => {
'stroke' => $colour || 'black',
},
'marker-end' => !$arrows ? "" : "url(#endArrow)",
);
}
}
}
write_svg($svg, $filename);
}
sub write_svg {
my ($svg, $filename) = @_;
Slic3r::open(\my $fh, '>', $filename);
print $fh $svg->xmlify;
close $fh;
printf "SVG written to %s\n", $filename;
}
1;
Slic3r-1.3.0/lib/Slic3r/Surface.pm 0000664 0000000 0000000 00000000542 13274424355 0016517 0 ustar 00root root 0000000 0000000 package Slic3r::Surface;
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(S_TYPE_TOP S_TYPE_BOTTOM S_TYPE_BOTTOMBRIDGE S_TYPE_INTERNAL S_TYPE_INTERNALSOLID S_TYPE_INTERNALBRIDGE S_TYPE_INTERNALVOID);
our %EXPORT_TAGS = (types => \@EXPORT_OK);
sub p {
my $self = shift;
return @{$self->polygons};
}
1;
Slic3r-1.3.0/lib/Slic3r/Test.pm 0000664 0000000 0000000 00000300645 13274424355 0016055 0 ustar 00root root 0000000 0000000 package Slic3r::Test;
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(_eq);
use IO::Scalar;
use List::Util qw(first);
use Slic3r::Geometry qw(epsilon X Y Z);
my %cuboids = (
'20mm_cube' => [20,20,20],
'2x20x10' => [2, 20,10],
);
sub mesh {
my ($name, %params) = @_;
my ($vertices, $facets);
if ($cuboids{$name}) {
my ($x, $y, $z) = @{ $cuboids{$name} };
$vertices = [
[$x,$y,0], [$x,0,0], [0,0,0], [0,$y,0], [$x,$y,$z], [0,$y,$z], [0,0,$z], [$x,0,$z],
];
$facets = [
[0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5],
],
} elsif ($name eq 'box') {
my ($x, $y, $z) = @{ $params{"dim"} };
$vertices = [
[$x,$y,0], [$x,0,0], [0,0,0], [0,$y,0], [$x,$y,$z], [0,$y,$z], [0,0,$z], [$x,0,$z],
];
$facets = [
[0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5],
],
} elsif ($name eq 'cube_with_hole') {
$vertices = [
[0,0,0],[0,0,10],[0,20,0],[0,20,10],[20,0,0],[20,0,10],[5,5,0],[15,5,0],[5,15,0],[20,20,0],[15,15,0],[20,20,10],[5,5,10],[5,15,10],[15,5,10],[15,15,10]
];
$facets = [
[0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,7,4],[8,2,9],[0,2,8],[10,8,9],[0,8,6],[0,6,4],[4,7,9],[7,10,9],[2,3,9],[9,3,11],[12,1,5],[13,3,12],[14,12,5],[3,1,12],[11,3,13],[11,15,5],[11,13,15],[15,14,5],[5,4,9],[11,5,9],[8,13,12],[6,8,12],[10,15,13],[8,10,13],[15,10,14],[14,10,7],[14,7,12],[12,7,6]
],
} elsif ($name eq 'cube_with_concave_hole') {
$vertices = [
[-10,-10,-5],[-10,-10,5],[-10,10,-5],[-10,10,5],[10,-10,-5],[10,-10,5],[-5,-5,-5],[5,-5,-5],[5,5,-5],[5,10,-5],[-5,5,-5],[3.06161699911402e-16,5,-5],[5,0,-5],[0,0,-5],[10,5,-5],[5,10,5],[-5,-5,5],[5,0,5],[5,-5,5],[-5,5,5],[10,5,5],[5,5,5],[3.06161699911402e-16,5,5],[0,0,5]
];
$facets = [
[0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,7,4],[8,2,9],[10,2,11],[11,12,13],[0,2,10],[0,10,6],[0,6,4],[11,2,8],[4,7,12],[4,12,8],[12,11,8],[14,4,8],[2,3,9],[9,3,15],[16,1,5],[17,18,5],[19,3,16],[20,21,5],[18,16,5],[3,1,16],[22,3,19],[21,3,22],[21,17,5],[21,22,17],[21,15,3],[23,17,22],[5,4,14],[20,5,14],[20,14,21],[21,14,8],[9,15,21],[8,9,21],[10,19,16],[6,10,16],[11,22,19],[10,11,19],[13,23,11],[11,23,22],[23,13,12],[17,23,12],[17,12,18],[18,12,7],[18,7,16],[16,7,6]
],
} elsif ($name eq 'V') {
$vertices = [
[-14,0,20],[-14,15,20],[0,0,0],[0,15,0],[-4,0,20],[-4,15,20],[5,0,7.14286],[10,0,0],[24,0,20],[14,0,20],[10,15,0],[5,15,7.14286],[14,15,20],[24,15,20]
];
$facets = [
[0,1,2],[2,1,3],[1,0,4],[5,1,4],[4,0,2],[6,4,2],[7,6,2],[8,9,7],[9,6,7],[2,3,7],[7,3,10],[1,5,3],[3,5,11],[11,12,13],[11,13,3],[3,13,10],[5,4,6],[11,5,6],[6,9,11],[11,9,12],[12,9,8],[13,12,8],[8,7,10],[13,8,10]
],
} elsif ($name eq 'L') {
$vertices = [
[0,10,0],[0,10,10],[0,20,0],[0,20,10],[10,10,0],[10,10,10],[20,20,0],[20,0,0],[10,0,0],[20,20,10],[10,0,10],[20,0,10]
];
$facets = [
[0,1,2],[2,1,3],[4,5,1],[0,4,1],[0,2,4],[4,2,6],[4,6,7],[4,7,8],[2,3,6],[6,3,9],[3,1,5],[9,3,5],[10,11,5],[11,9,5],[5,4,10],[10,4,8],[10,8,7],[11,10,7],[11,7,6],[9,11,6]
],
} elsif ($name eq 'overhang') {
$vertices = [
[1364.68505859375,614.398010253906,20.002498626709],[1389.68505859375,614.398010253906,20.002498626709],[1377.18505859375,589.398986816406,20.002498626709],[1389.68505859375,589.398986816406,20.002498626709],[1389.68505859375,564.398986816406,20.0014991760254],[1364.68505859375,589.398986816406,20.002498626709],[1364.68505859375,564.398986816406,20.0014991760254],[1360.93505859375,589.398986816406,17.0014991760254],[1360.93505859375,585.64697265625,17.0014991760254],[1357.18505859375,564.398986816406,17.0014991760254],[1364.68505859375,589.398986816406,17.0014991760254],[1364.68505859375,571.899963378906,17.0014991760254],[1364.68505859375,564.398986816406,17.0014991760254],[1348.43603515625,564.398986816406,17.0014991760254],[1352.80908203125,589.398986816406,17.0014991760254],[1357.18408203125,589.398986816406,17.0014991760254],[1357.18310546875,614.398010253906,17.0014991760254],[1364.68505859375,606.89599609375,17.0014991760254],[1364.68505859375,614.398010253906,17.0014991760254],[1352.18603515625,564.398986816406,20.0014991760254],[1363.65405273438,589.398986816406,23.3004989624023],[1359.46704101562,589.398986816406,23.3004989624023],[1358.37109375,564.398986816406,23.3004989624023],[1385.56103515625,564.398986816406,23.3004989624023],[1373.06311035156,589.398986816406,23.3004989624023],[1368.80810546875,564.398986816406,23.3004989624023],[1387.623046875,589.398986816406,23.3004989624023],[1387.623046875,585.276000976562,23.3004989624023],[1389.68505859375,589.398986816406,23.3004989624023],[1389.68505859375,572.64599609375,23.3004989624023],[1389.68505859375,564.398986816406,23.3004989624023],[1367.77709960938,589.398986816406,23.3004989624023],[1366.7470703125,564.398986816406,23.3004989624023],[1354.31201171875,589.398986816406,23.3004989624023],[1352.18603515625,564.398986816406,23.3004989624023],[1389.68505859375,614.398010253906,23.3004989624023],[1377.31701660156,614.398010253906,23.3004989624023],[1381.43908691406,589.398986816406,23.3004989624023],[1368.80700683594,614.398010253906,23.3004989624023],[1368.80810546875,589.398986816406,23.3004989624023],[1356.43908691406,614.398010253906,23.3004989624023],[1357.40502929688,589.398986816406,23.3004989624023],[1360.56201171875,614.398010253906,23.3004989624023],[1348.705078125,614.398010253906,23.3004989624023],[1350.44506835938,589.398986816406,23.3004989624023],[1389.68505859375,606.153015136719,23.3004989624023],[1347.35205078125,589.398986816406,23.3004989624023],[1346.56005859375,589.398986816406,23.3004989624023],[1346.56005859375,594.159912109375,17.0014991760254],[1346.56005859375,589.398986816406,17.0014991760254],[1346.56005859375,605.250427246094,23.3004989624023],[1346.56005859375,614.398010253906,23.3004989624023],[1346.56005859375,614.398010253906,20.8258285522461],[1346.56005859375,614.398010253906,17.0014991760254],[1346.56005859375,564.398986816406,19.10133934021],[1346.56005859375,567.548583984375,23.3004989624023],[1346.56005859375,564.398986816406,17.0020332336426],[1346.56005859375,564.398986816406,23.0018501281738],[1346.56005859375,564.398986816406,23.3004989624023],[1346.56005859375,575.118957519531,17.0014991760254],[1346.56005859375,574.754028320312,23.3004989624023]
];
$facets = [
[0,1,2],[2,3,4],[2,5,0],[4,6,2],[2,6,5],[2,1,3],[7,8,9],[10,9,8],[11,9,10],[12,9,11],[9,13,14],[7,15,16],[10,17,0],[10,0,5],[12,11,6],[18,16,0],[6,19,13],[6,13,9],[9,12,6],[17,18,0],[11,10,5],[11,5,6],[14,16,15],[17,7,18],[16,18,7],[14,15,9],[7,9,15],[7,17,8],[10,8,17],[20,21,22],[23,24,25],[26,23,27],[28,27,23],[29,28,23],[30,29,23],[25,31,32],[22,33,34],[35,36,37],[24,38,39],[21,40,41],[38,42,20],[33,43,44],[6,4,23],[6,23,25],[36,35,1],[1,0,38],[1,38,36],[29,30,4],[25,32,6],[40,42,0],[35,45,1],[4,3,28],[4,28,29],[3,1,45],[3,45,28],[22,34,19],[19,6,32],[19,32,22],[42,38,0],[30,23,4],[0,16,43],[0,43,40],[24,37,36],[38,24,36],[24,23,37],[37,23,26],[22,32,20],[20,32,31],[33,41,40],[43,33,40],[45,35,26],[37,26,35],[33,44,34],[44,43,46],[20,42,21],[40,21,42],[31,39,38],[20,31,38],[33,22,41],[21,41,22],[31,25,39],[24,39,25],[26,27,45],[28,45,27],[47,48,49],[47,50,48],[51,48,50],[52,48,51],[53,48,52],[54,55,56],[57,55,54],[58,55,57],[49,59,47],[60,56,55],[59,56,60],[60,47,59],[48,53,16],[56,13,19],[54,56,19],[56,59,13],[59,49,14],[59,14,13],[49,48,16],[49,16,14],[44,46,60],[44,60,55],[51,50,43],[19,34,58],[19,58,57],[53,52,16],[43,16,52],[43,52,51],[57,54,19],[47,60,46],[55,58,34],[55,34,44],[50,47,46],[50,46,43]
],
} elsif ($name eq '40x10') {
$vertices = [
[12.8680295944214,29.5799007415771,12],[11.7364797592163,29.8480796813965,12],[11.1571502685547,29.5300102233887,12],[10.5814504623413,29.9830799102783,12],[10,29.6000003814697,12],[9.41855144500732,29.9830799102783,12],[8.84284687042236,29.5300102233887,12],[8.26351833343506,29.8480796813965,12],[7.70256900787354,29.3210391998291,12],[7.13196802139282,29.5799007415771,12],[6.59579277038574,28.9761600494385,12],[6.03920221328735,29.1821594238281,12],[5.53865718841553,28.5003795623779,12],[5,28.6602592468262,12],[4.54657793045044,27.9006500244141,12],[4.02841377258301,28.0212306976318,12],[3.63402199745178,27.1856994628906,12],[3.13758301734924,27.2737407684326,12],[2.81429696083069,26.3659801483154,12],[2.33955597877502,26.4278793334961,12],[2.0993549823761,25.4534206390381,12],[1.64512205123901,25.4950904846191,12],[1.49962198734283,24.4613399505615,12],[1.0636739730835,24.4879894256592,12],[1.02384400367737,23.4042091369629,12],[0.603073298931122,23.4202003479004,12],[0.678958415985107,22.2974300384521,12],[0.269550800323486,22.3061599731445,12],[0.469994693994522,21.1571502685547,12],[0.067615881562233,21.1609306335449,12],[0.399999290704727,20,12],[0,20,12],[0.399999290704727,5,12],[0,5,12],[0.456633001565933,4.2804012298584,12],[0.0615576282143593,4.21782684326172,12],[0.625140011310577,3.5785219669342,12],[0.244717106223106,3.45491504669189,12],[0.901369392871857,2.91164398193359,12],[0.544967114925385,2.73004698753357,12],[1.27852201461792,2.29618692398071,12],[0.954914808273315,2.06107401847839,12],[1.74730801582336,1.74730801582336,12],[1.46446597576141,1.46446597576141,12],[2.29618692398071,1.27852201461792,12],[2.06107401847839,0.954914808273315,12],[2.91164398193359,0.901369392871857,12],[2.73004698753357,0.544967114925385,12],[3.5785219669342,0.625140011310577,12],[3.45491504669189,0.244717106223106,12],[4.2804012298584,0.456633001565933,12],[4.21782684326172,0.0615576282143593,12],[5,0.399999290704727,12],[5,0,12],[19.6000003814697,0.399999290704727,12],[20,0,12],[19.6000003814697,20,12],[20,20,12],[19.5300102233887,21.1571502685547,12],[19.9323806762695,21.1609306335449,12],[19.3210391998291,22.2974300384521,12],[19.7304496765137,22.3061599731445,12],[18.9761600494385,23.4042091369629,12],[19.3969306945801,23.4202003479004,12],[18.5003795623779,24.4613399505615,12],[18.9363307952881,24.4879894256592,12],[17.9006500244141,25.4534206390381,12],[18.3548793792725,25.4950904846191,12],[17.1856994628906,26.3659801483154,12],[17.6604404449463,26.4278793334961,12],[16.3659801483154,27.1856994628906,12],[16.862419128418,27.2737407684326,12],[15.4534196853638,27.9006500244141,12],[15.9715900421143,28.0212306976318,12],[14.4613399505615,28.5003795623779,12],[15,28.6602592468262,12],[13.4042100906372,28.9761600494385,12],[13.9608001708984,29.1821594238281,12],[12.2974300384521,29.3210391998291,12],[7.13196802139282,29.5799007415771,0],[8.26351833343506,29.8480796813965,0],[8.84284687042236,29.5300102233887,0],[9.41855144500732,29.9830799102783,0],[10,29.6000003814697,0],[10.5814504623413,29.9830799102783,0],[11.1571502685547,29.5300102233887,0],[11.7364797592163,29.8480796813965,0],[12.2974300384521,29.3210391998291,0],[12.8680295944214,29.5799007415771,0],[13.4042100906372,28.9761600494385,0],[13.9608001708984,29.1821594238281,0],[14.4613399505615,28.5003795623779,0],[15,28.6602592468262,0],[15.4534196853638,27.9006500244141,0],[15.9715900421143,28.0212306976318,0],[16.3659801483154,27.1856994628906,0],[16.862419128418,27.2737407684326,0],[17.1856994628906,26.3659801483154,0],[17.6604404449463,26.4278793334961,0],[17.9006500244141,25.4534206390381,0],[18.3548793792725,25.4950904846191,0],[18.5003795623779,24.4613399505615,0],[18.9363307952881,24.4879894256592,0],[18.9761600494385,23.4042091369629,0],[19.3969306945801,23.4202003479004,0],[19.3210391998291,22.2974300384521,0],[19.7304496765137,22.3061599731445,0],[19.5300102233887,21.1571502685547,0],[19.9323806762695,21.1609306335449,0],[19.6000003814697,20,0],[20,20,0],[19.6000003814697,0.399999290704727,0],[20,0,0],[5,0.399999290704727,0],[5,0,0],[4.2804012298584,0.456633001565933,0],[4.21782684326172,0.0615576282143593,0],[3.5785219669342,0.625140011310577,0],[3.45491504669189,0.244717106223106,0],[2.91164398193359,0.901369392871857,0],[2.73004698753357,0.544967114925385,0],[2.29618692398071,1.27852201461792,0],[2.06107401847839,0.954914808273315,0],[1.74730801582336,1.74730801582336,0],[1.46446597576141,1.46446597576141,0],[1.27852201461792,2.29618692398071,0],[0.954914808273315,2.06107401847839,0],[0.901369392871857,2.91164398193359,0],[0.544967114925385,2.73004698753357,0],[0.625140011310577,3.5785219669342,0],[0.244717106223106,3.45491504669189,0],[0.456633001565933,4.2804012298584,0],[0.0615576282143593,4.21782684326172,0],[0.399999290704727,5,0],[0,5,0],[0.399999290704727,20,0],[0,20,0],[0.469994693994522,21.1571502685547,0],[0.067615881562233,21.1609306335449,0],[0.678958415985107,22.2974300384521,0],[0.269550800323486,22.3061599731445,0],[1.02384400367737,23.4042091369629,0],[0.603073298931122,23.4202003479004,0],[1.49962198734283,24.4613399505615,0],[1.0636739730835,24.4879894256592,0],[2.0993549823761,25.4534206390381,0],[1.64512205123901,25.4950904846191,0],[2.81429696083069,26.3659801483154,0],[2.33955597877502,26.4278793334961,0],[3.63402199745178,27.1856994628906,0],[3.13758301734924,27.2737407684326,0],[4.54657793045044,27.9006500244141,0],[4.02841377258301,28.0212306976318,0],[5.53865718841553,28.5003795623779,0],[5,28.6602592468262,0],[6.59579277038574,28.9761600494385,0],[6.03920221328735,29.1821594238281,0],[7.70256900787354,29.3210391998291,0]
];
$facets = [
[0,1,2],[2,1,3],[2,3,4],[4,3,5],[4,5,6],[6,5,7],[6,7,8],[8,7,9],[8,9,10],[10,9,11],[10,11,12],[12,11,13],[12,13,14],[14,13,15],[14,15,16],[16,15,17],[16,17,18],[18,17,19],[18,19,20],[20,19,21],[20,21,22],[22,21,23],[22,23,24],[24,23,25],[24,25,26],[26,25,27],[26,27,28],[28,27,29],[28,29,30],[30,29,31],[30,31,32],[32,31,33],[32,33,34],[34,33,35],[34,35,36],[36,35,37],[36,37,38],[38,37,39],[38,39,40],[40,39,41],[40,41,42],[42,41,43],[42,43,44],[44,43,45],[44,45,46],[46,45,47],[46,47,48],[48,47,49],[48,49,50],[50,49,51],[50,51,52],[52,51,53],[52,53,54],[54,53,55],[54,55,56],[56,55,57],[56,57,58],[58,57,59],[58,59,60],[60,59,61],[60,61,62],[62,61,63],[62,63,64],[64,63,65],[64,65,66],[66,65,67],[66,67,68],[68,67,69],[68,69,70],[70,69,71],[70,71,72],[72,71,73],[72,73,74],[74,73,75],[74,75,76],[76,75,77],[76,77,78],[78,77,0],[78,0,2],[79,80,81],[81,80,82],[81,82,83],[83,82,84],[83,84,85],[85,84,86],[85,86,87],[87,86,88],[87,88,89],[89,88,90],[89,90,91],[91,90,92],[91,92,93],[93,92,94],[93,94,95],[95,94,96],[95,96,97],[97,96,98],[97,98,99],[99,98,100],[99,100,101],[101,100,102],[101,102,103],[103,102,104],[103,104,105],[105,104,106],[105,106,107],[107,106,108],[107,108,109],[109,108,110],[109,110,111],[111,110,112],[111,112,113],[113,112,114],[113,114,115],[115,114,116],[115,116,117],[117,116,118],[117,118,119],[119,118,120],[119,120,121],[121,120,122],[121,122,123],[123,122,124],[123,124,125],[125,124,126],[125,126,127],[127,126,128],[127,128,129],[129,128,130],[129,130,131],[131,130,132],[131,132,133],[133,132,134],[133,134,135],[135,134,136],[135,136,137],[137,136,138],[137,138,139],[139,138,140],[139,140,141],[141,140,142],[141,142,143],[143,142,144],[143,144,145],[145,144,146],[145,146,147],[147,146,148],[147,148,149],[149,148,150],[149,150,151],[151,150,152],[151,152,153],[153,152,154],[153,154,155],[155,154,156],[155,156,157],[157,156,79],[157,79,81],[57,110,108],[57,108,59],[59,108,106],[59,106,61],[61,106,104],[61,104,63],[63,104,102],[63,102,65],[65,102,100],[65,100,67],[67,100,98],[67,98,69],[69,98,96],[69,96,71],[71,96,94],[71,94,73],[73,94,92],[73,92,75],[75,92,90],[75,90,77],[77,90,88],[77,88,0],[0,88,86],[0,86,1],[1,86,84],[1,84,3],[3,84,82],[3,82,5],[5,82,80],[5,80,7],[7,80,79],[7,79,9],[9,79,156],[9,156,11],[11,156,154],[11,154,13],[13,154,152],[13,152,15],[15,152,150],[15,150,17],[17,150,148],[17,148,19],[19,148,146],[19,146,21],[21,146,144],[21,144,23],[23,144,142],[23,142,25],[25,142,140],[25,140,27],[27,140,138],[27,138,29],[29,138,136],[29,136,31],[33,31,134],[134,31,136],[33,134,132],[33,132,35],[35,132,130],[35,130,37],[37,130,128],[37,128,39],[39,128,126],[39,126,41],[41,126,124],[41,124,43],[43,124,122],[43,122,45],[45,122,120],[45,120,47],[47,120,118],[47,118,49],[49,118,116],[49,116,51],[51,116,114],[51,114,53],[55,53,112],[112,53,114],[57,55,110],[110,55,112],[30,135,137],[30,137,28],[28,137,139],[28,139,26],[26,139,141],[26,141,24],[24,141,143],[24,143,22],[22,143,145],[22,145,20],[20,145,147],[20,147,18],[18,147,149],[18,149,16],[16,149,151],[16,151,14],[14,151,153],[14,153,12],[12,153,155],[12,155,10],[10,155,157],[10,157,8],[8,157,81],[8,81,6],[6,81,83],[6,83,4],[4,83,85],[4,85,2],[2,85,87],[2,87,78],[78,87,89],[78,89,76],[76,89,91],[76,91,74],[74,91,93],[74,93,72],[72,93,95],[72,95,70],[70,95,97],[70,97,68],[68,97,99],[68,99,66],[66,99,101],[66,101,64],[64,101,103],[64,103,62],[62,103,105],[62,105,60],[60,105,107],[60,107,58],[58,107,109],[58,109,56],[30,32,135],[135,32,133],[52,113,115],[52,115,50],[50,115,117],[50,117,48],[48,117,119],[48,119,46],[46,119,121],[46,121,44],[44,121,123],[44,123,42],[42,123,125],[42,125,40],[40,125,127],[40,127,38],[38,127,129],[38,129,36],[36,129,131],[36,131,34],[34,131,133],[34,133,32],[52,54,113],[113,54,111],[54,56,111],[111,56,109]
],
} elsif ($name eq 'sloping_hole') {
$vertices = [
[-20,-20,-5],[-20,-20,5],[-20,20,-5],[-20,20,5],[20,-20,-5],[20,-20,5],[4.46294021606445,7.43144989013672,-5],[20,20,-5],[-19.1420993804932,0,-5],[-18.8330993652344,-2.07911992073059,-5],[-17.9195003509521,-4.06736993789673,-5],[-16.4412002563477,-5.87785005569458,-5],[-14.4629001617432,-7.43144989013672,-5],[-12.0711002349854,-8.66024971008301,-5],[-9.37016010284424,-9.51056003570557,-5],[-3.5217399597168,-9.94521999359131,-5],[-6.4782600402832,-9.94521999359131,-5],[-0.629840016365051,-9.51056003570557,-5],[2.07106995582581,-8.66024971008301,-5],[6.44122982025146,-5.87785005569458,-5],[4.46294021606445,-7.43144989013672,-5],[-12.0711002349854,8.66024971008301,-5],[-9.37016010284424,9.51056003570557,-5],[7.91947984695435,-4.06736993789673,-5],[8.83310031890869,-2.07911992073059,-5],[-6.4782600402832,9.94521999359131,-5],[-0.629840016365051,9.51056003570557,-5],[2.07106995582581,8.66024971008301,-5],[9.14214038848877,0,-5],[8.83310031890869,2.07911992073059,-5],[-3.5217399597168,9.94521999359131,-5],[7.91947984695435,4.06736993789673,-5],[6.44122982025146,5.87785005569458,-5],[-14.4629001617432,7.43144989013672,-5],[-16.4412002563477,5.87785005569458,-5],[-17.9195003509521,4.06736993789673,-5],[-18.8330993652344,2.07911992073059,-5],[20,20,5],[3.5217399597168,-9.94521999359131,5],[-8.83310031890869,-2.07911992073059,5],[-9.14214038848877,0,5],[-8.83310031890869,2.07911992073059,5],[6.4782600402832,-9.94521999359131,5],[-7.91947984695435,4.06736993789673,5],[-6.44122982025146,5.87785005569458,5],[-4.46294021606445,7.43144989013672,5],[-2.07106995582581,8.66024971008301,5],[0.629840016365051,9.51056003570557,5],[12.0711002349854,-8.66024971008301,5],[9.37016010284424,-9.51056003570557,5],[3.5217399597168,9.94521999359131,5],[6.4782600402832,9.94521999359131,5],[9.37016010284424,9.51056003570557,5],[12.0711002349854,8.66024971008301,5],[14.4629001617432,7.43144989013672,5],[16.4412002563477,-5.87785005569458,5],[14.4629001617432,-7.43144989013672,5],[16.4412002563477,5.87785005569458,5],[17.9195003509521,4.06736993789673,5],[18.8330993652344,-2.07911992073059,5],[17.9195003509521,-4.06736993789673,5],[18.8330993652344,2.07911992073059,5],[19.1420993804932,0,5],[0.629840016365051,-9.51056003570557,5],[-2.07106995582581,-8.66024971008301,5],[-4.46294021606445,-7.43144989013672,5],[-6.44122982025146,-5.87785005569458,5],[-7.91947984695435,-4.06736993789673,5]
];
$facets = [
[0,1,2],[2,1,3],[1,0,4],[5,1,4],[6,2,7],[0,2,8],[0,8,9],[0,9,10],[0,10,11],[0,11,12],[0,12,13],[0,13,4],[13,14,4],[15,4,16],[17,4,15],[18,4,17],[19,4,20],[18,20,4],[21,2,22],[4,19,23],[4,23,7],[23,24,7],[22,2,25],[26,2,27],[28,29,7],[25,2,30],[29,31,7],[30,2,26],[31,32,7],[27,2,6],[32,6,7],[28,7,24],[33,2,21],[34,2,33],[35,2,34],[36,2,35],[8,2,36],[16,4,14],[2,3,7],[7,3,37],[38,1,5],[3,1,39],[3,39,40],[3,40,41],[42,38,5],[3,41,43],[3,43,44],[37,3,45],[37,45,46],[37,46,47],[48,49,5],[37,47,50],[49,42,5],[37,50,51],[37,51,52],[37,52,53],[37,53,54],[55,56,5],[37,54,57],[37,57,58],[59,60,5],[37,58,61],[37,62,5],[37,61,62],[62,59,5],[60,55,5],[63,1,38],[64,1,63],[65,1,64],[66,1,65],[67,1,66],[39,1,67],[44,45,3],[56,48,5],[5,4,7],[37,5,7],[41,40,36],[36,40,8],[39,9,40],[40,9,8],[43,41,35],[35,41,36],[44,43,34],[34,43,35],[33,45,44],[34,33,44],[21,46,45],[33,21,45],[22,47,46],[21,22,46],[25,50,47],[22,25,47],[30,51,50],[25,30,50],[26,52,51],[30,26,51],[27,53,52],[26,27,52],[6,54,53],[27,6,53],[32,57,54],[6,32,54],[31,58,57],[32,31,57],[29,61,58],[31,29,58],[28,62,61],[29,28,61],[59,62,28],[24,59,28],[60,59,24],[23,60,24],[55,60,23],[19,55,23],[55,19,56],[56,19,20],[56,20,48],[48,20,18],[48,18,49],[49,18,17],[49,17,42],[42,17,15],[42,15,38],[38,15,16],[38,16,63],[63,16,14],[63,14,64],[64,14,13],[64,13,65],[65,13,12],[65,12,66],[66,12,11],[66,11,67],[67,11,10],[67,10,39],[39,10,9]
],
} elsif ($name eq 'ipadstand') {
$vertices = [
[17.4344673156738,-2.69879599481136e-16,9.5],[14.2814798355103,10,9.5],[0,0,9.5],[31.7159481048584,10,9.5],[62.2344741821289,2.06667568800577e-16,20],[31.7159481048584,10,20],[17.4344673156738,-2.69879599481136e-16,20],[62.2344741821289,10,20],[98.2079696655273,10,0],[98.2079696655273,8.56525380796383e-16,10],[98.2079696655273,0,0],[98.2079696655273,10,20],[98.2079696655273,0,20],[81.6609649658203,-4.39753856997999e-16,10],[90.0549850463867,10,10],[78.5079803466797,10,10],[93.2079696655273,8.56525380796383e-16,10],[14.2814798355103,10,20],[0,0,20],[87.4344711303711,2.81343962782118e-15,20],[84.2814788818359,10,20],[0,10,20],[0,0,0],[0,10,0],[62.2344741821289,2.06667568800577e-16,30],[66.9609756469727,10,30],[62.2344741821289,10,30],[70.1139602661133,8.5525763717214e-16,30],[67.7053375244141,10,28.7107200622559],[71.6787109375,1.24046736339707e-15,27.2897701263428]
];
$facets = [
[0,1,2],[1,0,3],[4,5,6],[5,4,7],[8,9,10],[9,11,12],[11,9,8],[13,14,15],[14,13,16],[17,2,1],[2,17,18],[19,11,20],[11,19,12],[17,21,18],[21,2,18],[2,21,22],[22,21,23],[8,22,23],[22,8,10],[24,25,26],[25,24,27],[23,1,8],[1,23,21],[1,21,17],[5,15,3],[15,5,7],[15,7,28],[28,7,26],[28,26,25],[8,14,11],[14,8,3],[3,8,1],[14,3,15],[11,14,20],[26,4,24],[4,26,7],[12,16,9],[16,12,19],[29,4,13],[4,29,24],[24,29,27],[9,22,10],[22,9,0],[0,9,16],[0,16,13],[0,13,6],[6,13,4],[2,22,0],[19,14,16],[14,19,20],[15,29,13],[29,25,27],[25,29,15],[25,15,28],[6,3,0],[3,6,5]
];
} elsif ($name eq 'A') {
$vertices = [
[513.075988769531,51.6074333190918,36.0009002685547],[516.648803710938,51.7324333190918,36.0009002685547],[513.495178222656,51.7324333190918,36.0009002685547],[489.391204833984,51.4824333190918,24.0011005401611],[488.928588867188,51.7324333190918,24.0011005401611],[492.06201171875,51.7324333190918,24.0011005401611],[496.840393066406,51.2324333190918,24.0011005401611],[495.195404052734,51.7324333190918,24.0011005401611],[498.981994628906,51.7324333190918,24.0011005401611],[506.966613769531,51.6074333190918,24.0011005401611],[510.342010498047,51.7324333190918,24.0011005401611],[507.163818359375,51.6074333190918,24.0011005401611],[512.515380859375,54.7190322875977,36.0009002685547],[514.161987304688,54.5058326721191,36.0009002685547],[493.06201171875,54.7190322875977,36.0009002685547],[495.195404052734,51.7324333190918,36.0009002685547],[496.195404052734,54.7190322875977,36.0009002685547],[497.195404052734,57.7058334350586,36.0009002685547],[500.851989746094,60.2658309936523,36.0009002685547],[498.915405273438,62.8258323669434,36.0009002685547],[506.701995849609,62.8258323669434,36.0009002685547],[503.648590087891,60.2658309936523,36.0009002685547],[508.381805419922,57.7058334350586,36.0009002685547],[496.418792724609,60.052433013916,36.0009002685547],[506.515197753906,72.2124328613281,36.0009002685547],[502.808807373047,74.5324325561523,36.0009002685547],[503.781982421875,71.6058349609375,36.0009002685547],[515.358764648438,55.4658317565918,36.0009002685547],[499.375183105469,76.9058380126953,36.0009002685547],[501.168792724609,78.0658340454102,36.0009002685547],[504.568786621094,78.0658340454102,36.0009002685547],[506.32861328125,81.599235534668,36.0009002685547],[502.928588867188,81.599235534668,36.0009002685547],[499.528594970703,81.599235534668,36.0009002685547],[498.20361328125,77.8658294677734,36.0009002685547],[495.195404052734,51.7324333190918,30.0011005401611],[498.981994628906,51.7324333190918,27.0011005401611],[506.555206298828,51.7324333190918,33.0009002685547],[506.555206298828,51.7324333190918,36.0009002685547],[510.342010498047,51.7324333190918,36.0009002685547],[512.515380859375,54.7190322875977,24.0011005401611],[509.361999511719,54.7190322875977,24.0011005401611],[508.381805419922,57.7058334350586,24.0011005401611],[506.701995849609,62.8258323669434,24.0011005401611],[509.188812255859,60.052433013916,24.0011005401611],[493.06201171875,54.7190322875977,24.0011005401611],[503.648590087891,60.2658309936523,24.0011005401611],[500.851989746094,60.2658309936523,24.0011005401611],[498.915405273438,62.8258323669434,24.0011005401611],[502.808807373047,62.8258323669434,24.0011005401611],[491.425201416016,54.5058326721191,24.0011005401611],[506.421813964844,76.9058380126953,24.0011005401611],[502.808807373047,74.5324325561523,24.0011005401611],[504.568786621094,78.0658340454102,24.0011005401611],[506.32861328125,81.599235534668,24.0011005401611],[507.618804931641,77.8658294677734,24.0011005401611],[499.221801757812,72.2124328613281,24.0011005401611],[501.835388183594,71.6058349609375,24.0011005401611],[501.168792724609,78.0658340454102,24.0011005401611],[499.528594970703,81.599235534668,24.0011005401611],[502.048583984375,79.8324356079102,24.0011005401611],[490.253601074219,55.4658317565918,24.0011005401611],[488.928588867188,51.7324333190918,30.0011005401611],[488.928588867188,51.7324333190918,36.0009002685547],[490.253601074219,55.4658317565918,31.5009002685547],[498.20361328125,77.8658294677734,34.5009002685547],[508.381805419922,57.7058334350586,30.0011005401611],[505.585388183594,57.7058334350586,27.0011005401611],[502.788818359375,57.7058334350586,36.0009002685547],[499.992004394531,57.7058334350586,33.0009002685547],[509.851989746094,53.2258338928223,33.0009002685547],[509.361999511719,54.7190322875977,36.0009002685547],[508.871795654297,56.2124328613281,27.0011005401611],[496.695404052734,56.2124328613281,33.0009002685547],[495.695404052734,53.2258338928223,27.0011005401611],[506.32861328125,81.599235534668,30.0011005401611],[507.618804931641,77.8658294677734,25.5011005401611],[515.358764648438,55.4658317565918,34.5009002685547],[501.228607177734,81.599235534668,33.0009002685547],[504.628601074219,81.599235534668,27.0011005401611],[503.781982421875,71.6058349609375,33.0009002685547],[502.808807373047,74.5324325561523,30.0011005401611],[498.915405273438,62.8258323669434,30.0011005401611],[500.861999511719,62.8258323669434,27.0011005401611],[502.808807373047,62.8258323669434,36.0009002685547],[504.755187988281,62.8258323669434,33.0009002685547],[501.835388183594,71.6058349609375,33.0009002685547],[499.888793945312,65.7524337768555,33.0009002685547],[499.888793945312,65.7524337768555,36.0009002685547],[513.128601074219,51.4824333190918,36.0009002685547],[513.075988769531,51.6074333190918,24.0011005401611],[516.648803710938,51.7324333190918,24.0011005401611],[513.128601074219,51.4824333190918,24.0011005401611],[513.495178222656,51.7324333190918,24.0011005401611],[506.966613769531,51.6074333190918,36.0009002685547],[507.163818359375,51.6074333190918,36.0009002685547],[490.337799072266,51.4824333190918,24.0011005401611],[489.391204833984,51.4824333190918,36.0009002685547],[492.06201171875,51.7324333190918,36.0009002685547],[490.337799072266,51.4824333190918,36.0009002685547],[513.233764648438,51.2324333190918,24.0011005401611],[513.233764648438,51.2324333190918,36.0009002685547],[504.773803710938,51.4824333190918,36.0009002685547],[504.773803710938,51.4824333190918,24.0011005401611],[489.266998291016,51.2324333190918,24.0011005401611],[489.266998291016,51.2324333190918,36.0009002685547],[490.253601074219,55.4658317565918,25.5011005401611],[499.528594970703,81.599235534668,30.0011005401611],[498.20361328125,77.8658294677734,31.5009002685547],[515.358764648438,55.4658317565918,28.5011005401611],[515.358764648438,55.4658317565918,25.5011005401611],[495.246795654297,61.0124320983887,36.0009002685547],[490.253601074219,55.4658317565918,34.5009002685547],[490.253601074219,55.4658317565918,36.0009002685547],[494.228607177734,66.6658325195312,24.0011005401611],[499.068786621094,67.5192337036133,24.0011005401611],[498.20361328125,77.8658294677734,25.5011005401611],[498.20361328125,77.8658294677734,24.0011005401611],[506.608795166016,67.5192337036133,36.0009002685547],[509.09521484375,64.7458343505859,36.0009002685547],[507.618804931641,77.8658294677734,34.5009002685547],[507.618804931641,77.8658294677734,36.0009002685547],[510.385406494141,61.0124320983887,24.0011005401611],[515.358764648438,55.4658317565918,24.0011005401611],[489.32861328125,47.7324333190918,31.5009002685547],[492.95361328125,47.7324333190918,33.5634994506836],[489.32861328125,47.7324333190918,34.5009002685547],[489.32861328125,47.7324333190918,28.5011005401611],[489.32861328125,47.7324333190918,25.5011005401611],[492.95361328125,47.7324333190918,26.4385013580322],[492.95361328125,47.7324333190918,30.5635013580322],[492.95361328125,47.7324333190918,32.0634994506836],[492.95361328125,47.7324333190918,31.3135013580322],[492.95361328125,47.7324333190918,35.4384994506836],[489.32861328125,47.7324333190918,36.0009002685547],[492.95361328125,47.7324333190918,34.3134994506836],[492.95361328125,47.7324333190918,34.6884994506836],[492.95361328125,47.7324333190918,27.9385013580322],[492.95361328125,47.7324333190918,28.6885013580322],[492.95361328125,47.7324333190918,29.0635013580322],[489.32861328125,47.7324333190918,24.0011005401611],[492.95361328125,47.7324333190918,24.5635013580322],[492.95361328125,47.7324333190918,25.6885013580322],[492.95361328125,47.7324333190918,25.3135013580322],[492.95361328125,47.7324333190918,24.1885013580322],[492.95361328125,47.7324333190918,24.0011005401611],[513.443786621094,50.7324333190918,24.0011005401611],[492.95361328125,47.7324333190918,35.8134994506836],[492.95361328125,47.7324333190918,36.0009002685547],[513.443786621094,50.7324333190918,36.0009002685547],[506.350402832031,51.4824333190918,36.0009002685547],[506.350402832031,51.4824333190918,24.0011005401611],[492.743804931641,48.2324333190918,24.0011005401611],[492.638793945312,48.4824333190918,24.0011005401611],[492.743804931641,48.2324333190918,36.0009002685547],[492.638793945312,48.4824333190918,36.0009002685547],[490.089599609375,50.9824333190918,36.0009002685547],[490.089599609375,50.9824333190918,24.0011005401611],[510.342010498047,51.7324333190918,30.0011005401611],[499.068786621094,67.5192337036133,36.0009002685547],[494.228607177734,66.6658325195312,36.0009002685547],[499.375183105469,76.9058380126953,24.0011005401611],[506.421813964844,76.9058380126953,36.0009002685547],[506.608795166016,67.5192337036133,24.0011005401611],[505.728607177734,65.7524337768555,24.0011005401611],[509.09521484375,64.7458343505859,24.0011005401611],[506.701995849609,62.8258323669434,30.0011005401611],[505.728607177734,65.7524337768555,27.0011005401611],[501.835388183594,71.6058349609375,27.0011005401611],[499.888793945312,65.7524337768555,27.0011005401611],[494.228607177734,66.6658325195312,30.0011005401611],[495.553588867188,70.3992309570312,28.5011005401611],[492.903594970703,62.9324340820312,28.5011005401611],[495.553588867188,70.3992309570312,31.5009002685547],[492.903594970703,62.9324340820312,31.5009002685547],[511.488800048828,66.6658325195312,24.0011005401611],[511.488800048828,66.6658325195312,30.0011005401611],[512.778564453125,62.9324340820312,28.5011005401611],[515.358764648438,55.4658317565918,31.5009002685547],[507.618804931641,77.8658294677734,31.5009002685547],[510.198791503906,70.3992309570312,28.5011005401611],[511.488800048828,66.6658325195312,36.0009002685547],[512.778564453125,62.9324340820312,31.5009002685547],[510.198791503906,70.3992309570312,31.5009002685547],[502.788818359375,57.7058334350586,24.0011005401611],[497.195404052734,57.7058334350586,30.0011005401611],[492.903594970703,62.9324340820312,34.5009002685547],[492.903594970703,62.9324340820312,36.0009002685547],[495.553588867188,70.3992309570312,24.0011005401611],[496.725189208984,69.4392318725586,24.0011005401611],[495.553588867188,70.3992309570312,25.5011005401611],[495.246795654297,61.0124320983887,24.0011005401611],[492.903594970703,62.9324340820312,25.5011005401611],[492.903594970703,62.9324340820312,24.0011005401611],[495.553588867188,70.3992309570312,36.0009002685547],[496.725189208984,69.4392318725586,36.0009002685547],[495.553588867188,70.3992309570312,34.5009002685547],[510.198791503906,70.3992309570312,36.0009002685547],[509.002014160156,69.4392318725586,36.0009002685547],[510.198791503906,70.3992309570312,34.5009002685547],[512.778564453125,62.9324340820312,25.5011005401611],[512.778564453125,62.9324340820312,24.0011005401611],[510.198791503906,70.3992309570312,24.0011005401611],[509.002014160156,69.4392318725586,24.0011005401611],[510.198791503906,70.3992309570312,25.5011005401611],[510.385406494141,61.0124320983887,36.0009002685547],[512.778564453125,62.9324340820312,34.5009002685547],[512.778564453125,62.9324340820312,36.0009002685547],[496.840393066406,51.2324333190918,36.0009002685547],[498.981994628906,51.7324333190918,36.0009002685547],[498.981994628906,51.7324333190918,33.0009002685547],[506.555206298828,51.7324333190918,24.0011005401611],[506.555206298828,51.7324333190918,27.0011005401611],[503.82861328125,47.7324333190918,30.7509002685547],[507.45361328125,47.7324333190918,32.8134994506836],[503.82861328125,47.7324333190918,33.7509002685547],[503.82861328125,47.7324333190918,29.2511005401611],[503.82861328125,47.7324333190918,26.2511005401611],[507.45361328125,47.7324333190918,27.1885013580322],[493.921813964844,57.2792320251465,36.0009002685547],[491.425201416016,54.5058326721191,36.0009002685547],[497.195404052734,57.7058334350586,24.0011005401611],[496.418792724609,60.052433013916,24.0011005401611],[509.188812255859,60.052433013916,36.0009002685547],[511.675415039062,57.2792320251465,24.0011005401611],[514.161987304688,54.5058326721191,24.0011005401611],[507.45361328125,47.7324333190918,34.3134994506836],[503.82861328125,47.7324333190918,35.2509002685547],[507.45361328125,47.7324333190918,25.6885013580322],[503.82861328125,47.7324333190918,24.7511005401611],[500.20361328125,47.7324333190918,31.6885013580322],[500.20361328125,47.7324333190918,28.3135013580322],[500.20361328125,47.7324333190918,30.1885013580322],[507.45361328125,47.7324333190918,29.8135013580322],[507.45361328125,47.7324333190918,31.3135013580322],[507.45361328125,47.7324333190918,30.5635013580322],[503.82861328125,47.7324333190918,36.0009002685547],[507.45361328125,47.7324333190918,35.4384994506836],[507.45361328125,47.7324333190918,35.0634994506836],[507.45361328125,47.7324333190918,28.6885013580322],[507.45361328125,47.7324333190918,29.4385013580322],[503.82861328125,47.7324333190918,24.0011005401611],[507.45361328125,47.7324333190918,24.5635013580322],[507.45361328125,47.7324333190918,24.9385013580322],[500.20361328125,47.7324333190918,34.6884994506836],[500.20361328125,47.7324333190918,33.1884994506836],[500.20361328125,47.7324333190918,33.9384994506836],[500.20361328125,47.7324333190918,25.3135013580322],[500.20361328125,47.7324333190918,26.8135013580322],[500.20361328125,47.7324333190918,26.0635013580322],[500.20361328125,47.7324333190918,30.9385013580322],[500.20361328125,47.7324333190918,35.0634994506836],[500.20361328125,47.7324333190918,35.4384994506836],[500.20361328125,47.7324333190918,29.0635013580322],[500.20361328125,47.7324333190918,29.4385013580322],[500.20361328125,47.7324333190918,24.9385013580322],[500.20361328125,47.7324333190918,24.5635013580322],[507.45361328125,47.7324333190918,24.1885013580322],[507.45361328125,47.7324333190918,24.0011005401611],[513.86376953125,49.7324333190918,24.0011005401611],[507.45361328125,47.7324333190918,35.8134994506836],[507.45361328125,47.7324333190918,36.0009002685547],[513.86376953125,49.7324333190918,36.0009002685547],[500.20361328125,47.7324333190918,24.1885013580322],[500.20361328125,47.7324333190918,24.0011005401611],[502.988800048828,49.7324333190918,24.0011005401611],[500.20361328125,47.7324333190918,35.8134994506836],[500.20361328125,47.7324333190918,36.0009002685547],[502.988800048828,49.7324333190918,36.0009002685547],[504.755187988281,62.8258323669434,27.0011005401611],[499.205383300781,51.2324333190918,36.0009002685547],[498.786193847656,51.1074333190918,36.0009002685547],[502.358795166016,51.2324333190918,36.0009002685547],[499.205383300781,51.2324333190918,24.0011005401611],[502.358795166016,51.2324333190918,24.0011005401611],[498.786193847656,51.1074333190918,24.0011005401611],[502.568786621094,50.7324333190918,24.0011005401611],[505.931213378906,51.3574333190918,24.0011005401611],[509.503601074219,51.4824333190918,24.0011005401611],[502.568786621094,50.7324333190918,36.0009002685547],[505.931213378906,51.3574333190918,36.0009002685547],[509.503601074219,51.4824333190918,36.0009002685547],[499.048583984375,50.4824333190918,36.0009002685547],[492.428588867188,48.9824333190918,36.0009002685547],[499.048583984375,50.4824333190918,24.0011005401611],[492.428588867188,48.9824333190918,24.0011005401611],[506.088806152344,50.9824333190918,24.0011005401611],[506.036010742188,51.1074333190918,24.0011005401611],[506.088806152344,50.9824333190918,36.0009002685547],[506.036010742188,51.1074333190918,36.0009002685547],[498.891204833984,50.8574333190918,36.0009002685547],[498.943786621094,50.7324333190918,36.0009002685547],[498.891204833984,50.8574333190918,24.0011005401611],[498.943786621094,50.7324333190918,24.0011005401611],[499.573608398438,49.2324333190918,24.0011005401611],[499.783813476562,48.7324333190918,24.0011005401611],[499.573608398438,49.2324333190918,36.0009002685547],[499.783813476562,48.7324333190918,36.0009002685547],[506.403594970703,50.2324333190918,24.0011005401611],[506.298797607422,50.4824333190918,24.0011005401611],[506.403594970703,50.2324333190918,36.0009002685547],[506.298797607422,50.4824333190918,36.0009002685547],[501.228607177734,81.599235534668,27.0011005401611],[502.928588867188,81.599235534668,24.0011005401611],[499.2587890625,49.9824333190918,36.0009002685547],[499.363800048828,49.7324333190918,36.0009002685547],[499.2587890625,49.9824333190918,24.0011005401611],[499.363800048828,49.7324333190918,24.0011005401611],[496.695404052734,56.2124328613281,27.0011005401611],[496.195404052734,54.7190322875977,24.0011005401611],[509.851989746094,53.2258338928223,27.0011005401611],[493.464782714844,51.1074333190918,36.0009002685547],[493.464782714844,51.1074333190918,24.0011005401611],[502.768798828125,51.7324333190918,24.0011005401611],[500.215789794922,51.3574333190918,24.0011005401611],[497.628601074219,51.2324333190918,24.0011005401611],[502.768798828125,51.7324333190918,36.0009002685547],[500.215789794922,51.3574333190918,36.0009002685547],[497.628601074219,51.2324333190918,36.0009002685547],[507.033813476562,48.7324333190918,24.0011005401611],[506.823791503906,49.2324333190918,24.0011005401611],[507.033813476562,48.7324333190918,36.0009002685547],[506.823791503906,49.2324333190918,36.0009002685547],[494.4501953125,51.1074333190918,24.0011005401611],[494.4501953125,51.1074333190918,36.0009002685547],[500.807006835938,51.3574333190918,36.0009002685547],[503.591186523438,51.4824333190918,36.0009002685547],[503.591186523438,51.4824333190918,24.0011005401611],[500.807006835938,51.3574333190918,24.0011005401611],[505.728607177734,65.7524337768555,36.0009002685547],[505.728607177734,65.7524337768555,33.0009002685547],[499.221801757812,72.2124328613281,36.0009002685547],[501.835388183594,71.6058349609375,36.0009002685547],[506.515197753906,72.2124328613281,24.0011005401611],[503.781982421875,71.6058349609375,24.0011005401611],[503.781982421875,71.6058349609375,27.0011005401611],[499.888793945312,65.7524337768555,24.0011005401611],[495.695404052734,53.2258338928223,33.0009002685547],[516.648803710938,51.7324333190918,30.0011005401611],[498.20361328125,77.8658294677734,28.5011005401611],[505.585388183594,57.7058334350586,33.0009002685547],[508.871795654297,56.2124328613281,33.0009002685547],[499.992004394531,57.7058334350586,27.0011005401611],[504.628601074219,81.599235534668,33.0009002685547],[500.861999511719,62.8258323669434,33.0009002685547],[496.878601074219,74.1324310302734,27.0011005401611],[496.878601074219,74.1324310302734,33.0009002685547],[491.57861328125,59.199031829834,27.0011005401611],[490.253601074219,55.4658317565918,28.5011005401611],[491.57861328125,59.199031829834,33.0009002685547],[514.068786621094,59.199031829834,27.0011005401611],[514.068786621094,59.199031829834,33.0009002685547],[508.908813476562,74.1324310302734,27.0011005401611],[507.618804931641,77.8658294677734,28.5011005401611],[508.908813476562,74.1324310302734,33.0009002685547],[491.271789550781,50.9824333190918,36.0009002685547],[490.877807617188,50.9824333190918,36.0009002685547],[491.271789550781,50.9824333190918,24.0011005401611],[490.877807617188,50.9824333190918,24.0011005401611],[495.213806152344,50.9824333190918,36.0009002685547],[493.636993408203,50.9824333190918,36.0009002685547],[495.213806152344,50.9824333190918,24.0011005401611],[493.636993408203,50.9824333190918,24.0011005401611],[503.985412597656,51.4824333190918,36.0009002685547],[503.985412597656,51.4824333190918,24.0011005401611],[511.675415039062,57.2792320251465,36.0009002685547],[493.921813964844,57.2792320251465,24.0011005401611],[502.768798828125,51.7324333190918,30.0011005401611],[506.555206298828,51.7324333190918,30.0011005401611],[498.981994628906,51.7324333190918,30.0011005401611],[492.848815917969,50.9824333190918,24.0011005401611],[492.848815917969,50.9824333190918,36.0009002685547],[500.861999511719,68.6792297363281,36.0009002685547],[500.861999511719,68.6792297363281,24.0011005401611],[496.878601074219,74.1324310302734,24.0011005401611],[496.878601074219,74.1324310302734,36.0009002685547],[504.755187988281,68.6792297363281,24.0011005401611],[504.755187988281,68.6792297363281,36.0009002685547],[508.908813476562,74.1324310302734,36.0009002685547],[508.908813476562,74.1324310302734,24.0011005401611],[505.728607177734,65.7524337768555,30.0011005401611],[504.755187988281,68.6792297363281,30.0011005401611],[503.781982421875,71.6058349609375,30.0011005401611],[500.861999511719,68.6792297363281,30.0011005401611],[499.888793945312,65.7524337768555,30.0011005401611],[501.835388183594,71.6058349609375,30.0011005401611],[491.57861328125,59.199031829834,24.0011005401611],[491.57861328125,59.199031829834,36.0009002685547],[514.068786621094,59.199031829834,36.0009002685547],[514.068786621094,59.199031829834,24.0011005401611],[511.07861328125,47.7324333190918,34.8759002685547],[511.07861328125,47.7324333190918,31.8759002685547],[514.70361328125,47.7324333190918,33.9384994506836],[511.07861328125,47.7324333190918,25.1261005401611],[514.70361328125,47.7324333190918,26.0635013580322],[511.07861328125,47.7324333190918,28.1261005401611],[502.788818359375,57.7058334350586,30.0011005401611],[502.048583984375,79.8324356079102,36.0009002685547],[514.70361328125,47.7324333190918,30.9385013580322],[511.07861328125,47.7324333190918,30.3759002685547],[514.70361328125,47.7324333190918,29.0635013580322],[511.07861328125,47.7324333190918,29.6261005401611],[496.57861328125,47.7324333190918,31.1259002685547],[496.57861328125,47.7324333190918,32.6259002685547],[496.57861328125,47.7324333190918,34.1259002685547],[496.57861328125,47.7324333190918,28.8761005401611],[496.57861328125,47.7324333190918,27.3761005401611],[496.57861328125,47.7324333190918,25.8761005401611],[496.57861328125,47.7324333190918,29.6261005401611],[514.70361328125,47.7324333190918,35.4384994506836],[511.07861328125,47.7324333190918,35.6259002685547],[514.70361328125,47.7324333190918,24.5635013580322],[511.07861328125,47.7324333190918,24.3761005401611],[496.57861328125,47.7324333190918,34.8759002685547],[496.57861328125,47.7324333190918,25.1261005401611],[496.57861328125,47.7324333190918,35.6259002685547],[496.57861328125,47.7324333190918,24.3761005401611],[511.07861328125,47.7324333190918,36.0009002685547],[511.07861328125,47.7324333190918,24.0011005401611],[514.70361328125,47.7324333190918,30.1885013580322],[514.70361328125,47.7324333190918,35.8134994506836],[514.70361328125,47.7324333190918,29.8135013580322],[514.70361328125,47.7324333190918,24.1885013580322],[496.57861328125,47.7324333190918,36.0009002685547],[496.57861328125,47.7324333190918,24.0011005401611],[510.238800048828,49.7324333190918,24.0011005401611],[510.238800048828,49.7324333190918,36.0009002685547],[514.70361328125,47.7324333190918,24.0011005401611],[514.70361328125,47.7324333190918,36.0009002685547],[496.158813476562,48.7324333190918,36.0009002685547],[496.158813476562,48.7324333190918,24.0011005401611],[502.808807373047,62.8258323669434,30.0011005401611],[509.608795166016,51.2324333190918,24.0011005401611],[509.608795166016,51.2324333190918,36.0009002685547],[491.641204833984,50.8574333190918,24.0011005401611],[495.423797607422,50.4824333190918,36.0009002685547],[495.423797607422,50.4824333190918,24.0011005401611],[491.641204833984,50.8574333190918,36.0009002685547],[495.528594970703,50.2324333190918,24.0011005401611],[492.0087890625,49.9824333190918,24.0011005401611],[509.818786621094,50.7324333190918,24.0011005401611],[495.948608398438,49.2324333190918,36.0009002685547],[495.528594970703,50.2324333190918,36.0009002685547],[495.948608398438,49.2324333190918,24.0011005401611],[509.818786621094,50.7324333190918,36.0009002685547],[492.0087890625,49.9824333190918,36.0009002685547],[491.956207275391,50.1074333190918,24.0011005401611],[491.956207275391,50.1074333190918,36.0009002685547],[502.928588867188,81.599235534668,30.0011005401611],[491.851013183594,50.3574333190918,36.0009002685547],[491.851013183594,50.3574333190918,24.0011005401611],[496.195404052734,54.7190322875977,30.0011005401611],[509.361999511719,54.7190322875977,30.0011005401611],[488.632598876953,51.7256317138672,30.0011005401611],[488.632598876953,51.7256317138672,29.5091018676758],[488.632598876953,51.7188339233398,24.0011005401611],[488.632598876953,51.7256317138672,27.4929008483887],[488.632598876953,51.7324333190918,30.0011005401611],[488.632598876953,51.7324333190918,29.0175018310547],[488.632598876953,51.7324333190918,24.9847011566162],[488.632598876953,51.7324333190918,24.0011005401611],[488.632598876953,51.7188339233398,30.0011005401611],[488.632598876953,51.7176322937012,24.0011005401611],[488.632598876953,51.7182312011719,30.0011005401611],[488.632598876953,51.7176322937012,30.0011005401611],[488.632598876953,51.715030670166,24.0011005401611],[488.632598876953,51.7162322998047,30.0011005401611],[488.632598876953,50.761833190918,24.0011005401611],[488.632598876953,50.7578315734863,24.0011005401611],[488.632598876953,50.7598342895508,30.0011005401611],[488.632598876953,50.7522315979004,24.0011005401611],[488.632598876953,49.7838325500488,24.0011005401611],[488.632598876953,50.2680320739746,30.0011005401611],[488.632598876953,51.7046318054199,24.0011005401611],[488.632598876953,51.709831237793,30.0011005401611],[488.632598876953,50.9120330810547,24.0011005401611],[488.632598876953,50.8882331848145,24.0011005401611],[488.632598876953,50.9002304077148,30.0011005401611],[488.632598876953,47.7324333190918,24.0370998382568],[488.632598876953,48.5612335205078,30.0011005401611],[488.632598876953,47.7324333190918,24.0011005401611],[488.632598876953,47.7324333190918,24.1091003417969],[488.632598876953,48.5612335205078,30.0189018249512],[488.632598876953,47.7324333190918,25.3211002349854],[488.632598876953,48.5612335205078,30.0551013946533],[488.632598876953,47.7324333190918,25.4651012420654],[488.632598876953,48.5612335205078,30.6609001159668],[488.632598876953,47.7324333190918,25.5371017456055],[488.632598876953,48.5612335205078,30.7329006195068],[488.632598876953,47.7324333190918,25.6091003417969],[488.632598876953,48.5612335205078,30.7689018249512],[488.632598876953,47.7324333190918,25.8971004486084],[488.632598876953,48.5612335205078,30.8051013946533],[488.632598876953,47.7324333190918,28.321102142334],[488.632598876953,48.5612335205078,30.9491004943848],[488.632598876953,47.7324333190918,28.4651012420654],[488.632598876953,48.5612335205078,32.1609001159668],[488.632598876953,47.7324333190918,28.5371017456055],[488.632598876953,48.5612335205078,32.2329025268555],[488.632598876953,47.7324333190918,28.6811008453369],[488.632598876953,48.5612335205078,32.2689018249512],[488.632598876953,47.7324333190918,31.1049003601074],[488.632598876953,48.5612335205078,32.3411026000977],[488.632598876953,47.7324333190918,31.3929004669189],[488.632598876953,49.3900299072266,36.0009002685547],[488.632598876953,47.7324333190918,31.536901473999],[488.632598876953,47.7324333190918,31.6809005737305],[488.632598876953,47.7324333190918,34.1049003601074],[488.632598876953,47.7324333190918,34.3929023742676],[488.632598876953,47.7324333190918,34.464900970459],[488.632598876953,47.7324333190918,34.5369033813477],[488.632598876953,47.7324333190918,34.6809005737305],[488.632598876953,47.7324333190918,35.8929023742676],[488.632598876953,47.7324333190918,35.964900970459],[488.632598876953,47.7324333190918,36.0009002685547],[488.632598876953,50.8816299438477,24.0011005401611],[488.632598876953,50.8850326538086,30.0011005401611],[488.632598876953,49.7480316162109,24.0011005401611],[488.632598876953,49.7426300048828,24.0011005401611],[488.632598876953,49.745231628418,30.0011005401611],[488.632598876953,49.7592315673828,24.0011005401611],[488.632598876953,49.7536315917969,30.0011005401611],[488.632598876953,49.3900299072266,24.0011005401611],[488.632598876953,49.5664329528809,30.0011005401611],[488.632598876953,50.8786315917969,24.0011005401611],[488.632598876953,50.7764320373535,24.0011005401611],[488.632598876953,50.8274307250977,30.0011005401611],[488.632598876953,50.7550315856934,30.0011005401611],[488.632598876953,50.7692337036133,30.0011005401611],[488.632598876953,50.9284324645996,24.0011005401611],[488.632598876953,50.9202308654785,30.0011005401611],[488.632598876953,51.1788330078125,24.0011005401611],[488.632598876953,51.139232635498,24.0011005401611],[488.632598876953,51.1590309143066,30.0011005401611],[488.632598876953,51.2324333190918,24.0011005401611],[488.632598876953,51.2056312561035,30.0011005401611],[488.632598876953,51.4340324401855,24.0011005401611],[488.632598876953,51.3946304321289,24.0011005401611],[488.632598876953,51.4142303466797,30.0011005401611],[488.632598876953,51.4498329162598,24.0011005401611],[488.632598876953,51.5772323608398,30.0011005401611],[488.632598876953,51.4418334960938,30.0011005401611],[488.632598876953,51.3136329650879,30.0011005401611],[488.632598876953,49.7714309692383,30.0011005401611],[488.632598876953,51.0338325500488,30.0011005401611],[488.632598876953,50.8816299438477,30.0011005401611],[488.632598876953,50.8800315856934,30.0011005401611],[488.632598876953,51.7188339233398,36.0009002685547],[488.632598876953,51.7176322937012,36.0009002685547],[488.632598876953,49.3900299072266,30.0011005401611],[488.632598876953,50.7522315979004,30.0011005401611],[488.632598876953,50.7522315979004,36.0009002685547],[488.632598876953,49.7426300048828,30.0011005401611],[488.632598876953,49.7426300048828,36.0009002685547],[488.632598876953,49.7480316162109,30.0011005401611],[488.632598876953,49.7480316162109,36.0009002685547],[488.632598876953,51.715030670166,30.0011005401611],[488.632598876953,51.715030670166,36.0009002685547],[488.632598876953,50.7578315734863,30.0011005401611],[488.632598876953,50.7578315734863,36.0009002685547],[488.632598876953,50.761833190918,30.0011005401611],[488.632598876953,50.761833190918,36.0009002685547],[488.632598876953,50.8882331848145,30.0011005401611],[488.632598876953,50.8882331848145,36.0009002685547],[488.632598876953,49.7592315673828,30.0011005401611],[488.632598876953,49.7592315673828,36.0009002685547],[488.632598876953,51.1788330078125,30.0011005401611],[488.632598876953,51.1788330078125,36.0009002685547],[488.632598876953,50.9120330810547,30.0011005401611],[488.632598876953,50.9120330810547,36.0009002685547],[488.632598876953,51.4498329162598,30.0011005401611],[488.632598876953,51.4498329162598,36.0009002685547],[488.632598876953,51.7046318054199,30.0011005401611],[488.632598876953,51.7046318054199,36.0009002685547],[488.632598876953,51.2324333190918,30.0011005401611],[488.632598876953,51.2324333190918,36.0009002685547],[488.632598876953,51.3946304321289,30.0011005401611],[488.632598876953,51.3946304321289,36.0009002685547],[488.632598876953,51.4340324401855,30.0011005401611],[488.632598876953,51.4340324401855,36.0009002685547],[488.632598876953,49.7838325500488,30.0011005401611],[488.632598876953,49.7838325500488,36.0009002685547],[488.632598876953,50.7764320373535,30.0011005401611],[488.632598876953,50.7764320373535,36.0009002685547],[488.632598876953,51.139232635498,30.0011005401611],[488.632598876953,51.139232635498,36.0009002685547],[488.632598876953,50.9284324645996,30.0011005401611],[488.632598876953,50.9284324645996,36.0009002685547],[488.632598876953,50.8816299438477,36.0009002685547],[488.632598876953,50.8786315917969,30.0011005401611],[488.632598876953,50.8786315917969,36.0009002685547],[488.632598876953,51.7324333190918,35.0173034667969],[488.632598876953,51.7324333190918,36.0009002685547],[488.632598876953,51.7324333190918,30.9847011566162],[517.188415527344,51.7140884399414,24.0011005401611],[517.188415527344,51.7140884399414,36.0009002685547],[517.188415527344,50.4475173950195,24.0011005401611],[517.188415527344,51.7324333190918,35.3734130859375],[517.188415527344,51.7324333190918,36.0009002685547],[517.188415527344,51.7324333190918,34.1185760498047],[517.188415527344,51.7324333190918,31.88330078125],[517.188415527344,51.7324333190918,30.0011005401611],[517.188415527344,51.7324333190918,28.1187744140625],[517.188415527344,51.7324333190918,25.8834266662598],[517.188415527344,51.7324333190918,24.6285915374756],[517.188415527344,51.7324333190918,24.0011005401611],[517.188415527344,47.7324333190918,24.0600452423096],[517.188415527344,47.7324333190918,24.0011005401611],[517.188415527344,50.4475173950195,36.0009002685547],[517.188415527344,47.7324333190918,24.1779975891113],[517.188415527344,47.7324333190918,24.6498031616211],[517.188415527344,47.7324333190918,28.7625770568848],[517.188415527344,47.7324333190918,29.7061901092529],[517.188415527344,47.7324333190918,29.9420928955078],[517.188415527344,47.7324333190918,30.0600452423096],[517.188415527344,47.7324333190918,30.2959480285645],[517.188415527344,47.7324333190918,31.2395629882812],[517.188415527344,47.7324333190918,35.3521995544434],[517.188415527344,47.7324333190918,35.8240051269531],[517.188415527344,47.7324333190918,35.9419555664062],[517.188415527344,47.7324333190918,36.0009002685547]
];
$facets = [
[0,1,2],[3,4,5],[6,7,8],[9,10,11],[12,2,1],[12,1,13],[14,15,16],[17,18,19],[20,21,22],[17,19,23],[24,25,26],[27,13,1],[28,25,29],[30,31,32],[28,33,34],[35,36,7],[37,38,39],[40,10,41],[42,43,44],[45,5,4],[46,47,48],[46,48,49],[45,4,50],[51,52,53],[51,54,55],[56,52,57],[58,59,60],[61,50,4],[62,63,64],[65,34,33],[66,67,42],[68,17,69],[70,71,22],[66,42,72],[73,16,15],[35,7,74],[75,76,54],[77,27,1],[78,32,31],[75,54,79],[80,26,25],[81,80,25],[82,83,48],[84,20,85],[81,25,86],[87,88,19],[0,89,1],[90,91,92],[90,10,93],[38,94,39],[94,95,39],[3,7,96],[97,15,98],[97,99,15],[92,91,100],[89,101,1],[102,39,95],[103,11,10],[104,96,7],[105,15,99],[106,61,4],[107,108,33],[76,55,54],[109,91,110],[111,23,19],[112,63,113],[114,115,48],[116,59,117],[118,20,119],[120,31,121],[122,44,43],[110,91,123],[124,125,126],[127,128,129],[127,130,124],[131,124,132],[126,133,134],[135,136,126],[137,138,127],[139,127,138],[128,140,141],[142,128,143],[144,140,145],[100,91,146],[147,148,134],[101,149,1],[102,150,39],[103,10,151],[145,140,152],[152,140,153],[148,154,134],[154,155,134],[156,15,105],[157,104,7],[36,8,7],[158,37,39],[159,19,88],[160,19,159],[161,59,58],[161,117,59],[162,31,30],[162,121,31],[163,43,164],[163,165,43],[166,167,43],[167,164,43],[168,57,52],[82,48,169],[114,170,171],[108,65,33],[64,63,112],[114,172,170],[160,173,170],[171,170,173],[172,174,170],[160,170,174],[175,176,177],[178,77,1],[179,31,120],[175,180,176],[181,182,176],[177,176,182],[180,183,176],[181,176,183],[184,42,67],[185,69,17],[160,111,19],[186,187,160],[188,189,114],[190,188,114],[114,48,191],[192,114,193],[194,160,195],[196,160,194],[197,198,181],[199,197,181],[122,43,165],[200,201,175],[202,175,203],[204,175,202],[205,119,20],[206,181,207],[208,209,15],[210,15,209],[211,10,9],[212,10,211],[213,214,215],[216,217,218],[219,14,17],[113,63,220],[221,222,48],[191,48,222],[22,223,20],[205,20,223],[224,40,42],[123,91,225],[214,226,215],[227,215,226],[218,217,228],[229,228,217],[215,230,213],[125,135,126],[217,216,231],[129,128,142],[216,213,232],[130,132,124],[213,216,233],[234,213,235],[236,227,237],[238,237,227],[239,240,216],[233,216,240],[241,242,229],[243,229,242],[215,227,244],[245,215,246],[217,247,229],[248,249,217],[232,213,250],[230,250,213],[133,147,134],[244,227,251],[236,252,227],[251,227,252],[231,216,253],[254,253,216],[141,140,144],[247,255,229],[241,229,256],[255,256,229],[257,241,258],[259,146,91],[260,261,236],[262,1,149],[263,264,241],[265,241,264],[266,236,267],[268,267,236],[49,48,83],[166,43,269],[270,271,272],[273,274,275],[276,274,277],[278,151,10],[279,280,272],[281,39,150],[272,282,279],[155,283,134],[274,276,284],[153,140,285],[286,276,287],[265,276,286],[288,289,279],[268,288,279],[290,291,272],[271,290,272],[292,274,293],[275,274,292],[294,265,295],[276,265,294],[296,297,268],[279,296,268],[241,265,298],[298,265,299],[236,300,268],[300,301,268],[107,33,78],[302,303,59],[304,305,279],[282,304,279],[306,276,307],[284,276,306],[185,17,73],[308,309,221],[158,39,70],[310,41,10],[15,311,208],[7,6,312],[313,314,6],[315,6,314],[316,208,317],[318,317,208],[258,241,319],[319,241,320],[261,321,236],[321,322,236],[6,315,323],[208,324,318],[270,325,318],[326,318,325],[327,328,315],[273,315,328],[118,329,20],[330,20,329],[331,332,25],[86,25,332],[333,334,52],[335,52,334],[115,336,48],[169,48,336],[62,106,4],[35,15,210],[35,337,15],[158,10,212],[158,310,10],[338,178,1],[339,59,116],[107,302,59],[66,22,340],[66,341,22],[185,221,342],[185,308,221],[75,31,179],[75,343,31],[166,20,330],[166,85,20],[81,52,335],[81,168,52],[82,19,344],[82,87,19],[108,339,345],[346,108,345],[64,347,348],[349,347,64],[178,109,350],[351,178,350],[179,352,353],[354,352,179],[355,208,356],[356,208,311],[357,358,6],[358,312,6],[68,22,21],[68,340,22],[221,48,47],[184,342,221],[359,270,360],[318,360,270],[361,362,273],[315,273,362],[272,102,270],[363,270,102],[274,273,103],[364,103,273],[21,19,18],[21,20,84],[184,46,42],[43,42,46],[12,22,71],[365,22,12],[14,98,15],[14,220,63],[40,93,10],[40,225,91],[45,221,309],[366,221,45],[313,367,212],[212,367,368],[36,369,367],[313,36,367],[316,37,367],[37,368,367],[210,367,369],[316,367,210],[362,370,315],[370,323,315],[360,318,371],[371,318,324],[372,331,159],[159,195,160],[373,115,56],[115,114,189],[52,56,161],[374,161,56],[25,28,331],[375,331,28],[376,333,163],[163,203,175],[377,118,24],[118,181,198],[25,24,162],[378,162,24],[52,51,333],[379,333,51],[167,380,381],[376,167,381],[377,381,330],[330,381,380],[335,381,382],[376,381,335],[373,383,169],[169,383,384],[168,385,383],[373,168,383],[372,87,383],[87,384,383],[377,80,381],[80,382,381],[86,383,385],[372,383,86],[106,348,347],[386,106,347],[375,65,346],[108,346,65],[64,112,349],[387,349,112],[171,190,114],[346,345,171],[374,190,345],[171,345,190],[349,172,347],[172,114,192],[386,347,192],[172,192,347],[173,160,196],[171,173,346],[375,346,196],[173,196,346],[172,349,174],[174,186,160],[387,186,349],[174,349,186],[64,348,62],[106,62,348],[108,107,339],[59,339,107],[374,345,116],[339,116,345],[76,353,352],[379,76,352],[388,77,351],[178,351,77],[179,120,354],[378,354,120],[177,200,175],[351,350,177],[389,200,350],[177,350,200],[354,180,352],[180,175,204],[379,352,204],[180,204,352],[182,181,206],[177,182,351],[388,351,206],[182,206,351],[180,354,183],[183,199,181],[378,199,354],[183,354,199],[91,109,338],[178,338,109],[76,75,353],[179,353,75],[389,350,110],[109,110,350],[390,391,392],[393,394,395],[224,122,389],[122,175,201],[365,388,205],[205,207,181],[66,340,396],[68,396,340],[184,396,342],[185,342,396],[66,396,67],[184,67,396],[68,69,396],[185,396,69],[219,111,387],[111,160,187],[366,386,191],[191,193,114],[150,272,280],[102,272,150],[151,277,274],[103,151,274],[161,374,117],[116,117,374],[366,61,386],[106,386,61],[111,187,387],[186,387,187],[56,188,374],[190,374,188],[191,386,193],[192,193,386],[331,375,194],[196,194,375],[28,34,375],[65,375,34],[219,387,113],[112,113,387],[224,389,123],[110,123,389],[51,55,379],[76,379,55],[24,197,378],[199,378,197],[122,201,389],[200,389,201],[333,379,202],[204,202,379],[205,388,207],[206,207,388],[365,27,388],[77,388,27],[162,378,121],[120,121,378],[162,30,25],[30,29,25],[51,53,54],[303,60,59],[28,29,33],[29,397,33],[161,58,52],[53,52,58],[21,84,19],[84,344,19],[46,49,43],[49,269,43],[208,316,209],[210,209,316],[327,313,211],[212,211,313],[36,35,369],[210,369,35],[37,158,368],[212,368,158],[6,8,313],[36,313,8],[326,38,316],[37,316,38],[392,391,398],[399,398,391],[394,400,395],[401,395,400],[390,214,391],[214,213,234],[393,395,218],[218,239,216],[402,230,403],[230,215,245],[125,124,131],[404,125,403],[405,406,231],[231,248,217],[129,137,127],[407,406,129],[130,127,139],[402,130,408],[194,195,331],[159,331,195],[115,189,56],[188,56,189],[14,219,220],[113,220,219],[45,50,366],[61,366,50],[221,366,222],[191,222,366],[17,23,219],[111,219,23],[118,198,24],[197,24,198],[202,203,333],[163,333,203],[40,224,225],[123,225,224],[12,13,365],[27,365,13],[22,365,223],[205,223,365],[42,44,224],[122,224,44],[399,391,234],[214,234,391],[401,239,395],[218,395,239],[214,390,226],[226,238,227],[218,228,393],[228,229,243],[401,399,233],[233,235,213],[392,409,390],[410,390,409],[394,393,411],[412,411,393],[402,403,131],[125,131,403],[405,137,406],[129,406,137],[405,408,139],[130,139,408],[230,245,403],[404,403,245],[231,406,248],[407,248,406],[232,254,216],[402,408,232],[413,404,244],[244,246,215],[414,247,407],[247,217,249],[133,126,136],[415,133,413],[141,143,128],[416,414,141],[410,238,390],[226,390,238],[412,393,243],[228,243,393],[233,399,235],[234,235,399],[237,260,236],[238,410,237],[417,260,410],[237,410,260],[239,401,240],[233,240,401],[242,241,257],[243,242,412],[418,412,257],[242,257,412],[401,419,399],[398,399,419],[417,410,420],[409,420,410],[400,421,401],[419,401,421],[418,422,412],[411,412,422],[413,135,404],[125,404,135],[414,407,142],[129,142,407],[130,402,132],[131,132,402],[133,136,413],[135,413,136],[423,147,415],[133,415,147],[137,405,138],[139,138,405],[141,414,143],[142,143,414],[424,416,144],[141,144,416],[405,254,408],[232,408,254],[244,404,246],[245,246,404],[247,249,407],[248,407,249],[232,250,402],[230,402,250],[415,413,251],[244,251,413],[252,236,266],[251,252,415],[423,415,266],[252,266,415],[231,253,405],[254,405,253],[416,255,414],[247,414,255],[256,263,241],[255,416,256],[424,263,416],[256,416,263],[257,258,418],[425,418,258],[260,417,261],[426,261,417],[422,418,427],[427,259,91],[420,428,417],[428,1,262],[147,423,148],[429,148,423],[263,424,264],[264,295,265],[266,267,423],[267,268,297],[144,145,424],[430,424,145],[49,431,269],[166,269,431],[82,431,83],[49,83,431],[84,85,431],[166,431,85],[82,344,431],[84,431,344],[432,278,90],[10,90,278],[433,0,281],[39,281,0],[362,361,434],[435,271,359],[270,359,271],[436,361,275],[273,275,361],[360,437,359],[277,287,276],[151,278,277],[280,279,289],[150,280,281],[436,438,439],[439,285,140],[90,92,432],[440,432,92],[282,272,291],[441,282,442],[284,293,274],[443,438,284],[278,432,286],[286,299,265],[281,288,433],[288,268,301],[0,433,89],[444,89,433],[435,445,442],[445,134,283],[439,446,436],[361,436,446],[442,290,435],[271,435,290],[438,436,292],[275,292,436],[445,435,447],[359,447,435],[286,287,278],[277,278,287],[288,281,289],[280,289,281],[145,152,430],[443,430,152],[148,429,154],[441,154,429],[424,430,294],[294,307,276],[423,296,429],[296,279,305],[425,440,100],[92,100,440],[290,442,291],[282,291,442],[292,293,438],[284,438,293],[298,320,241],[432,440,298],[300,236,322],[433,300,444],[426,101,444],[89,444,101],[107,448,302],[302,79,54],[78,31,343],[107,78,448],[75,79,448],[302,448,79],[78,343,448],[75,448,343],[427,418,259],[425,259,418],[428,262,417],[426,417,262],[437,449,359],[447,359,449],[434,361,450],[446,450,361],[32,33,397],[78,33,32],[53,303,54],[302,54,303],[152,153,443],[438,443,153],[429,304,441],[282,441,304],[430,443,306],[284,306,443],[154,441,155],[442,155,441],[298,299,432],[286,432,299],[300,433,301],[288,301,433],[185,451,308],[308,74,7],[73,15,337],[185,73,451],[35,74,451],[308,451,74],[73,337,451],[35,451,337],[158,452,310],[310,72,42],[70,22,341],[158,70,452],[66,72,452],[310,452,72],[70,341,452],[66,452,341],[313,327,314],[315,314,327],[316,317,326],[318,326,317],[15,156,311],[356,311,156],[7,312,157],[358,157,312],[211,9,327],[364,327,9],[38,326,94],[363,94,326],[294,295,424],[264,424,295],[296,423,297],[267,297,423],[262,149,426],[101,426,149],[258,319,425],[440,425,319],[261,426,321],[444,321,426],[259,425,146],[100,146,425],[306,307,430],[294,430,307],[304,429,305],[296,305,429],[319,320,440],[298,440,320],[321,444,322],[300,322,444],[445,283,442],[155,442,283],[439,438,285],[153,285,438],[17,68,18],[21,18,68],[46,184,47],[221,47,184],[102,95,363],[94,363,95],[9,11,364],[103,364,11],[6,323,357],[370,357,323],[371,324,355],[208,355,324],[270,363,325],[326,325,363],[327,364,328],[273,328,364],[0,2,39],[12,39,2],[90,93,91],[40,91,93],[14,16,17],[73,17,16],[45,309,7],[308,7,309],[12,71,39],[70,39,71],[40,41,42],[310,42,41],[97,98,63],[14,63,98],[3,5,7],[45,7,5],[118,377,329],[330,329,377],[331,372,332],[86,332,372],[333,376,334],[335,334,376],[115,373,336],[169,336,373],[167,166,380],[330,380,166],[80,81,382],[335,382,81],[86,385,81],[168,81,385],[169,384,82],[87,82,384],[159,88,372],[87,372,88],[163,164,376],[167,376,164],[24,26,377],[80,377,26],[56,57,373],[168,373,57],[32,397,30],[29,30,397],[58,60,53],[303,53,60],[205,181,119],[118,119,181],[163,175,165],[122,165,175],[453,454,455],[454,456,455],[457,455,456],[458,455,457],[459,455,458],[460,455,459],[461,462,463],[464,465,466],[467,468,469],[470,471,472],[465,473,474],[475,476,477],[478,479,480],[481,482,478],[483,484,481],[485,486,483],[487,488,485],[489,490,487],[491,492,489],[493,494,491],[495,496,493],[497,498,495],[499,500,497],[501,502,499],[503,504,501],[505,504,503],[506,504,505],[507,504,506],[508,504,507],[509,504,508],[510,504,509],[511,504,510],[512,504,511],[513,504,512],[514,504,513],[476,515,516],[517,518,519],[520,517,521],[518,522,523],[522,480,479],[524,525,526],[468,470,527],[525,467,528],[529,475,530],[531,532,533],[534,531,535],[536,537,538],[473,539,540],[539,536,541],[537,534,542],[471,520,543],[532,529,544],[545,524,546],[453,461,547],[463,464,548],[523,549,504],[527,550,551],[519,552,553],[521,554,555],[466,556,557],[469,558,559],[528,560,561],[477,562,563],[543,564,565],[535,566,567],[530,568,569],[540,570,571],[474,572,573],[542,574,575],[538,576,577],[541,578,579],[472,580,581],[526,582,583],[533,584,585],[544,586,587],[516,545,588],[588,589,590],[455,460,4],[591,592,63],[462,455,4],[592,547,63],[547,548,63],[465,462,4],[548,557,63],[127,124,501],[127,501,499],[505,503,124],[124,126,507],[124,507,506],[509,508,126],[126,134,512],[126,512,511],[510,509,126],[128,127,493],[128,493,491],[497,495,127],[489,487,128],[140,128,483],[140,483,481],[487,485,128],[478,480,140],[480,522,140],[514,513,134],[504,514,134],[551,581,437],[471,470,434],[445,447,555],[445,555,553],[134,445,553],[134,553,504],[446,439,518],[446,518,517],[439,140,522],[439,522,518],[515,476,358],[563,588,356],[557,573,63],[473,465,4],[437,360,559],[437,559,551],[360,371,561],[360,561,559],[362,434,470],[362,470,468],[370,362,468],[370,468,467],[499,497,127],[506,505,124],[495,493,127],[513,512,134],[481,478,140],[447,449,565],[447,565,555],[450,446,517],[450,517,520],[356,156,569],[356,569,563],[157,358,476],[157,476,475],[357,370,467],[357,467,525],[371,355,583],[371,583,561],[460,459,4],[63,62,593],[63,593,591],[62,4,459],[62,459,458],[532,531,104],[531,534,104],[567,585,105],[575,567,105],[4,3,539],[4,539,473],[536,539,3],[97,63,573],[97,573,571],[571,579,97],[99,97,579],[99,579,577],[105,99,577],[105,577,575],[96,104,534],[96,534,537],[3,96,537],[3,537,536],[503,501,124],[508,507,126],[491,489,128],[511,510,126],[485,483,128],[434,450,520],[434,520,471],[449,437,581],[449,581,565],[156,105,585],[156,585,587],[587,569,156],[104,157,529],[104,529,532],[475,529,157],[590,583,355],[355,356,588],[355,588,590],[358,357,524],[358,524,515],[525,524,357],[458,457,62],[457,593,62],[479,478,482],[479,504,549],[479,482,504],[482,481,484],[472,551,550],[581,551,472],[482,484,504],[484,483,486],[523,553,552],[504,553,523],[540,573,572],[571,573,540],[544,585,584],[587,585,544],[542,577,576],[575,577,542],[526,590,589],[583,590,526],[535,575,574],[567,575,535],[533,567,566],[585,567,533],[538,579,578],[577,579,538],[543,581,580],[565,581,543],[477,569,568],[563,569,477],[530,587,586],[569,587,530],[541,571,570],[579,571,541],[528,583,582],[561,583,528],[591,453,592],[547,592,453],[521,565,564],[555,565,521],[474,557,556],[573,557,474],[516,563,562],[588,563,516],[519,555,554],[553,555,519],[527,559,558],[551,559,527],[469,561,560],[559,561,469],[462,461,455],[453,455,461],[461,463,547],[548,547,463],[465,464,462],[463,462,464],[464,466,548],[557,548,466],[469,560,467],[528,467,560],[472,550,470],[527,470,550],[474,556,465],[466,465,556],[477,568,475],[530,475,568],[516,562,476],[477,476,562],[519,554,517],[521,517,554],[521,564,520],[543,520,564],[523,552,518],[519,518,552],[479,549,522],[523,522,549],[526,589,524],[589,546,524],[527,558,468],[469,468,558],[528,582,525],[526,525,582],[530,586,529],[544,529,586],[533,566,531],[535,531,566],[535,574,534],[542,534,574],[538,578,536],[541,536,578],[540,572,473],[474,473,572],[541,570,539],[540,539,570],[542,576,537],[538,537,576],[543,580,471],[472,471,580],[544,584,532],[533,532,584],[524,545,515],[516,515,545],[545,546,588],[589,588,546],[453,591,454],[593,454,591],[484,486,504],[486,485,488],[486,488,504],[488,487,490],[488,490,504],[490,489,492],[490,492,504],[492,491,494],[492,494,504],[494,493,496],[494,496,504],[496,495,498],[496,498,504],[498,497,500],[498,500,504],[500,499,502],[500,502,504],[501,504,502],[454,593,456],[457,456,593],[594,595,596],[597,598,594],[599,597,594],[600,599,594],[601,600,594],[602,601,594],[603,602,594],[604,603,594],[605,604,594],[606,607,608],[609,606,608],[610,609,608],[611,610,608],[612,611,608],[613,612,608],[614,613,608],[615,614,608],[616,615,608],[617,616,608],[618,617,608],[619,618,608],[620,619,608],[596,608,607],[595,594,598],[608,596,595],[605,594,91],[91,338,602],[91,602,603],[598,597,1],[594,596,91],[608,595,1],[595,598,1],[616,617,392],[610,611,394],[419,421,613],[419,613,614],[422,427,607],[422,607,606],[427,91,596],[427,596,607],[428,420,619],[428,619,620],[1,428,620],[1,620,608],[420,409,618],[420,618,619],[411,422,606],[411,606,609],[398,419,614],[398,614,615],[421,400,612],[421,612,613],[409,392,617],[409,617,618],[394,411,609],[394,609,610],[604,605,91],[338,1,599],[338,599,600],[392,398,615],[392,615,616],[400,394,611],[400,611,612],[603,604,91],[601,602,338],[597,599,1],[600,601,338]
];
} elsif ($name eq 'gt2_teeth') {
$vertices = [
[15.8899993896484,19.444055557251,2.67489433288574],[15.9129991531372,19.1590557098389,2.67489433288574],[15.9039993286133,19.1500549316406,2.67489433288574],[15.9489994049072,19.2490558624268,2.67489433288574],[15.9579992294312,19.3570556640625,2.67489433288574],[15.8819999694824,18.690055847168,2.67489433288574],[15.8319997787476,17.7460556030273,2.67489433288574],[15.8489999771118,18.819055557251,2.67489433288574],[15.8589992523193,17.7190551757812,2.67489433288574],[15.8769998550415,19.0490550994873,2.67489433288574],[15.7529993057251,17.8080558776855,2.67489433288574],[15.7869997024536,19.5010547637939,2.67489433288574],[14.0329990386963,18.7170543670654,2.67489433288574],[13.9599990844727,18.7460556030273,2.67489433288574],[13.9869995117188,20.2840557098389,2.67489433288574],[14.2029991149902,20.149055480957,2.67489433288574],[14.1939992904663,19.9560546875,2.67489433288574],[14.1939992904663,20.1670551300049,2.67489433288574],[14.2119998931885,20.0590553283691,2.67489433288574],[12.1899995803833,19.1840553283691,2.67489433288574],[12.096999168396,19.1950550079346,2.67489433288574],[12.1099996566772,20.6690559387207,2.67489433288574],[11.382999420166,19.9750556945801,2.67489433288574],[11.2599992752075,19.2490558624268,2.67489433288574],[11.2369995117188,19.9320545196533,2.67489433288574],[11.5349998474121,20.0640544891357,2.67489433288574],[11.6259994506836,20.1550559997559,2.67489433288574],[11.6829986572266,20.2390556335449,2.67489433288574],[11.7369995117188,20.3570556640625,2.67489433288574],[11.8449993133545,20.645055770874,2.67489433288574],[11.7729988098145,20.4640560150146,2.67489433288574],[11.7799987792969,20.5370559692383,9.41389465332031],[11.7639999389648,20.4470558166504,2.67489433288574],[11.9559993743896,20.6810550689697,2.67489433288574],[12.3079996109009,20.6020545959473,2.67489433288574],[12.1959991455078,19.1860542297363,2.67489433288574],[12.2059993743896,20.6540546417236,2.67489433288574],[12.3489990234375,20.3740558624268,2.67489433288574],[12.3579998016357,20.2750549316406,2.67489433288574],[12.3669996261597,20.266056060791,2.67489433288574],[12.3849992752075,20.1670551300049,2.67489433288574],[12.4269990921021,20.0680541992188,2.67489433288574],[12.5029993057251,19.9540557861328,2.67489433288574],[12.6169996261597,19.8550548553467,2.67489433288574],[12.7449989318848,19.7800559997559,2.67489433288574],[12.7629995346069,19.7800559997559,2.67489433288574],[12.8799991607666,19.7350559234619,2.67489433288574],[13.0369997024536,19.7250556945801,2.67489433288574],[13.0149993896484,19.0340557098389,2.67489433288574],[11.1699991226196,19.2580547332764,2.67489433288574],[11.0959987640381,19.2580547332764,2.67489433288574],[11.1209993362427,19.9230556488037,2.67489433288574],[13.0599994659424,19.024055480957,2.67489433288574],[14.9049997329712,18.3170547485352,2.67489433288574],[14.8779993057251,18.3400554656982,2.67489433288574],[14.8779993057251,19.149055480957,2.67489433288574],[13.3039989471436,19.77805519104,2.67489433288574],[13.1589994430542,18.9890556335449,2.67489433288574],[13.1559991836548,19.7350559234619,2.67489433288574],[13.4269990921021,19.8600559234619,2.67489433288574],[13.5339994430542,19.9700546264648,2.67389440536499],[13.6359996795654,20.1220550537109,2.67489433288574],[13.6359996795654,20.1400547027588,2.67489433288574],[13.6719989776611,20.2210559844971,2.67489433288574],[13.6899995803833,20.2300548553467,2.67489433288574],[13.7509994506836,20.3010559082031,2.67489433288574],[13.8539991378784,20.3180541992188,2.67489433288574],[14.8329992294312,18.3580551147461,2.67489433288574],[14.1849994659424,19.8530559539795,2.67489433288574],[14.0769996643066,18.7000541687012,2.67489433288574],[14.1099996566772,20.2400550842285,2.67489433288574],[14.2009992599487,19.6230545043945,2.67489433288574],[14.2729997634888,19.4670543670654,2.67489433288574],[14.3379993438721,19.3790550231934,2.67489433288574],[14.4549999237061,19.2770557403564,2.67489433288574],[14.5899991989136,19.2040557861328,2.67489433288574],[14.6079998016357,19.2040557861328,2.67489433288574],[14.7209997177124,19.1600551605225,2.67489433288574],[15.1379995346069,19.210054397583,2.67489433288574],[14.9949998855591,18.2680549621582,2.67489433288574],[15.0029993057251,19.1580543518066,2.67489433288574],[15.2369995117188,19.2760543823242,2.67489433288574],[15.3779993057251,19.4060554504395,2.67489433288574],[15.4539995193481,19.520055770874,2.67489433288574],[15.471999168396,19.52805519104,2.67489433288574],[15.5449991226196,19.5830554962158,2.67489433288574],[15.6529998779297,19.573055267334,2.67489433288574],[15.7059993743896,17.8360557556152,2.67489433288574],[15.9449996948242,18.5560550689697,2.67489433288574],[15.8589992523193,18.9380550384521,2.67489433288574],[14.9589996337891,18.2950553894043,2.67489433288574],[15.7779998779297,19.5100555419922,2.67489433288574],[14.0049991607666,20.2750549316406,2.67489433288574],[12.3489990234375,20.5000553131104,2.67489433288574],[13.0689992904663,19.0150547027588,2.67489433288574],[13.0999994277954,19.0100555419922,2.67489433288574],[15.9489994049072,19.3670558929443,9.41489505767822],[15.9489994049072,19.2490558624268,9.41489505767822],[15.75,17.8080558776855,9.41489505767822],[15.6639995574951,19.5710544586182,9.41489505767822],[15.5709991455078,17.9260559082031,9.41489505767822],[15.8769998550415,18.690055847168,9.41489505767822],[15.8499994277954,18.8170547485352,9.41489505767822],[15.9459991455078,18.5520553588867,9.41489505767822],[15.914999961853,17.6890544891357,9.41489505767822],[15.3999996185303,19.4290542602539,9.41489505767822],[15.3099994659424,19.339054107666,9.41489505767822],[15.3729991912842,18.0440559387207,9.41489505767822],[15.4579992294312,19.5170555114746,9.41489505767822],[15.5469999313354,19.5820541381836,9.41489505767822],[13.2309989929199,19.7610549926758,9.41489505767822],[13.168999671936,19.7360553741455,9.41489505767822],[13.096999168396,19.0140552520752,9.41489505767822],[13.1999988555908,18.9870548248291,9.41489505767822],[15.1399993896484,19.2080554962158,9.41489505767822],[15.0159997940063,19.1600551605225,9.41489505767822],[14.9859991073608,18.2770557403564,9.41489505767822],[15.1749992370605,18.1690559387207,9.41489505767822],[15.9039993286133,19.1320552825928,9.41489505767822],[15.8949995040894,19.4460544586182,9.41489505767822],[15.8769998550415,19.0420551300049,9.41489505767822],[12.2169990539551,20.6500549316406,9.41489505767822],[11.9379997253418,20.6810550689697,9.41489505767822],[11.8629989624023,19.2130546569824,9.41489505767822],[12.096999168396,19.1950550079346,9.41489505767822],[14.1669998168945,18.6640548706055,9.41489505767822],[14.1039991378784,20.2460556030273,9.41489505767822],[13.9849996566772,18.7360553741455,9.41489505767822],[14.7349996566772,19.1590557098389,9.41489505767822],[14.5849990844727,19.2050552368164,9.41489505767822],[14.5719995498657,18.4850559234619,9.41489505767822],[14.1939992904663,19.6760559082031,9.41489505767822],[14.1849994659424,19.9330558776855,9.41489505767822],[14.1759996414185,18.6640548706055,9.41489505767822],[14.261999130249,19.4890556335449,9.41489505767822],[14.3539991378784,19.3610553741455,9.41489505767822],[14.3559989929199,18.5830554962158,9.41489505767822],[11.6039991378784,20.1250553131104,9.41489505767822],[11.5209999084473,20.0520553588867,9.41489505767822],[11.4209995269775,19.2480545043945,9.41489505767822],[11.6989994049072,20.2690544128418,9.41389465332031],[11.7609996795654,20.4310550689697,9.41489505767822],[11.8359994888306,19.2130546569824,9.41489505767822],[14.1889991760254,20.1710548400879,9.41489505767822],[13.9689998626709,20.2840557098389,9.41489505767822],[13.8739995956421,20.315055847168,9.41489505767822],[13.7799997329712,18.8080558776855,9.41489505767822],[13.9869995117188,20.2750549316406,9.41489505767822],[12.3129997253418,20.5980548858643,9.41489505767822],[12.3399991989136,20.5090560913086,9.41489505767822],[12.3489990234375,20.3830547332764,9.41489505767822],[12.3599996566772,20.2680549621582,9.41489505767822],[12.3849992752075,20.1850547790527,9.41489505767822],[12.3849992752075,20.1670551300049,9.41489505767822],[12.4249992370605,20.065055847168,9.41489505767822],[12.4729995727539,19.1350555419922,9.41489505767822],[14.4399995803833,19.2900543212891,9.41489505767822],[14.3649997711182,18.5740547180176,9.41489505767822],[13.5729999542236,20.0310554504395,9.41489505767822],[13.4889993667603,19.9140548706055,9.41489505767822],[13.5639991760254,18.8710556030273,9.41489505767822],[13.6389999389648,20.1310558319092,9.41489505767822],[13.6719989776611,20.2130546569824,9.41489505767822],[13.75,20.3020553588867,9.41489505767822],[12.7399997711182,19.7810554504395,9.41489505767822],[12.6189994812012,19.8520545959473,9.41489505767822],[12.5799999237061,19.1200542449951,9.41489505767822],[12.8349990844727,19.069055557251,9.41489505767822],[11.2669992446899,19.9350547790527,9.41489505767822],[11.1029987335205,19.9230556488037,9.41489505767822],[11.0209999084473,19.2600555419922,9.41489505767822],[11.3819999694824,19.9710559844971,9.41489505767822],[13.418999671936,19.8530559539795,9.41489505767822],[13.4329996109009,18.9160556793213,9.41489505767822],[11.8399991989136,20.6430549621582,9.41489505767822],[13.3119993209839,19.7800559997559,9.41489505767822],[15.2189998626709,19.2600555419922,9.41489505767822],[15.1839990615845,18.1600551605225,9.41489505767822],[15.3639993667603,18.0520553588867,9.41489505767822],[13.0189990997314,19.7250556945801,9.41489505767822],[12.8949995040894,19.7350559234619,9.41489505767822],[15.9039993286133,19.1500549316406,9.41489505767822],[15.7699995040894,19.5140552520752,9.41489505767822],[15.8589992523193,18.9340553283691,9.41489505767822],[14.1939992904663,19.9510555267334,9.41489505767822],[14.2119998931885,20.0630550384521,9.41489505767822],[14.8589992523193,19.149055480957,9.41489505767822],[14.8159999847412,18.3670558929443,9.41489505767822],[14.8959999084473,18.3220558166504,9.41489505767822],[12.5189990997314,19.9360542297363,9.41489505767822],[11.0209999084473,19.9290542602539,9.41489505767822],[11.0209999084473,19.2530555725098,2.67489433288574],[11.0209999084473,19.9300556182861,2.67489433288574],[15.9799995422363,18.505931854248,5.58724021911621],[15.9799995422363,18.5044555664062,9.41489505767822],[15.9799995422363,18.5041732788086,2.67489433288574],[15.9799995422363,18.1684837341309,2.67489433288574],[15.9799995422363,18.1288299560547,9.41489505767822],[15.9799995422363,17.9876575469971,2.67489433288574],[15.9799995422363,17.6247596740723,3.91620373725891],[15.9799995422363,17.6247596740723,2.67489433288574],[15.9799995422363,17.6254329681396,4.32245063781738],[15.9799995422363,17.8920269012451,9.41489505767822],[15.9799995422363,17.8795108795166,2.67489433288574],[15.9799995422363,17.629810333252,4.58585262298584],[15.9799995422363,17.6336059570312,5.27938556671143],[15.9799995422363,17.8311748504639,2.67489433288574],[15.9799995422363,17.638355255127,9.41489505767822],[15.9799995422363,17.6346111297607,5.98653984069824],[15.9799995422363,17.8728256225586,2.67489433288574],[15.9799995422363,18.2221603393555,2.67489433288574]
];
$facets = [
[0,1,2],[0,3,1],[0,4,3],[5,6,7],[8,6,5],[2,9,0],[6,10,11],[12,13,14],[15,16,17],[18,16,15],[19,20,21],[22,23,24],[25,23,22],[26,23,25],[27,23,26],[28,23,27],[29,30,31],[29,32,30],[29,28,32],[33,28,29],[33,23,28],[21,23,33],[20,23,21],[34,35,36],[37,35,34],[38,35,37],[39,35,38],[40,35,39],[41,35,40],[42,35,41],[43,35,42],[44,35,43],[45,35,44],[46,35,45],[47,35,46],[48,35,47],[49,50,51],[52,48,47],[23,49,24],[53,54,55],[56,57,58],[59,57,56],[60,57,59],[61,57,60],[62,57,61],[63,57,62],[64,57,63],[65,57,64],[66,57,65],[13,57,66],[54,67,55],[68,69,70],[71,69,68],[72,69,71],[73,69,72],[74,69,73],[75,69,74],[76,69,75],[77,69,76],[67,69,77],[70,16,68],[70,17,16],[78,79,80],[81,79,78],[82,79,81],[83,79,82],[84,79,83],[85,79,84],[86,79,85],[87,79,86],[88,8,5],[11,7,6],[11,89,7],[11,9,89],[11,0,9],[55,90,53],[55,79,90],[55,80,79],[91,11,10],[92,69,12],[92,70,69],[34,93,37],[47,94,52],[47,95,94],[47,57,95],[47,58,57],[51,24,49],[21,35,19],[21,36,35],[14,92,12],[86,10,87],[86,91,10],[77,55,67],[66,14,13],[96,97,4],[98,99,100],[101,102,98],[103,101,98],[104,103,98],[105,106,107],[108,105,107],[109,108,107],[100,109,107],[110,111,112],[113,110,112],[114,115,116],[117,114,116],[118,119,120],[121,122,123],[124,121,123],[125,126,127],[128,129,130],[131,132,133],[71,131,133],[134,71,133],[135,134,133],[136,135,133],[137,138,139],[140,137,139],[141,140,139],[142,31,141],[142,141,139],[143,126,132],[144,145,146],[147,144,146],[127,147,146],[148,121,124],[149,148,124],[150,149,124],[151,150,124],[152,151,124],[153,152,124],[154,153,124],[155,154,124],[129,156,157],[130,129,157],[158,159,160],[161,158,160],[162,161,160],[163,162,160],[146,163,160],[164,165,166],[167,164,166],[168,169,170],[171,168,170],[139,171,170],[159,172,173],[123,174,142],[175,110,113],[173,175,113],[106,176,177],[178,106,177],[179,180,167],[112,179,167],[175,173,172],[119,118,181],[119,181,97],[119,97,96],[182,98,102],[182,102,183],[182,183,120],[182,120,119],[143,132,184],[184,185,143],[147,127,126],[174,123,122],[159,173,160],[126,125,133],[126,133,132],[186,187,188],[186,188,116],[186,116,115],[99,98,182],[109,100,99],[106,178,107],[114,117,177],[114,177,176],[128,130,187],[128,187,186],[135,136,157],[135,157,156],[163,146,145],[164,167,180],[179,112,111],[171,139,138],[189,155,166],[189,166,165],[149,150,93],[154,155,189],[31,142,174],[114,176,78],[81,78,176],[7,89,183],[89,9,120],[89,120,183],[78,80,114],[176,106,81],[88,5,103],[183,102,7],[118,120,9],[9,2,181],[9,181,118],[115,114,80],[82,81,106],[101,103,5],[102,101,5],[5,7,102],[97,181,2],[2,1,97],[1,3,97],[80,55,115],[172,159,59],[59,56,172],[3,4,97],[4,0,96],[105,108,82],[186,115,55],[82,106,105],[83,82,108],[60,59,159],[175,172,56],[119,96,0],[0,11,119],[108,109,84],[84,83,108],[55,77,186],[56,58,110],[56,110,175],[60,159,158],[11,91,182],[182,119,11],[91,86,182],[85,84,109],[86,85,99],[128,186,77],[58,111,110],[158,161,60],[26,25,137],[138,137,25],[99,182,86],[109,99,85],[77,76,128],[58,47,111],[61,60,161],[137,140,26],[27,26,140],[25,22,138],[129,128,76],[76,75,129],[75,74,129],[74,73,156],[73,72,135],[68,16,184],[68,184,132],[16,18,185],[161,162,62],[62,61,161],[179,111,47],[171,138,22],[156,129,74],[135,156,73],[134,135,72],[72,71,134],[68,132,131],[185,184,16],[18,15,185],[63,62,162],[28,27,140],[22,24,171],[71,68,131],[15,17,143],[15,143,185],[17,70,143],[70,92,126],[162,163,64],[64,63,162],[180,179,47],[47,46,180],[140,141,28],[168,171,24],[126,143,70],[92,14,147],[147,126,92],[14,66,144],[14,144,147],[65,64,163],[66,65,145],[46,45,180],[32,28,141],[24,51,168],[145,144,66],[163,145,65],[164,180,45],[45,44,164],[44,43,164],[43,42,165],[38,37,151],[150,151,37],[37,93,150],[141,31,30],[30,32,141],[169,168,51],[165,164,43],[189,165,42],[42,41,189],[40,39,152],[40,152,153],[151,152,39],[39,38,151],[93,34,149],[154,189,41],[153,154,41],[41,40,153],[148,149,34],[34,36,148],[36,21,121],[31,174,29],[121,148,36],[21,33,122],[21,122,121],[33,29,122],[174,122,29],[116,188,53],[104,98,10],[87,10,98],[98,100,87],[79,87,100],[79,100,107],[90,79,107],[90,107,178],[178,177,90],[53,90,177],[53,177,117],[117,116,53],[54,53,188],[54,188,187],[67,54,187],[67,187,130],[69,67,130],[69,130,157],[12,69,157],[12,157,136],[136,133,12],[12,133,125],[125,127,12],[13,12,127],[127,146,13],[57,13,146],[57,146,160],[95,57,160],[95,160,173],[173,113,95],[94,95,113],[113,112,94],[52,94,112],[48,52,112],[112,167,48],[35,48,167],[35,167,166],[19,35,166],[139,170,50],[50,49,139],[166,155,19],[20,19,155],[155,124,20],[23,20,124],[23,124,123],[49,23,123],[49,123,142],[142,139,49],[190,191,170],[192,191,190],[191,192,51],[191,51,50],[170,169,190],[169,51,192],[169,192,190],[170,191,50],[193,194,195],[196,197,198],[199,200,201],[198,202,203],[204,201,200],[205,204,200],[206,207,208],[206,208,205],[206,205,200],[207,206,209],[207,209,203],[207,203,202],[202,198,197],[197,196,210],[197,210,195],[197,195,194],[8,88,195],[8,195,210],[210,196,8],[196,198,8],[198,203,8],[203,209,8],[209,206,8],[206,200,8],[202,197,104],[207,202,104],[103,104,197],[103,197,194],[193,195,88],[88,103,194],[88,194,193],[200,199,8],[199,201,8],[204,205,6],[6,8,201],[6,201,204],[10,6,205],[10,205,208],[104,10,208],[104,208,207]
];
} elsif ($name eq 'pyramid') {
$vertices = [
[10,10,40],[0,0,0],[20,0,0],[20,20,0],[0,20,0],
];
$facets = [
[0,1,2],[0,3,4],[3,1,4],[1,3,2],[3,0,2],[4,1,0],
];
} elsif ($name eq 'two_hollow_squares') {
$vertices = [
[66.7133483886719,104.286666870117,0],[66.7133483886719,95.7133331298828,0],[65.6666870117188,94.6666717529297,0],[75.2866821289062,95.7133331298828,0],[76.3333435058594,105.333335876465,0],[76.3333435058594,94.6666717529297,0],[65.6666870117188,105.33332824707,0],[75.2866821289062,104.286666870117,0],[71.1066818237305,104.58666229248,2.79999995231628],[66.4133529663086,104.58666229248,2.79999995231628],[75.5866851806641,104.58666229248,2.79999995231628],[66.4133529663086,99.8933334350586,2.79999995231628],[66.4133529663086,95.4133377075195,2.79999995231628],[71.1066818237305,95.4133377075195,2.79999995231628],[75.5866851806641,95.4133377075195,2.79999995231628],[75.5866851806641,100.106666564941,2.79999995231628],[74.5400161743164,103.540000915527,2.79999995231628],[70.0320129394531,103.540000915527,2.79999995231628],[67.4600067138672,103.540000915527,2.79999995231628],[67.4600067138672,100.968002319336,2.79999995231628],[67.4600067138672,96.4599990844727,2.79999995231628],[74.5400161743164,99.0319976806641,2.79999995231628],[74.5400161743164,96.4599990844727,2.79999995231628],[70.0320129394531,96.4599990844727,2.79999995231628],[123.666717529297,94.6666717529297,0],[134.333312988281,94.6666717529297,0],[124.413360595703,95.4133377075195,2.79999995231628],[129.106674194336,95.4133377075195,2.79999995231628],[133.586669921875,95.4133377075195,2.79999995231628],[123.666717529297,105.33332824707,0],[124.413360595703,104.58666229248,2.79999995231628],[124.413360595703,99.8933334350586,2.79999995231628],[134.333312988281,105.33332824707,0],[129.106674194336,104.58666229248,2.79999995231628],[133.586669921875,104.58666229248,2.79999995231628],[133.586669921875,100.106666564941,2.79999995231628],[124.713317871094,104.286666870117,0],[124.713317871094,95.7133331298828,0],[133.286712646484,95.7133331298828,0],[133.286712646484,104.286666870117,0],[132.540023803711,103.540000915527,2.79999995231628],[128.032028198242,103.540008544922,2.79999995231628],[125.460006713867,103.540000915527,2.79999995231628],[125.460006713867,100.968002319336,2.79999995231628],[125.460006713867,96.4599990844727,2.79999995231628],[132.540023803711,99.0319976806641,2.79999995231628],[132.540023803711,96.4599990844727,2.79999995231628],[128.032028198242,96.4599990844727,2.79999995231628],
];
$facets = [
[0,1,2],[3,4,5],[6,4,0],[6,0,2],[2,1,5],[7,4,3],[1,3,5],[0,4,7],[4,6,8],[6,9,8],[4,8,10],[6,2,9],[2,11,9],[2,12,11],[2,5,12],[5,13,12],[5,14,13],[4,10,15],[5,4,14],[4,15,14],[7,16,17],[0,7,18],[7,17,18],[1,19,20],[1,0,19],[0,18,19],[7,3,21],[3,22,21],[7,21,16],[3,23,22],[3,1,23],[1,20,23],[24,25,26],[25,27,26],[25,28,27],[29,24,30],[24,31,30],[24,26,31],[32,29,33],[29,30,33],[32,33,34],[32,34,35],[25,32,28],[32,35,28],[36,37,24],[38,32,25],[29,32,36],[29,36,24],[24,37,25],[39,32,38],[37,38,25],[36,32,39],[39,40,41],[36,39,42],[39,41,42],[37,43,44],[37,36,43],[36,42,43],[39,38,45],[38,46,45],[39,45,40],[38,47,46],[38,37,47],[37,44,47],[16,8,9],[16,10,8],[10,16,15],[15,16,21],[22,15,21],[15,22,14],[22,23,14],[23,20,14],[17,16,9],[18,17,9],[19,18,9],[19,9,11],[19,11,20],[13,14,20],[20,11,12],[13,20,12],[41,40,30],[42,41,30],[43,42,30],[43,30,31],[43,31,44],[27,28,44],[44,31,26],[27,44,26],[40,33,30],[40,34,33],[34,40,35],[35,40,45],[46,35,45],[35,46,28],[46,47,28],[47,44,28],
];
} elsif ($name eq 'small_dorito') {
$vertices = [
[6.00058937072754,-22.9982089996338,0],[22.0010242462158,-49.9998741149902,0],[-9.99957847595215,-49.999870300293,0],[6.00071382522583,-32.2371635437012,28.0019245147705],[11.1670551300049,-37.9727020263672,18.9601669311523],[6.00060224533081,-26.5392456054688,10.7321853637695]
];
$facets = [
[0,1,2],[3,4,5],[2,1,4],[2,4,3],[2,3,5],[2,5,0],[5,4,1],[5,1,0]
];
} elsif ($name eq 'bridge') {
$vertices = [
[75,84.5,8],[125,84.5,8],[75,94.5,8],[120,84.5,5],[125,94.5,8],[75,84.5,0],[80,84.5,5],[125,84.5,0],[125,94.5,0],[80,94.5,5],[75,94.5,0],[120,94.5,5],[120,84.5,0],[80,94.5,0],[80,84.5,0],[120,94.5,0]
];
$facets = [
[0,1,2],[1,0,3],[2,1,4],[2,5,0],[0,6,3],[1,3,7],[1,8,4],[4,9,2],[10,5,2],[5,6,0],[6,11,3],[3,12,7],[7,8,1],[4,8,11],[4,11,9],[9,10,2],[10,13,5],[14,6,5],[9,11,6],[11,12,3],[12,8,7],[11,8,15],[13,10,9],[5,13,14],[14,13,6],[6,13,9],[15,12,11],[15,8,12]
];
} elsif ($name eq 'bridge_with_hole') {
$vertices = [
[75,69.5,8],[80,76.9091644287109,8],[75,94.5,8],[125,69.5,8],[120,76.9091644287109,8],[120,87.0908355712891,8],[80,87.0908355712891,8],[125,94.5,8],[80,87.0908355712891,5],[120,87.0908355712891,5],[125,94.5,0],[120,69.5,0],[120,94.5,0],[125,69.5,0],[120,94.5,5],[80,94.5,5],[80,94.5,0],[75,94.5,0],[80,69.5,5],[80,69.5,0],[80,76.9091644287109,5],[120,69.5,5],[75,69.5,0],[120,76.9091644287109,5]
];
$facets = [
[0,1,2],[1,0,3],[1,3,4],[4,3,5],[2,6,7],[6,2,1],[7,6,5],[7,5,3],[5,8,9],[8,5,6],[10,11,12],[11,10,13],[14,8,15],[8,14,9],[2,16,17],[16,2,15],[15,2,14],[14,10,12],[10,14,7],[7,14,2],[16,18,19],[18,16,20],[20,16,1],[1,16,8],[8,16,15],[6,1,8],[3,11,13],[11,3,21],[21,3,18],[18,22,19],[22,18,0],[0,18,3],[16,22,17],[22,16,19],[2,22,0],[22,2,17],[5,23,4],[23,11,21],[11,23,12],[12,23,9],[9,23,5],[12,9,14],[23,18,20],[18,23,21],[10,3,13],[3,10,7],[1,23,20],[23,1,4]
];
} elsif ($name eq 'step') {
$vertices = [
[0,20,5],[0,20,0],[0,0,5],[0,0,0],[20,0,0],[20,0,5],[1,19,5],[1,1,5],[19,1,5],[20,20,5],[19,19,5],[20,20,0],[19,19,10],[1,19,10],[1,1,10],[19,1,10]
];
$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;
}
my $mesh = Slic3r::TriangleMesh->new;
$mesh->ReadFromPerl($vertices, $facets);
$mesh->repair;
$mesh->scale_xyz(Slic3r::Pointf3->new(@{$params{scale_xyz}})) if $params{scale_xyz};
$mesh->translate(@{$params{translate}}) if $params{translate};
return $mesh;
}
sub model {
my ($model_name, %params) = @_;
my $model = Slic3r::Model->new;
my $object = $model->add_object(input_file => "${model_name}.stl");
$model->set_material($model_name);
$object->add_volume(mesh => mesh($model_name, %params), material_id => $model_name);
$object->add_instance(
offset => Slic3r::Pointf->new(0,0),
rotation => $params{rotation} // 0,
scaling_factor => $params{scale} // 1,
);
return $model;
}
sub init_print {
my ($models, %params) = @_;
my $config = Slic3r::Config->new;
$config->apply($params{config}) if $params{config};
$config->set('gcode_comments', 1) if $ENV{SLIC3R_TESTS_GCODE};
my $print = Slic3r::Print->new;
$print->apply_config($config);
$models = [$models] if ref($models) ne 'ARRAY';
$models = [ map { ref($_) ? $_ : model($_, %params) } @$models ];
for my $model (@$models) {
die "Unknown model in test" if !defined $model;
if (defined $params{duplicate} && $params{duplicate} > 1) {
$model->duplicate($params{duplicate} // 1, $print->config->min_object_distance);
}
$model->arrange_objects($print->config->min_object_distance);
$model->center_instances_around_point($params{print_center} ? Slic3r::Pointf->new(@{$params{print_center}}) : Slic3r::Pointf->new(100,100));
foreach my $model_object (@{$model->objects}) {
$print->auto_assign_extruders($model_object);
$print->add_model_object($model_object);
}
}
$print->validate;
# We return a proxy object in order to keep $models alive as required by the Print API.
return Slic3r::Test::Print->new(
print => $print,
models => $models,
);
}
sub gcode {
my ($print) = @_;
$print = $print->print if $print->isa('Slic3r::Test::Print');
my $fh = IO::Scalar->new(\my $gcode);
$print->process;
$print->export_gcode(output_fh => $fh, quiet => 1);
$fh->close;
return $gcode;
}
sub _eq {
my ($a, $b) = @_;
return abs($a - $b) < epsilon;
}
sub add_facet {
my ($facet, $vertices, $facets) = @_;
push @$facets, [];
for my $i (0..2) {
my $v = first { $vertices->[$_][X] == $facet->[$i][X] && $vertices->[$_][Y] == $facet->[$i][Y] && $vertices->[$_][Z] == $facet->[$i][Z] } 0..$#$vertices;
if (!defined $v) {
push @$vertices, [ @{$facet->[$i]}[X,Y,Z] ];
$v = $#$vertices;
}
$facets->[-1][$i] = $v;
}
}
package Slic3r::Test::Print;
use Moo;
has 'print' => (is => 'ro', required => 1, handles => [qw(process apply_config)]);
has 'models' => (is => 'ro', required => 1);
1;
Slic3r-1.3.0/lib/Slic3r/Test/ 0000775 0000000 0000000 00000000000 13274424355 0015507 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/lib/Slic3r/Test/SectionCut.pm 0000664 0000000 0000000 00000015567 13274424355 0020143 0 ustar 00root root 0000000 0000000 # 2D cut in the XZ plane through the toolpaths.
# For debugging purposes.
package Slic3r::Test::SectionCut;
use Moo;
use List::Util qw(any min max);
use Slic3r::Geometry qw(unscale);
use Slic3r::Geometry::Clipper qw(intersection_pl);
use SVG;
use Slic3r::SVG;
has 'print' => (is => 'ro', required => 1);
has 'scale' => (is => 'ro', default => sub { 30 });
has 'y_percent' => (is => 'ro', default => sub { 0.5 }); # Y coord of section line expressed as factor
has '_line' => (is => 'lazy');
has '_height' => (is => 'rw');
has '_svg' => (is => 'rw');
has '_svg_style' => (is => 'rw', default => sub { {} });
has '_bb' => (is => 'lazy');
sub _build__line {
my $self = shift;
# calculate the Y coordinate of the section line
my $bb = $self->_bb;
my $y = ($bb->y_min + $bb->y_max) * $self->y_percent;
# store our section line
return Slic3r::Line->new([ $bb->x_min, $y ], [ $bb->x_max, $y ]);
}
sub _build__bb {
my ($self) = @_;
return $self->print->bounding_box;
}
sub export_svg {
my $self = shift;
my ($filename) = @_;
# get bounding box of print and its height
# (Print should return a BoundingBox3 object instead)
my $print_size = $self->_bb->size;
$self->_height(max(map $_->print_z, map @{$_->layers}, @{$self->print->objects}));
# initialize the SVG canvas
$self->_svg(my $svg = SVG->new(
width => $self->scale * unscale($print_size->x),
height => $self->scale * $self->_height,
));
# set default styles
$self->_svg_style->{'stroke-width'} = 1;
$self->_svg_style->{'fill-opacity'} = 0.5;
$self->_svg_style->{'stroke-opacity'} = 0.2;
# plot perimeters
$self->_svg_style->{'stroke'} = '#EE0000';
$self->_svg_style->{'fill'} = '#FF0000';
$self->_plot_group(sub { map @{$_->perimeters}, @{$_[0]->regions} });
# plot infill
$self->_svg_style->{'stroke'} = '#444444';
$self->_svg_style->{'fill'} = '#454545';
$self->_plot_group(sub { map @{$_->fills}, @{$_[0]->regions} });
# plot support material
$self->_svg_style->{'stroke'} = '#12EF00';
$self->_svg_style->{'fill'} = '#22FF00';
$self->_plot_group(sub { $_[0]->isa('Slic3r::Layer::Support') ? ($_[0]->support_fills, $_[0]->support_interface_fills) : () });
Slic3r::open(\my $fh, '>', $filename);
print $fh $svg->xmlify;
close $fh;
printf "Section cut SVG written to %s\n", $filename;
}
sub _plot_group {
my $self = shift;
my ($filter) = @_;
foreach my $object (@{$self->print->objects}) {
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
my @paths = map $_->clone, map @{$_->flatten}, grep defined $_, $filter->($layer);
my $name = sprintf "%s %d (z = %f)",
($layer->isa('Slic3r::Layer::Support') ? 'Support Layer' : 'Layer'),
$layer->id,
$layer->print_z;
my $g = $self->_svg->getElementByID($name)
|| $self->_svg->group(id => $name, style => { %{$self->_svg_style} });
foreach my $copy (@{$object->_shifted_copies}) {
if (0) {
# export plan with section line and exit
my @grown = map @{$_->grow}, @paths;
$_->translate(@$copy) for @paths;
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"line.svg",
no_arrows => 1,
polygons => \@grown,
red_lines => [ $self->_line ],
);
exit;
}
$self->_plot_path($_, $g, $copy, $layer) for @paths;
}
}
}
}
sub _plot_path {
my ($self, $path, $g, $copy, $layer) = @_;
my $grown = $path->grow;
$_->translate(@$copy) for @$grown;
my $intersections = intersection_pl(
[ $self->_line->as_polyline ],
$grown,
);
if (0 && @$intersections) {
# export plan with section line and exit
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"intersections.svg",
no_arrows => 1,
polygons => $grown,
red_lines => [ $self->_line ],
);
exit;
}
#Â turn intersections to lines
die "Intersection has more than two points!\n"
if any { @$_ > 2 } @$intersections;
my @lines = map Slic3r::Line->new(@$_), @$intersections;
my $is_bridge = $path->isa('Slic3r::ExtrusionPath')
? $path->is_bridge
: any { $_->is_bridge } @$path;
foreach my $line (@lines) {
my $this_path = $path;
if ($path->isa('Slic3r::ExtrusionLoop')) {
# FIXME: find the actual ExtrusionPath of this intersection
$this_path = $path->[0];
}
# align to canvas
$line->translate(-$self->_bb->x_min, 0);
# we want lines oriented from left to right in order to draw rectangles correctly
$line->reverse if $line->a->x > $line->b->x;
if ($is_bridge) {
my $radius = $this_path->width / 2;
my $width = unscale abs($line->b->x - $line->a->x);
if ((10 * $radius) < $width) {
# we're cutting the path in the longitudinal direction, so we've got a rectangle
$g->rectangle(
'x' => $self->scale * unscale($line->a->x),
'y' => $self->scale * $self->_y($layer->print_z),
'width' => $self->scale * $width,
'height' => $self->scale * $radius * 2,
'rx' => $self->scale * $radius * 0.35,
'ry' => $self->scale * $radius * 0.35,
);
} else {
$g->circle(
'cx' => $self->scale * (unscale($line->a->x) + $radius),
'cy' => $self->scale * $self->_y($layer->print_z - $radius),
'r' => $self->scale * $radius,
);
}
} else {
my $height = $this_path->height != -1 ? $this_path->height : $layer->height;
$g->rectangle(
'x' => $self->scale * unscale($line->a->x),
'y' => $self->scale * $self->_y($layer->print_z),
'width' => $self->scale * unscale($line->b->x - $line->a->x),
'height' => $self->scale * $height,
'rx' => $self->scale * $height * 0.5,
'ry' => $self->scale * $height * 0.5,
);
}
}
}
sub _y {
my $self = shift;
my ($y) = @_;
return $self->_height - $y;
}
1;
Slic3r-1.3.0/package/ 0000775 0000000 0000000 00000000000 13274424355 0014256 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/package/common/ 0000775 0000000 0000000 00000000000 13274424355 0015546 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/package/common/coreperl 0000664 0000000 0000000 00000000327 13274424355 0017306 0 ustar 00root root 0000000 0000000 # 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
Slic3r-1.3.0/package/common/shell.cpp 0000664 0000000 0000000 00000006513 13274424355 0017366 0 ustar 00root root 0000000 0000000 #include // from the Perl distribution
#include // from the Perl distribution
#ifdef WIN32
// Perl win32 specific includes, found in perl\\lib\\CORE\\win32.h
// Defines the windows specific convenience RunPerl() function,
// which is not available on other operating systems.
#include
#include
#endif
#include
#include
#ifdef WIN32
int main(int argc, char **argv, char **env)
{
// If the Slic3r is installed in a localized directory (containing non-iso
// characters), spaces or semicolons, use short file names.
char exe_path[MAX_PATH] = {0};
char script_path[MAX_PATH];
char gui_flag[6] = {"--gui"};
#ifdef FORCE_GUI
char** command_line = (char**)malloc(sizeof(char*) * ((++ argc) + 2));
#else
char** command_line = (char**)malloc(sizeof(char*) * ((++ argc) + 1));
#endif
{
// Unicode path. This will not be used directly, but to test, whether
// there are any non-ISO characters, in which case the path is converted to a
// short path (using 8.3 directory names).
wchar_t exe_path_w[MAX_PATH] = {0};
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];
bool needs_short_paths = false;
int len;
int i;
GetModuleFileNameA(NULL, exe_path, MAX_PATH-1);
GetModuleFileNameW(NULL, exe_path_w, MAX_PATH-1);
len = strlen(exe_path);
if (len != wcslen(exe_path_w)) {
needs_short_paths = true;
} else {
for (i = 0; i < len; ++ i)
if ((wchar_t)exe_path[i] != exe_path_w[i] || exe_path[i] == ' ' ||
exe_path[i] == ';') {
needs_short_paths = true;
break;
}
}
if (needs_short_paths) {
wchar_t exe_path_short[MAX_PATH] = {0};
GetShortPathNameW(exe_path_w, exe_path_short, MAX_PATH);
len = wcslen(exe_path_short);
for (i = 0; i <= len; ++ i)
exe_path[i] = (char)exe_path_short[i];
}
_splitpath(exe_path, drive, dir, fname, ext);
_makepath(script_path, drive, dir, NULL, NULL);
if (needs_short_paths)
printf("Slic3r installed in a loclized path. Using an 8.3 path: \"%s\"\n",
script_path);
SetDllDirectoryA(script_path);
_makepath(script_path, drive, dir, "slic3r", "pl");
command_line[0] = exe_path;
command_line[1] = script_path;
memcpy(command_line + 2, argv + 1, sizeof(char*) * (argc - 2));
#ifdef FORCE_GUI
command_line[argc] = gui_flag;
command_line[argc+1] = NULL;
#else
command_line[argc] = NULL;
#endif
// Unset the PERL5LIB and PERLLIB environment variables.
SetEnvironmentVariable("PERL5LIB", NULL);
SetEnvironmentVariable("PERLLIB", NULL);
#if 0
printf("Arguments: \r\n");
for (size_t i = 0; i < argc + 1; ++ i)
printf(" %d: %s\r\n", i, command_line[i]);
#endif
}
#ifdef FORCE_GUI
RunPerl(argc+1, command_line, NULL);
#else
RunPerl(argc, command_line, NULL);
#endif
free(command_line);
}
#else
int main(int argc, char **argv, char **env)
{
PerlInterpreter *my_perl = perl_alloc();
if (my_perl == NULL) {
fprintf(stderr, "Cannot start perl interpreter. Exiting.\n");
return -1;
}
perl_construct(my_perl);
#ifdef FORCE_GUI
char* command_line[] = { "slic3r", "slic3r.pl", "--gui" };
#else
char* command_line[] = { "slic3r", "slic3r.pl" };
#endif
perl_parse(my_perl, NULL, 3, command_line, (char **)NULL);
perl_run(my_perl);
perl_destruct(my_perl);
perl_free(my_perl);
}
#endif
Slic3r-1.3.0/package/common/util.sh 0000664 0000000 0000000 00000004512 13274424355 0017061 0 ustar 00root root 0000000 0000000 #!/bin/bash
# must be run from the root
function set_version ()
{
SLIC3R_VERSION=$(grep "VERSION" xs/src/libslic3r/libslic3r.h | awk -F\" '{print $2}')
}
# Cache the SHA1 for this build commit.
function get_commit () {
if [ ! -z ${TRAVIS_COMMIT+x} ]; then
# Travis sets the sha1 in TRAVIS_COMMIT
COMMIT_SHA1=$(git rev-parse --short $TRAVIS_COMMIT)
else
# should be able to get it properly
COMMIT_SHA1=$(git rev-parse --short HEAD)
fi
}
function set_build_id ()
{
echo "Setting SLIC3R_BUILD_ID"
if [ $(git describe &>/dev/null) ]; then
SLIC3R_BUILD_ID=$(git describe)
TAGGED=true
else
SLIC3R_BUILD_ID=${SLIC3R_VERSION}-${COMMIT_SHA1}
fi
}
function set_branch ()
{
echo "Setting current_branch"
if [ -z ${TRAVIS_BRANCH} ] && [ -z ${GIT_BRANCH+x} ] && [ -z ${APPVEYOR_REPO_BRANCH+x} ]; then
current_branch=$(git symbolic-ref HEAD | sed 's!refs\/heads\/!!')
else
current_branch="unknown"
if [ ! -z ${GIT_BRANCH+x} ]; then
echo "Setting to GIT_BRANCH"
current_branch=$(echo $GIT_BRANCH | cut -d / -f 2)
fi
if [ ! -z ${APPVEYOR_REPO_BRANCH+x} ]; then
echo "Setting to APPVEYOR_REPO_BRANCH"
current_branch=$APPVEYOR_REPO_BRANCH
fi
if [ ! -z ${TRAVIS_BRANCH} ]; then
if [ "${TRAVIS_BRANCH}" != "false" ]; then
echo "Setting to TRAVIS_BRANCH"
current_branch=$TRAVIS_BRANCH
fi
fi
fi
if [ -z ${current_branch+x} ]; then
current_branch="unknown"
fi
}
function set_app_name ()
{
set_branch
if [ "$current_branch" == "master" ]; then
appname=Slic3r
else
appname=Slic3r-${current_branch}
fi
}
function set_pr_id ()
{
echo "Setting PR_ID if available."
if [ ! -z ${GITHUB_PR_NUMBER+x} ]; then
PR_ID=$GITHUB_PR_NUMBER
fi
if [ ! -z ${APPVEYOR_PULL_REQUEST_NUMBER+x} ]; then
PR_ID=$APPVEYOR_PULL_REQUEST_NUMBER
fi
if [ ! -z ${TRAVIS_PULL_REQUEST_BRANCH+x} ] && [ "${TRAVIS_PULL_REQUEST}" != "false" ] ; then
PR_ID=$TRAVIS_PULL_REQUEST
fi
if [ ! -z ${PR_ID+x} ]; then
echo "Setting PR_ID to $PR_ID."
else
echo "PR_ID remains unset."
fi
}
function install_par ()
{
cpanm -q PAR::Packer
cpanm -q Wx::Perl::Packager
}
Slic3r-1.3.0/package/deploy/ 0000775 0000000 0000000 00000000000 13274424355 0015552 5 ustar 00root root 0000000 0000000 Slic3r-1.3.0/package/deploy/bintray.sh 0000775 0000000 0000000 00000003665 13274424355 0017573 0 ustar 00root root 0000000 0000000 #!/bin/bash
# Prerequistes
# Environment variables:
# BINTRAY_API_KEY - Working API key
# BINTRAY_API_USER - Bintray username.
# Run this from the repository root (required to get slic3r version)
source $(dirname $0)/common/util.sh
SLIC3R_VERSION=$(grep "VERSION" xs/src/libslic3r/libslic3r.h | awk -F\" '{print $2}')
get_commit
set_build_id
set_branch
set_pr_id
if [ "$current_branch" == "master" ] && [ -z ${PR_ID} ]; then
# If building master, goes in slic3r_dev or slic3r, depending on whether or not this is a tagged build
if [ -z ${TAGGED+x} ]; then
SLIC3R_PKG=slic3r_dev
else
SLIC3R_PKG=slic3r
fi
version=$SLIC3R_BUILD_ID
else
# If building a branch, put the package somewhere else.
echo "Delploying pull request $PR_ID"
SLIC3R_PKG=Slic3r_Branches
version=${SLIC3R_BUILD_ID}-PR${PR_ID}
fi
file=$1
echo "Deploying $file to $version on Bintray repo $SLIC3R_PKG..."
API=${BINTRAY_API_KEY}
USER=${BINTRAY_API_USER}
echo "Creating version: $version"
curl -s -X POST -d "{ \"name\": \"$version\", \"released\": \"ISO8601 $(date +%Y-%m-%d'T'%H:%M:%S)\", \"desc\": \"This version...\", \"github_release_notes_file\": \"RELEASE.txt\", \"github_use_tag_release_notes\": true, \"vcs_tag\": \"$version\" }" -u${USER}:${API} https://api.bintray.com/content/lordofhyphens/Slic3r/${SLIC3R_PKG}/versions
echo "Publishing ${file} to ${version}..."
curl -s -H "X-Bintray-Package: $SLIC3R_PKG" -H "X-Bintray-Version: $version" -H 'X-Bintray-Publish: 1' -H 'X-Bintray-Override: 1' -T $file -u${USER}:${API} https://api.bintray.com/content/lordofhyphens/Slic3r/$(basename $1)
#curl -X POST -u${USER}:${API} https://api.bintray.com/content/lordofhyphens/Slic3r/${SLIC3R_PKG}/$version/publish
# Wait 5s for the server to catch up
#sleep 5
#curl -H 'Content-Type: application/json' -X PUT -d "{ \"list_in_downloads\":true }" -u${USER}:${API} https://api.bintray.com/file_metadata/lordofhyphens/Slic3r/$(basename $1)
exit 0
Slic3r-1.3.0/package/deploy/sftp-symlink.sh 0000775 0000000 0000000 00000001741 13274424355 0020554 0 ustar 00root root 0000000 0000000 #!/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
Slic3r-1.3.0/package/deploy/sftp.sh 0000775 0000000 0000000 00000001406 13274424355 0017066 0 ustar 00root root 0000000 0000000 #!/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
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")
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
Slic3r-1.3.0/package/deploy/slic3r-upload.ppk.enc 0000664 0000000 0000000 00000002700 13274424355 0021512 0 ustar 00root root 0000000 0000000 (ªÓô9„ôÇrµ×º±bÅZæJ3GØëž;RïÊX³šÀËß$\P…ßš¤ ß5ì :ÉÄCöÛŸ6ýYÕb¡9òçඤµmçŸ2‹uÈž{§É£bYâ
°{~w@‡œUiúͤ‰j~!}4bàoZšõÆ—œu‡²Oa1ÿ¹Ç]xõEÈŒÎ~—’;ÛZa˜$Œ+z¡q}ÄoßqÛ߆0Ë @gÁ£] 5.õl‚”4‹éqGõü~È/ûÚ&û³ö-¶¥}”Äg3J ’g‹ÇGbߨfÖ¿£*Bü8®õ¹….ivT{×I5î˜iôÒÎ%ÙÉX¤Û¥îëûn£Û!Ú©Kï
¶`Зò¿Jˆ”ëfNÔ¨n¤QöO¯5ç°Z(°•