Slic3r-version_1.39.1/000077500000000000000000000000001324354444700145235ustar00rootroot00000000000000Slic3r-version_1.39.1/.github/000077500000000000000000000000001324354444700160635ustar00rootroot00000000000000Slic3r-version_1.39.1/.github/CONTRIBUTING.md000066400000000000000000000032031324354444700203120ustar00rootroot00000000000000Did 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 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)) 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. Slic3r-version_1.39.1/.github/ISSUE_TEMPLATE.md000066400000000000000000000013371324354444700205740ustar00rootroot00000000000000### Version _Version of Slic3r used goes here_ _Use `About->About Slic3r` for release versions_ _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?_ #### STL/Config (.ZIP) where problem occurs _Upload a zipped copy of an STL and your config (`File -> Export Config`)_ Slic3r-version_1.39.1/.gitignore000066400000000000000000000002111324354444700165050ustar00rootroot00000000000000Build Build.bat MYMETA.json MYMETA.yml _build blib xs/buildtmp *.o MANIFEST.bak xs/MANIFEST.bak xs/assertlib* .init_bundle.ini local-lib Slic3r-version_1.39.1/Build.PL000066400000000000000000000112731324354444700160230ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Config; use File::Spec; my %prereqs = qw( Devel::CheckLib 0 ExtUtils::MakeMaker 6.80 ExtUtils::ParseXS 3.22 ExtUtils::XSpp 0 ExtUtils::Typemaps 0 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 ); my %recommends = qw( Class::XSAccessor 0 Test::Harness 0 ); my $sudo = grep { $_ eq '--sudo' } @ARGV; my $gui = grep { $_ eq '--gui' } @ARGV; my $nolocal = grep { $_ eq '--nolocal' } @ARGV; if ($gui) { %prereqs = qw( Class::Accessor 0 Wx 0.9918 Socket 2.016 ); %recommends = qw( Growl::GNTP 0.15 Wx::GLCanvas 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; } } 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') if ! $nolocal; # 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|Test::Harness)$/; 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"; } } } } print "\n"; if ($gui) { print "Perl dependencies for the Slic3r GUI were installed.\n"; } else { print "Perl dependencies for Slic3r were installed.\n"; print "If you also want to use the GUI you can now run `perl Build.PL --gui` to install the required modules.\n"; } print "\n"; print "In the next step, you need to build the Slic3r C++ library.\n"; print "1) Create a build directory and change to it\n"; print "2) run cmake .. -DCMAKE_BUILD_TYPE=Release\n"; print "3) run make\n"; print "4) to execute the automatic tests, run ctest --verbose\n"; __END__ Slic3r-version_1.39.1/CMakeLists.txt000066400000000000000000000044521324354444700172700ustar00rootroot00000000000000# Boost 1.63 requires CMake 3.7 or newer cmake_minimum_required(VERSION 2.8) project(Slic3r) if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "No build type selected, default to Release") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (default Release)" FORCE) endif() if(DEFINED ENV{SLIC3R_STATIC}) set(SLIC3R_STATIC_INITIAL $ENV{SLIC3R_STATIC}) else() if (MSVC OR MINGW OR APPLE) set(SLIC3R_STATIC_INITIAL 1) else() set(SLIC3R_STATIC_INITIAL 0) endif() endif() option(SLIC3R_STATIC "Compile Slic3r with static libraries (Boost, TBB, glew)" ${SLIC3R_STATIC_INITIAL}) option(SLIC3R_GUI "Compile Slic3r with GUI components (OpenGL, wxWidgets)" 1) option(SLIC3R_PRUSACONTROL "Compile Slic3r with the PrusaControl prject file format (requires wxWidgets base library)" 1) option(SLIC3R_PROFILE "Compile Slic3r with an invasive Shiny profiler" 0) option(SLIC3R_HAS_BROKEN_CROAK "Compile Slic3r for a broken Strawberry Perl 64bit" 0) option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1) if (MSVC AND SLIC3R_MSVC_COMPILE_PARALLEL) add_compile_options(/MP) endif () # Find the Perl interpreter, add local-lib to PATH and PERL5LIB environment variables, # so the locally installed modules (mainly the Alien::wxPerl) will be reached. if (WIN32) set(ENV_PATH_SEPARATOR ";") else() set(ENV_PATH_SEPARATOR ":") endif() set(ENV{PATH} "${PROJECT_SOURCE_DIR}/local-lib/bin${ENV_PATH_SEPARATOR}$ENV{PATH}") set(ENV{PERL5LIB} "${PROJECT_SOURCE_DIR}/local-lib/lib/perl${ENV_PATH_SEPARATOR}$ENV{PERL5LIB}") message("PATH: $ENV{PATH}") message("PERL5LIB: $ENV{PERL5LIB}") find_package(Perl REQUIRED) add_subdirectory(xs) enable_testing () get_filename_component(PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY) add_test (NAME xs COMMAND "${PERL_EXECUTABLE}" ${PERL_BIN_PATH}/prove -I ${PROJECT_SOURCE_DIR}/local-lib/lib/perl5 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/xs) add_test (NAME integration COMMAND "${PERL_EXECUTABLE}" ${PERL_BIN_PATH}/prove WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) install(PROGRAMS slic3r.pl DESTINATION bin RENAME slic3r-prusa3d) file(GLOB MyVar var/*.png) install(FILES ${MyVar} DESTINATION share/slic3r-prusa3d) install(FILES lib/Slic3r.pm DESTINATION lib/slic3r-prusa3d) install(DIRECTORY lib/Slic3r DESTINATION lib/slic3r-prusa3d) Slic3r-version_1.39.1/LICENSE000066400000000000000000001045551324354444700155420ustar00rootroot00000000000000 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-version_1.39.1/README.md000066400000000000000000000555111324354444700160110ustar00rootroot00000000000000_Q: Oh cool, a new RepRap slicer?_ A: Yes. Slic3r ====== Prebuilt Windows, OSX and Linux binaries are available through the [git releases page](https://github.com/prusa3d/Slic3r/releases). Slic3r takes 3D models (STL, OBJ, AMF) and converts them into G-code instructions for 3D printers. It's compatible with any modern printer based on the RepRap toolchain, including all those based on the Marlin, Sprinter and Repetier firmware. It also works with Mach3, LinuxCNC and Machinekit controllers. See the [project homepage](http://slic3r.org/) at slic3r.org and the [manual](http://manual.slic3r.org/) for more information. ### What language is it written in? The core geometric algorithms and data structures are written in C++, and Perl is used for high-level flow abstraction, GUI and testing. If you're wondering why Perl, see https://xkcd.com/224/ The C++ API is public and its use in other projects is encouraged. The goal is to make Slic3r fully modular so that any part of its logic can be used separately. ### What are Slic3r's main features? Key features are: * **multi-platform** (Linux/Mac/Win) and packaged as standalone-app with no dependencies required * complete **command-line interface** to use it with no GUI * multi-material **(multiple extruders)** object printing * multiple G-code flavors supported (RepRap, Makerbot, Mach3, Machinekit etc.) * ability to plate **multiple objects having distinct print settings** * **multithread** processing * **STL auto-repair** (tolerance for broken models) * wide automated unit testing Other major features are: * combine infill every 'n' perimeters layer to speed up printing * **3D preview** (including multi-material files) * **multiple layer heights** in a single print * **spiral vase** mode for bumpless vases * fine-grained configuration of speed, acceleration, extrusion width * several infill patterns including honeycomb, spirals, Hilbert curves * support material, raft, brim, skirt * **standby temperature** and automatic wiping for multi-extruder printing * customizable **G-code macros** and output filename with variable placeholders * support for **post-processing scripts** * **cooling logic** controlling fan speed and dynamic print speed ### How to install? You can download a precompiled package from [slic3r.org](http://slic3r.org/); it will run without the need for any dependency. 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/prusa3d/Slic3r/wiki/How-to-compile-Slic3r-Prusa-Edition-on-MS-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: * [Pull Request Milestone](https://github.com/alexrj/Slic3r/milestone/31) * Please comment in the related github issue that you are working on it so that other people know. * Items in the [TODO](https://github.com/alexrj/Slic3r/wiki/TODO) wiki page. * Please comment in the related github issue that you are working on it so that other people know. * Drop me a line at aar@cpan.org. * You can also find me (rarely) in #reprap and in #slic3r on [FreeNode](https://webchat.freenode.net) with the nickname _Sound_. Another contributor, _LoH_, is also in both channels. * Add an [issue](https://github.com/alexrj/Slic3r/issues) to the github tracker if it isn't already present. Before sending patches and pull requests contact me (preferably through opening a github issue or commenting on an existing, related, issue) to discuss your proposed changes: this way we'll ensure nobody wastes their time and no conflicts arise in development. ### What's Slic3r license? Slic3r is licensed under the _GNU Affero General Public License, version 3_. The author is Alessandro Ranellucci. The [Silk icon set](http://www.famfamfam.com/lab/icons/silk/) used in Slic3r is licensed under the _Creative Commons Attribution 3.0 License_. The author of the Silk icon set is Mark James. ### How can I invoke slic3r.pl 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-plater Disable the plater tab --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: --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-comments Make G-code verbose by adding comments (default: no) 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) If you want to change a preset file, just do slic3r.pl --load config.ini --layer-height 0.25 --save config.ini If you want to slice a file overriding an option contained in your preset file: slic3r.pl --load config.ini --layer-height 0.25 file.stlSlic3r-version_1.39.1/cmake/000077500000000000000000000000001324354444700156035ustar00rootroot00000000000000Slic3r-version_1.39.1/cmake/modules/000077500000000000000000000000001324354444700172535ustar00rootroot00000000000000Slic3r-version_1.39.1/cmake/modules/FindAlienWx.cmake000066400000000000000000000076731324354444700224420ustar00rootroot00000000000000# Find the wxWidgets module based on the information provided by the Perl Alien::wxWidgets module. # Check for the Perl & PerlLib modules include(LibFindMacros) libfind_package(AlienWx Perl) libfind_package(AlienWx PerlLibs) if (AlienWx_DEBUG) message(STATUS " AlienWx_FIND_COMPONENTS=${AlienWx_FIND_COMPONENTS}") endif() # Execute an Alien::Wx module to find the relevant information regarding # the wxWidgets used by the Perl interpreter. # Perl specific stuff set(AlienWx_TEMP_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/AlienWx_TEMP_INCLUDE.txt) execute_process( COMMAND ${PERL_EXECUTABLE} -e " # Import Perl modules. use strict; use warnings; use Text::ParseWords; BEGIN { # CMake sets the environment variables CC and CXX to the detected C compiler. # There is an issue with the Perl ExtUtils::CBuilder, which does not handle whitespaces # in the paths correctly on Windows, so we rather drop the CMake auto-detected paths. delete \$ENV{CC}; delete \$ENV{CXX}; } use Alien::wxWidgets; use ExtUtils::CppGuess; # Test for a Visual Studio compiler my \$cpp_guess = ExtUtils::CppGuess->new; my \$mswin = \$^O eq 'MSWin32'; my \$msvc = \$cpp_guess->is_msvc; # List of wxWidgets components to be used. my @components = split /;/, '${AlienWx_FIND_COMPONENTS}'; # Query the available data from Alien::wxWidgets. my \$version = Alien::wxWidgets->version; my \$config = Alien::wxWidgets->config; my \$compiler = Alien::wxWidgets->compiler; my \$linker = Alien::wxWidgets->linker; my \$include_path = ' ' . Alien::wxWidgets->include_path; my \$defines = ' ' . Alien::wxWidgets->defines; my \$cflags = Alien::wxWidgets->c_flags; my \$linkflags = Alien::wxWidgets->link_flags; my \$libraries = ' ' . Alien::wxWidgets->libraries(@components); #my @libraries = Alien::wxWidgets->link_libraries(@components); #my @implib = Alien::wxWidgets->import_libraries(@components); #my @shrlib = Alien::wxWidgets->shared_libraries(@components); #my @keys = Alien::wxWidgets->library_keys; # 'gl', 'adv', ... #my \$library_path = Alien::wxWidgets->shared_library_path; #my \$key = Alien::wxWidgets->key; #my \$prefix = Alien::wxWidgets->prefix; my \$filename = '${AlienWx_TEMP_INCLUDE}'; open(my $fh, '>', \$filename) or die \"Could not open file '\$filename' \$!\"; # Convert a space separated lists to CMake semicolon separated lists, # escape the backslashes, # export the resulting list to a temp file. sub cmake_set_var { my (\$varname, \$content) = @_; # Remove line separators. \$content =~ s/\\r|\\n//g; # Escape the path separators. \$content =~ s/\\\\/\\\\\\\\\\\\\\\\/g; my @words = shellwords(\$content); print \$fh \"set(AlienWx_\$varname \\\"\" . join(';', @words) . \"\\\")\\n\"; } cmake_set_var('VERSION', \$version); \$include_path =~ s/ -I/ /g; cmake_set_var('INCLUDE_DIRS', \$include_path); \$libraries =~ s/ -L/ -LIBPATH:/g if \$msvc; cmake_set_var('LIBRARIES', \$libraries); #cmake_set_var('LIBRARY_DIRS', ); #\$defines =~ s/ -D/ /g; cmake_set_var('DEFINITIONS', \$defines); #cmake_set_var('DEFINITIONS_DEBUG', ); cmake_set_var('CXX_FLAGS', \$cflags); close \$fh; ") include(${AlienWx_TEMP_INCLUDE}) file(REMOVE ${AlienWx_TEMP_INCLUDE}) unset(AlienWx_TEMP_INCLUDE) if (AlienWx_DEBUG) message(STATUS " AlienWx_VERSION = ${AlienWx_VERSION}") message(STATUS " AlienWx_INCLUDE_DIRS = ${AlienWx_INCLUDE_DIRS}") message(STATUS " AlienWx_LIBRARIES = ${AlienWx_LIBRARIES}") message(STATUS " AlienWx_LIBRARY_DIRS = ${AlienWx_LIBRARY_DIRS}") message(STATUS " AlienWx_DEFINITIONS = ${AlienWx_DEFINITIONS}") message(STATUS " AlienWx_DEFINITIONS_DEBUG = ${AlienWx_DEFINITIONS_DEBUG}") message(STATUS " AlienWx_CXX_FLAGS = ${AlienWx_CXX_FLAGS}") endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(AlienWx REQUIRED_VARS AlienWx_INCLUDE_DIRS AlienWx_LIBRARIES # HANDLE_COMPONENTS VERSION_VAR AlienWx_VERSION) Slic3r-version_1.39.1/cmake/modules/FindEigen3.cmake000066400000000000000000000062601324354444700221740ustar00rootroot00000000000000# - Try to find Eigen3 lib # # This module supports requiring a minimum version, e.g. you can do # find_package(Eigen3 3.1.2) # to require version 3.1.2 or newer of Eigen3. # # Once done this will define # # EIGEN3_FOUND - system has eigen lib with correct version # EIGEN3_INCLUDE_DIR - the eigen include directory # EIGEN3_VERSION - eigen version # Copyright (c) 2006, 2007 Montel Laurent, # Copyright (c) 2008, 2009 Gael Guennebaud, # Copyright (c) 2009 Benoit Jacob # Redistribution and use is allowed according to the terms of the 2-clause BSD license. if(NOT Eigen3_FIND_VERSION) if(NOT Eigen3_FIND_VERSION_MAJOR) set(Eigen3_FIND_VERSION_MAJOR 2) endif(NOT Eigen3_FIND_VERSION_MAJOR) if(NOT Eigen3_FIND_VERSION_MINOR) set(Eigen3_FIND_VERSION_MINOR 91) endif(NOT Eigen3_FIND_VERSION_MINOR) if(NOT Eigen3_FIND_VERSION_PATCH) set(Eigen3_FIND_VERSION_PATCH 0) endif(NOT Eigen3_FIND_VERSION_PATCH) set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") endif(NOT Eigen3_FIND_VERSION) macro(_eigen3_check_version) file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) set(EIGEN3_VERSION_OK FALSE) else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) set(EIGEN3_VERSION_OK TRUE) endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) if(NOT EIGEN3_VERSION_OK) message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " "but at least version ${Eigen3_FIND_VERSION} is required") endif(NOT EIGEN3_VERSION_OK) endmacro(_eigen3_check_version) if (EIGEN3_INCLUDE_DIR) # in cache already _eigen3_check_version() set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) else (EIGEN3_INCLUDE_DIR) # specific additional paths for some OS if (WIN32) set(EIGEN_ADDITIONAL_SEARCH_PATHS ${EIGEN_ADDITIONAL_SEARCH_PATHS} "C:/Program Files/Eigen/include" "C:/Program Files (x86)/Eigen/include") endif(WIN32) find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library PATHS ${CMAKE_INSTALL_PREFIX}/include ${EIGEN_ADDITIONAL_SEARCH_PATHS} ${KDE4_INCLUDE_DIR} PATH_SUFFIXES eigen3 eigen ) if(EIGEN3_INCLUDE_DIR) _eigen3_check_version() endif(EIGEN3_INCLUDE_DIR) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) mark_as_advanced(EIGEN3_INCLUDE_DIR) endif(EIGEN3_INCLUDE_DIR) Slic3r-version_1.39.1/cmake/modules/FindPerlEmbed.cmake000066400000000000000000000065671324354444700227330ustar00rootroot00000000000000# Find the dependencies for linking with the Perl runtime library. # Check for the Perl & PerlLib modules include(LibFindMacros) libfind_package(PerlEmbed Perl) libfind_package(PerlEmbed PerlLibs) # Execute an Alien::Wx module to find the relevant information regarding # the wxWidgets used by the Perl interpreter. # Perl specific stuff set(PerlEmbed_TEMP_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/PerlEmbed_TEMP_INCLUDE.txt) execute_process( COMMAND ${PERL_EXECUTABLE} -MExtUtils::Embed -e " # Import Perl modules. use strict; use warnings; use Config; use Text::ParseWords; use ExtUtils::CppGuess; # Test for a Visual Studio compiler my \$cpp_guess = ExtUtils::CppGuess->new; my \$mswin = \$^O eq 'MSWin32'; my \$msvc = \$cpp_guess->is_msvc; # Query the available data from Alien::wxWidgets. my \$ccflags; my \$ldflags; { local *STDOUT; open STDOUT, '>', \\\$ccflags; ccflags; } { local *STDOUT; open STDOUT, '>', \\\$ldflags; ldopts; } \$ccflags = ' ' . \$ccflags; \$ldflags = ' ' . \$ldflags; my \$filename = '${PerlEmbed_TEMP_INCLUDE}'; open(my $fh, '>', \$filename) or die \"Could not open file '\$filename' \$!\"; # Convert a space separated lists to CMake semicolon separated lists, # escape the backslashes, # export the resulting list to a temp file. sub cmake_set_var { my (\$varname, \$content) = @_; # Remove line separators. \$content =~ s/\\r|\\n//g; # Escape the path separators. \$content =~ s/\\\\/\\\\\\\\\\\\\\\\/g; my @words = shellwords(\$content); print \$fh \"set(PerlEmbed_\$varname \\\"\" . join(';', @words) . \"\\\")\\n\"; } cmake_set_var('ARCHNAME', \$Config{archname}); cmake_set_var('CCFLAGS', \$ccflags); \$ldflags =~ s/ -L/ -LIBPATH:/g if \$msvc; cmake_set_var('LD', \$Config{ld}); cmake_set_var('LDFLAGS', \$ldflags); cmake_set_var('CCCDLFLAGS', \$Config{cccdlflags}); cmake_set_var('LDDLFLAGS', \$Config{lddlflags}); cmake_set_var('DLEXT', \$Config{dlext}); close \$fh; ") include(${PerlEmbed_TEMP_INCLUDE}) file(REMOVE ${PerlEmbed_TEMP_INCLUDE}) unset(PerlEmbed_TEMP_INCLUDE) if (PerlEmbed_DEBUG) # First show the configuration extracted by FindPerl & FindPerlLibs: message(STATUS " PERL_INCLUDE_PATH = ${PERL_INCLUDE_PATH}") message(STATUS " PERL_LIBRARY = ${PERL_LIBRARY}") message(STATUS " PERL_EXECUTABLE = ${PERL_EXECUTABLE}") message(STATUS " PERL_SITESEARCH = ${PERL_SITESEARCH}") message(STATUS " PERL_SITELIB = ${PERL_SITELIB}") message(STATUS " PERL_VENDORARCH = ${PERL_VENDORARCH}") message(STATUS " PERL_VENDORLIB = ${PERL_VENDORLIB}") message(STATUS " PERL_ARCHLIB = ${PERL_ARCHLIB}") message(STATUS " PERL_PRIVLIB = ${PERL_PRIVLIB}") message(STATUS " PERL_EXTRA_C_FLAGS = ${PERL_EXTRA_C_FLAGS}") # Second show the configuration extracted by this module (FindPerlEmbed): message(STATUS " PerlEmbed_ARCHNAME = ${PerlEmbed_ARCHNAME}") message(STATUS " PerlEmbed_CCFLAGS = ${PerlEmbed_CCFLAGS}") message(STATUS " PerlEmbed_CCCDLFLAGS = ${PerlEmbed_CCCDLFLAGS}") message(STATUS " LD = ${PerlEmbed_LD}") message(STATUS " PerlEmbed_LDFLAGS = ${PerlEmbed_LDFLAGS}") message(STATUS " PerlEmbed_LDDLFLAGS = ${PerlEmbed_LDDLFLAGS}") endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(PerlEmbed REQUIRED_VARS PerlEmbed_CCFLAGS PerlEmbed_LDFLAGS VERSION_VAR PERL_VERSION) Slic3r-version_1.39.1/cmake/modules/FindTBB.cmake000066400000000000000000000306511324354444700214720ustar00rootroot00000000000000# The MIT License (MIT) # # Copyright (c) 2015 Justus Calvin # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # FindTBB # ------- # # Find TBB include directories and libraries. # # Usage: # # find_package(TBB [major[.minor]] [EXACT] # [QUIET] [REQUIRED] # [[COMPONENTS] [components...]] # [OPTIONAL_COMPONENTS components...]) # # where the allowed components are tbbmalloc and tbb_preview. Users may modify # the behavior of this module with the following variables: # # * TBB_ROOT_DIR - The base directory the of TBB installation. # * TBB_INCLUDE_DIR - The directory that contains the TBB headers files. # * TBB_LIBRARY - The directory that contains the TBB library files. # * TBB__LIBRARY - The path of the TBB the corresponding TBB library. # These libraries, if specified, override the # corresponding library search results, where # may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug, # tbb_preview, or tbb_preview_debug. # * TBB_USE_DEBUG_BUILD - The debug version of tbb libraries, if present, will # be used instead of the release version. # * TBB_STATIC - Static linking of libraries with a _static suffix. # For example, on Windows a tbb_static.lib will be searched for # instead of tbb.lib. # # Users may modify the behavior of this module with the following environment # variables: # # * TBB_INSTALL_DIR # * TBBROOT # * LIBRARY_PATH # # This module will set the following variables: # # * TBB_FOUND - Set to false, or undefined, if we haven’t found, or # don’t want to use TBB. # * TBB__FOUND - If False, optional part of TBB sytem is # not available. # * TBB_VERSION - The full version string # * TBB_VERSION_MAJOR - The major version # * TBB_VERSION_MINOR - The minor version # * TBB_INTERFACE_VERSION - The interface version number defined in # tbb/tbb_stddef.h. # * TBB__LIBRARY_RELEASE - The path of the TBB release version of # , where may be tbb, tbb_debug, # tbbmalloc, tbbmalloc_debug, tbb_preview, or # tbb_preview_debug. # * TBB__LIBRARY_DEGUG - The path of the TBB release version of # , where may be tbb, tbb_debug, # tbbmalloc, tbbmalloc_debug, tbb_preview, or # tbb_preview_debug. # # The following varibles should be used to build and link with TBB: # # * TBB_INCLUDE_DIRS - The include directory for TBB. # * TBB_LIBRARIES - The libraries to link against to use TBB. # * TBB_LIBRARIES_RELEASE - The release libraries to link against to use TBB. # * TBB_LIBRARIES_DEBUG - The debug libraries to link against to use TBB. # * TBB_DEFINITIONS - Definitions to use when compiling code that uses # TBB. # * TBB_DEFINITIONS_RELEASE - Definitions to use when compiling release code that # uses TBB. # * TBB_DEFINITIONS_DEBUG - Definitions to use when compiling debug code that # uses TBB. # # This module will also create the "tbb" target that may be used when building # executables and libraries. include(FindPackageHandleStandardArgs) if(NOT TBB_FOUND) ################################## # Check the build type ################################## if(NOT DEFINED TBB_USE_DEBUG_BUILD) if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)") set(TBB_BUILD_TYPE DEBUG) else() set(TBB_BUILD_TYPE RELEASE) endif() elseif(TBB_USE_DEBUG_BUILD) set(TBB_BUILD_TYPE DEBUG) else() set(TBB_BUILD_TYPE RELEASE) endif() ################################## # Set the TBB search directories ################################## # Define search paths based on user input and environment variables set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT}) # Define the search directories based on the current platform if(CMAKE_SYSTEM_NAME STREQUAL "Windows") set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB" "C:/Program Files (x86)/Intel/TBB") # Set the target architecture if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(TBB_ARCHITECTURE "intel64") else() set(TBB_ARCHITECTURE "ia32") endif() # Set the TBB search library path search suffix based on the version of VC if(WINDOWS_STORE) set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui") elseif(MSVC14) set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc14") elseif(MSVC12) set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc12") elseif(MSVC11) set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11") elseif(MSVC10) set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10") endif() # Add the library path search suffix for the VC independent version of TBB list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt") elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") # OS X set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") # TODO: Check to see which C++ library is being used by the compiler. if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0) # The default C++ library on OS X 10.9 and later is libc++ set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib") else() set(TBB_LIB_PATH_SUFFIX "lib") endif() elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") # Linux set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb") # TODO: Check compiler version to see the suffix should be /gcc4.1 or # /gcc4.1. For now, assume that the compiler is more recent than # gcc 4.4.x or later. if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4") elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4") endif() endif() ################################## # Find the TBB include dir ################################## find_path(TBB_INCLUDE_DIRS tbb/tbb.h HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR} PATHS ${TBB_DEFAULT_SEARCH_DIR} PATH_SUFFIXES include) ################################## # Set version strings ################################## if(TBB_INCLUDE_DIRS) file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file) string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1" TBB_VERSION_MAJOR "${_tbb_version_file}") string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1" TBB_VERSION_MINOR "${_tbb_version_file}") string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1" TBB_INTERFACE_VERSION "${_tbb_version_file}") set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}") endif() ################################## # Find TBB components ################################## if(TBB_VERSION VERSION_LESS 4.3) set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb) else() set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb) endif() if(TBB_STATIC) set(TBB_STATIC_SUFFIX "_static") endif() # Find each component foreach(_comp ${TBB_SEARCH_COMPOMPONENTS}) if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};") # Search for the libraries find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX} HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX}) if(TBB_${_comp}_LIBRARY_DEBUG) list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}") endif() if(TBB_${_comp}_LIBRARY_RELEASE) list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}") endif() if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY) set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}") endif() if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}") set(TBB_${_comp}_FOUND TRUE) else() set(TBB_${_comp}_FOUND FALSE) endif() # Mark internal variables as advanced mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE) mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG) mark_as_advanced(TBB_${_comp}_LIBRARY) endif() endforeach() unset(TBB_STATIC_SUFFIX) ################################## # Set compile flags and libraries ################################## set(TBB_DEFINITIONS_RELEASE "") set(TBB_DEFINITIONS_DEBUG "-DTBB_USE_DEBUG=1") if(TBB_LIBRARIES_${TBB_BUILD_TYPE}) set(TBB_DEFINITIONS "${TBB_DEFINITIONS_${TBB_BUILD_TYPE}}") set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}") elseif(TBB_LIBRARIES_RELEASE) set(TBB_DEFINITIONS "${TBB_DEFINITIONS_RELEASE}") set(TBB_LIBRARIES "${TBB_LIBRARIES_RELEASE}") elseif(TBB_LIBRARIES_DEBUG) set(TBB_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}") set(TBB_LIBRARIES "${TBB_LIBRARIES_DEBUG}") endif() find_package_handle_standard_args(TBB REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES HANDLE_COMPONENTS VERSION_VAR TBB_VERSION) ################################## # Create targets ################################## if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND) add_library(tbb SHARED IMPORTED) set_target_properties(tbb PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS} IMPORTED_LOCATION ${TBB_LIBRARIES}) if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG) set_target_properties(tbb PROPERTIES INTERFACE_COMPILE_DEFINITIONS "$<$,$>:TBB_USE_DEBUG=1>" IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG} IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_DEBUG} IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE} IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE} ) elseif(TBB_LIBRARIES_RELEASE) set_target_properties(tbb PROPERTIES IMPORTED_LOCATION ${TBB_LIBRARIES_RELEASE}) else() set_target_properties(tbb PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}" IMPORTED_LOCATION ${TBB_LIBRARIES_DEBUG} ) endif() endif() mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES) unset(TBB_ARCHITECTURE) unset(TBB_BUILD_TYPE) unset(TBB_LIB_PATH_SUFFIX) unset(TBB_DEFAULT_SEARCH_DIR) if(TBB_DEBUG) message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}") message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}") message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}") message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}") message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}") message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}") message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}") endif() endif() Slic3r-version_1.39.1/cmake/modules/LibFindMacros.cmake000066400000000000000000000247161324354444700227430ustar00rootroot00000000000000# Version 2.2 # Public Domain, originally written by Lasse Kärkkäinen # Maintained at https://github.com/Tronic/cmake-modules # Please send your improvements as pull requests on Github. # Find another package and make it a dependency of the current package. # This also automatically forwards the "REQUIRED" argument. # Usage: libfind_package( [extra args to find_package]) macro (libfind_package PREFIX PKG) set(${PREFIX}_args ${PKG} ${ARGN}) if (${PREFIX}_FIND_REQUIRED) set(${PREFIX}_args ${${PREFIX}_args} REQUIRED) endif() find_package(${${PREFIX}_args}) set(${PREFIX}_DEPENDENCIES ${${PREFIX}_DEPENDENCIES};${PKG}) unset(${PREFIX}_args) endmacro() # A simple wrapper to make pkg-config searches a bit easier. # Works the same as CMake's internal pkg_check_modules but is always quiet. macro (libfind_pkg_check_modules) find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) pkg_check_modules(${ARGN} QUIET) endif() endmacro() # Avoid useless copy&pasta by doing what most simple libraries do anyway: # pkg-config, find headers, find library. # Usage: libfind_pkg_detect( FIND_PATH [other args] FIND_LIBRARY [other args]) # E.g. libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h PATH_SUFFIXES SDL2 FIND_LIBRARY SDL2) function (libfind_pkg_detect PREFIX) # Parse arguments set(argname pkgargs) foreach (i ${ARGN}) if ("${i}" STREQUAL "FIND_PATH") set(argname pathargs) elseif ("${i}" STREQUAL "FIND_LIBRARY") set(argname libraryargs) else() set(${argname} ${${argname}} ${i}) endif() endforeach() if (NOT pkgargs) message(FATAL_ERROR "libfind_pkg_detect requires at least a pkg_config package name to be passed.") endif() # Find library libfind_pkg_check_modules(${PREFIX}_PKGCONF ${pkgargs}) if (pathargs) find_path(${PREFIX}_INCLUDE_DIR NAMES ${pathargs} HINTS ${${PREFIX}_PKGCONF_INCLUDE_DIRS}) endif() if (libraryargs) find_library(${PREFIX}_LIBRARY NAMES ${libraryargs} HINTS ${${PREFIX}_PKGCONF_LIBRARY_DIRS}) endif() endfunction() # Extracts a version #define from a version.h file, output stored to _VERSION. # Usage: libfind_version_header(Foobar foobar/version.h FOOBAR_VERSION_STR) # Fourth argument "QUIET" may be used for silently testing different define names. # This function does nothing if the version variable is already defined. function (libfind_version_header PREFIX VERSION_H DEFINE_NAME) # Skip processing if we already have a version or if the include dir was not found if (${PREFIX}_VERSION OR NOT ${PREFIX}_INCLUDE_DIR) return() endif() set(quiet ${${PREFIX}_FIND_QUIETLY}) # Process optional arguments foreach(arg ${ARGN}) if (arg STREQUAL "QUIET") set(quiet TRUE) else() message(AUTHOR_WARNING "Unknown argument ${arg} to libfind_version_header ignored.") endif() endforeach() # Read the header and parse for version number set(filename "${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") if (NOT EXISTS ${filename}) if (NOT quiet) message(AUTHOR_WARNING "Unable to find ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") endif() return() endif() file(READ "${filename}" header) string(REGEX REPLACE ".*#[ \t]*define[ \t]*${DEFINE_NAME}[ \t]*\"([^\n]*)\".*" "\\1" match "${header}") # No regex match? if (match STREQUAL header) if (NOT quiet) message(AUTHOR_WARNING "Unable to find \#define ${DEFINE_NAME} \"\" from ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") endif() return() endif() # Export the version string set(${PREFIX}_VERSION "${match}" PARENT_SCOPE) endfunction() # Do the final processing once the paths have been detected. # If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain # all the variables, each of which contain one include directory. # Ditto for ${PREFIX}_PROCESS_LIBS and library files. # Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. # Also handles errors in case library detection was required, etc. function (libfind_process PREFIX) # Skip processing if already processed during this configuration run if (${PREFIX}_FOUND) return() endif() set(found TRUE) # Start with the assumption that the package was found # Did we find any files? Did we miss includes? These are for formatting better error messages. set(some_files FALSE) set(missing_headers FALSE) # Shorthands for some variables that we need often set(quiet ${${PREFIX}_FIND_QUIETLY}) set(required ${${PREFIX}_FIND_REQUIRED}) set(exactver ${${PREFIX}_FIND_VERSION_EXACT}) set(findver "${${PREFIX}_FIND_VERSION}") set(version "${${PREFIX}_VERSION}") # Lists of config option names (all, includes, libs) unset(configopts) set(includeopts ${${PREFIX}_PROCESS_INCLUDES}) set(libraryopts ${${PREFIX}_PROCESS_LIBS}) # Process deps to add to foreach (i ${PREFIX} ${${PREFIX}_DEPENDENCIES}) if (DEFINED ${i}_INCLUDE_OPTS OR DEFINED ${i}_LIBRARY_OPTS) # The package seems to export option lists that we can use, woohoo! list(APPEND includeopts ${${i}_INCLUDE_OPTS}) list(APPEND libraryopts ${${i}_LIBRARY_OPTS}) else() # If plural forms don't exist or they equal singular forms if ((NOT DEFINED ${i}_INCLUDE_DIRS AND NOT DEFINED ${i}_LIBRARIES) OR ({i}_INCLUDE_DIR STREQUAL ${i}_INCLUDE_DIRS AND ${i}_LIBRARY STREQUAL ${i}_LIBRARIES)) # Singular forms can be used if (DEFINED ${i}_INCLUDE_DIR) list(APPEND includeopts ${i}_INCLUDE_DIR) endif() if (DEFINED ${i}_LIBRARY) list(APPEND libraryopts ${i}_LIBRARY) endif() else() # Oh no, we don't know the option names message(FATAL_ERROR "We couldn't determine config variable names for ${i} includes and libs. Aieeh!") endif() endif() endforeach() if (includeopts) list(REMOVE_DUPLICATES includeopts) endif() if (libraryopts) list(REMOVE_DUPLICATES libraryopts) endif() string(REGEX REPLACE ".*[ ;]([^ ;]*(_INCLUDE_DIRS|_LIBRARIES))" "\\1" tmp "${includeopts} ${libraryopts}") if (NOT tmp STREQUAL "${includeopts} ${libraryopts}") message(AUTHOR_WARNING "Plural form ${tmp} found in config options of ${PREFIX}. This works as before but is now deprecated. Please only use singular forms INCLUDE_DIR and LIBRARY, and update your find scripts for LibFindMacros > 2.0 automatic dependency system (most often you can simply remove the PROCESS variables entirely).") endif() # Include/library names separated by spaces (notice: not CMake lists) unset(includes) unset(libs) # Process all includes and set found false if any are missing foreach (i ${includeopts}) list(APPEND configopts ${i}) if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") list(APPEND includes "${${i}}") else() set(found FALSE) set(missing_headers TRUE) endif() endforeach() # Process all libraries and set found false if any are missing foreach (i ${libraryopts}) list(APPEND configopts ${i}) if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") list(APPEND libs "${${i}}") else() set (found FALSE) endif() endforeach() # Version checks if (found AND findver) if (NOT version) message(WARNING "The find module for ${PREFIX} does not provide version information, so we'll just assume that it is OK. Please fix the module or remove package version requirements to get rid of this warning.") elseif (version VERSION_LESS findver OR (exactver AND NOT version VERSION_EQUAL findver)) set(found FALSE) set(version_unsuitable TRUE) endif() endif() # If all-OK, hide all config options, export variables, print status and exit if (found) foreach (i ${configopts}) mark_as_advanced(${i}) endforeach() if (NOT quiet) message(STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") if (LIBFIND_DEBUG) message(STATUS " ${PREFIX}_DEPENDENCIES=${${PREFIX}_DEPENDENCIES}") message(STATUS " ${PREFIX}_INCLUDE_OPTS=${includeopts}") message(STATUS " ${PREFIX}_INCLUDE_DIRS=${includes}") message(STATUS " ${PREFIX}_LIBRARY_OPTS=${libraryopts}") message(STATUS " ${PREFIX}_LIBRARIES=${libs}") endif() set (${PREFIX}_INCLUDE_OPTS ${includeopts} PARENT_SCOPE) set (${PREFIX}_LIBRARY_OPTS ${libraryopts} PARENT_SCOPE) set (${PREFIX}_INCLUDE_DIRS ${includes} PARENT_SCOPE) set (${PREFIX}_LIBRARIES ${libs} PARENT_SCOPE) set (${PREFIX}_FOUND TRUE PARENT_SCOPE) endif() return() endif() # Format messages for debug info and the type of error set(vars "Relevant CMake configuration variables:\n") foreach (i ${configopts}) mark_as_advanced(CLEAR ${i}) set(val ${${i}}) if ("${val}" STREQUAL "${i}-NOTFOUND") set (val "") elseif (val AND NOT EXISTS ${val}) set (val "${val} (does not exist)") else() set(some_files TRUE) endif() set(vars "${vars} ${i}=${val}\n") endforeach() set(vars "${vars}You may use CMake GUI, cmake -D or ccmake to modify the values. Delete CMakeCache.txt to discard all values and force full re-detection if necessary.\n") if (version_unsuitable) set(msg "${PREFIX} ${${PREFIX}_VERSION} was found but") if (exactver) set(msg "${msg} only version ${findver} is acceptable.") else() set(msg "${msg} version ${findver} is the minimum requirement.") endif() else() if (missing_headers) set(msg "We could not find development headers for ${PREFIX}. Do you have the necessary dev package installed?") elseif (some_files) set(msg "We only found some files of ${PREFIX}, not all of them. Perhaps your installation is incomplete or maybe we just didn't look in the right place?") if(findver) set(msg "${msg} This could also be caused by incompatible version (if it helps, at least ${PREFIX} ${findver} should work).") endif() else() set(msg "We were unable to find package ${PREFIX}.") endif() endif() # Fatal error out if REQUIRED if (required) set(msg "REQUIRED PACKAGE NOT FOUND\n${msg} This package is REQUIRED and you need to install it or adjust CMake configuration in order to continue building ${CMAKE_PROJECT_NAME}.") message(FATAL_ERROR "${msg}\n${vars}") endif() # Otherwise just print a nasty warning if (NOT quiet) message(WARNING "WARNING: MISSING PACKAGE\n${msg} This package is NOT REQUIRED and you may ignore this warning but by doing so you may miss some functionality of ${CMAKE_PROJECT_NAME}. \n${vars}") endif() endfunction() Slic3r-version_1.39.1/doc/000077500000000000000000000000001324354444700152705ustar00rootroot00000000000000Slic3r-version_1.39.1/doc/How_to_build_Slic3r.txt000066400000000000000000000417741324354444700217030ustar00rootroot00000000000000How to build Slic3r on Mac OS X 10.9 Maveric --------------------------------------------- Vojtech Bubnik, 2017-12-12 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 Install cmake brew install cmake 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.26.1 The --switch parameter switches the active perl to the currently compiled one. Available perl versions could be listed by calling perlbrew available Switch to the newly compiled perl perl5/perlbrew/bin/perlbrew switch perl-5.26.1 Install cpanm perlbrew install-cpanm Initialize CPAN, install PAR and PAR::Packer modules execute cpan command, from the cpan prompt, run install App::cpanminus install ExtUtils::CppGuess install ExtUtils::Typemaps install ExtUtils::Typemaps::Basic install PAR install PAR::Packer install Module::Build install Module::Pluggable install Module::Runtime install Moo install Test::Pod install Test::Pod::Coverage 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 ------------------------------------------------------------------------------ Building the Perl Alien-Wx containing a wxWidgets library: We use wxWidgets-3.0.3. patch wxWidgets-3.0.3//src/osx/cocoa/textctrl.mm , see https://github.com/prusa3d/Slic3r/issues/600 perl -I. Build.PL --wxWidgets-extraflags="--with-osx_cocoa --with-macosx-version-min=10.9 --with-macosx-sdk=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk --with-libjpeg=builtin --with-libpng=builtin --with-regex=builtin --with-libtiff=builtin --with-zlib=builtin --with-expat=builtin --with-opengl" perl -I. Build perl -I. Build test perl -I. Build Building the Perl Wx package: cpan install Wx Building the Perl OpenGL package: OpenGL needs to be patched to support MSAA, see the Windows patch. 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. ---------------------------------------------------------------------- Building boost. One may save compilation time by compiling just what Slic3r needs. ./bootstrap.sh --with-libraries=system,filesystem,thread,log,locale,regex The -fPIC flag is required on Linux to make the static libraries rellocatable, so they could be embedded into a shared library. It is important to disable boost.locale.icu=off when compiling the static boost library. ./bjam -a link=static variant=release threading=multi boost.locale.icu=off --with-locale cxxflags=-fPIC cflags=-fPIC To install on Linux to /usr/local/..., run the line above with the additional install keyword and with sudo. Slic3r-version_1.39.1/lib/000077500000000000000000000000001324354444700152715ustar00rootroot00000000000000Slic3r-version_1.39.1/lib/Slic3r.pm000066400000000000000000000240251324354444700167710ustar00rootroot00000000000000# 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; use Config; require v5.10; our $VERSION = VERSION(); our $BUILD = BUILD(); our $FORK_NAME = FORK_NAME(); our $debug = 0; sub debugf { printf @_ if $debug; } our $loglevel = 0; # load threads before Moo as required by it BEGIN { # Test, whether the perl was compiled with ithreads support and ithreads actually work. use Config; use Moo; my $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1"; die "Slic3r Prusa Edition requires working Perl threads.\n" if ! $have_threads; die "threads.pm >= 1.96 is required, please update\n" if $threads::VERSION < 1.96; die "Perl threading is broken with this Moo version: " . $Moo::VERSION . "\n" if $Moo::VERSION == 1.003000; $debug = 1 if (defined($ENV{'SLIC3R_DEBUGOUT'}) && $ENV{'SLIC3R_DEBUGOUT'} == 1); print "Debugging output enabled\n" if $debug; } warn "Running Slic3r under Perl 5.16 is neither supported nor recommended\n" if $^V == v5.16; use FindBin; # Let the XS module know where the GUI resources reside. set_resources_dir(decode_path($FindBin::Bin) . (($^O eq 'darwin') ? '/../Resources' : '/resources')); set_var_dir(resources_dir() . "/icons"); 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::Reader; 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::Object; use Slic3r::Print::Simple; use Slic3r::Surface; our $build = eval "use Slic3r::Build; 1"; use Thread::Semaphore; # Scaling between the float and integer coordinates. # Floats are in mm. use constant SCALING_FACTOR => 0.000001; # Keep track of threads we created. Perl worker threads shall not create further threads. my @threads = (); my $pause_sema = Thread::Semaphore->new; my $paused = 0; # Set the logging level at the Slic3r XS module. $Slic3r::loglevel = (defined($ENV{'SLIC3R_LOGLEVEL'}) && $ENV{'SLIC3R_LOGLEVEL'} =~ /^[1-9]/) ? $ENV{'SLIC3R_LOGLEVEL'} : 0; set_logging_level($Slic3r::loglevel); # Let the palceholder parser evaluate one expression to initialize its local static macro_processor # class instance in a thread safe manner. Slic3r::GCode::PlaceholderParser->new->evaluate_boolean_expression('1==1'); sub spawn_thread { my ($cb) = @_; @_ = (); my $thread = threads->create(sub { Slic3r::debugf "Starting thread %d...\n", threads->tid; local $SIG{'KILL'} = sub { Slic3r::debugf "Exiting thread %d...\n", threads->tid; Slic3r::thread_cleanup(); threads->exit(); }; local $SIG{'STOP'} = sub { $pause_sema->down; $pause_sema->up; }; $cb->(); }); push @threads, $thread->tid; return $thread; } # 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 { # 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::ExtrusionLoop::DESTROY = sub {}; *Slic3r::ExtrusionMultiPath::DESTROY = sub {}; *Slic3r::ExtrusionPath::DESTROY = sub {}; *Slic3r::ExtrusionPath::Collection::DESTROY = sub {}; *Slic3r::ExtrusionSimulator::DESTROY = sub {}; *Slic3r::Flow::DESTROY = sub {}; *Slic3r::GCode::DESTROY = sub {}; *Slic3r::GCode::PlaceholderParser::DESTROY = sub {}; *Slic3r::GCode::Sender::DESTROY = sub {}; *Slic3r::Geometry::BoundingBox::DESTROY = sub {}; *Slic3r::Geometry::BoundingBoxf::DESTROY = sub {}; *Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {}; *Slic3r::Layer::PerimeterGenerator::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::Surface::DESTROY = sub {}; *Slic3r::Surface::Collection::DESTROY = sub {}; *Slic3r::Print::SupportMaterial2::DESTROY = sub {}; *Slic3r::TriangleMesh::DESTROY = sub {}; *Slic3r::GUI::AppConfig::DESTROY = sub {}; *Slic3r::GUI::PresetBundle::DESTROY = sub {}; return undef; # this prevents a "Scalars leaked" warning } sub _get_running_threads { return grep defined($_), map threads->object($_), @threads; } sub kill_all_threads { # Send SIGKILL to all the running threads to let them die. foreach my $thread (_get_running_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) { 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; } @threads = (); } sub pause_all_threads { return if $paused; $paused = 1; $pause_sema->down; $_->kill('STOP') for _get_running_threads; } sub resume_all_threads { return unless $paused; $paused = 0; $pause_sema->up; } # 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); } sub tags { my ($format) = @_; $format //= ''; my %tags; # End of line $tags{eol} = ($format eq 'html') ? '
' : "\n"; # Heading $tags{h2start} = ($format eq 'html') ? '' : ''; $tags{h2end} = ($format eq 'html') ? '' : ''; # Bold font $tags{bstart} = ($format eq 'html') ? '' : ''; $tags{bend} = ($format eq 'html') ? '' : ''; # Verbatim $tags{vstart} = ($format eq 'html') ? '
'  : '';
    $tags{vend}    = ($format eq 'html') ? '
' : ''; return %tags; } sub slic3r_info { my (%params) = @_; my %tag = Slic3r::tags($params{format}); my $out = ''; $out .= "$tag{bstart}$Slic3r::FORK_NAME$tag{bend}$tag{eol}"; $out .= "$tag{bstart}Version: $tag{bend}$Slic3r::VERSION$tag{eol}"; $out .= "$tag{bstart}Build: $tag{bend}$Slic3r::BUILD$tag{eol}"; return $out; } sub copyright_info { my (%params) = @_; my %tag = Slic3r::tags($params{format}); my $out = 'Copyright © 2016 Vojtech Bubnik, Prusa Research.
' . 'Copyright © 2011-2016 Alessandro Ranellucci.
' . 'Slic3r is licensed under the ' . 'GNU Affero General Public License, version 3.' . '


' . 'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake 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. '; return $out; } sub system_info { my (%params) = @_; my %tag = Slic3r::tags($params{format}); my $out = ''; $out .= "$tag{bstart}Operating System: $tag{bend}$Config{osname}$tag{eol}"; $out .= "$tag{bstart}System Architecture: $tag{bend}$Config{archname}$tag{eol}"; if ($^O eq 'MSWin32') { $out .= "$tag{bstart}Windows Version: $tag{bend}" . `ver` . $tag{eol}; } else { # Hopefully some kind of unix / linux. $out .= "$tag{bstart}System Version: $tag{bend}" . `uname -a` . $tag{eol}; } $out .= $tag{vstart} . Config::myconfig . $tag{vend}; $out .= " $tag{bstart}\@INC:$tag{bend}$tag{eol}$tag{vstart}"; foreach my $i (@INC) { $out .= " $i\n"; } $out .= "$tag{vend}"; return $out; } # this package declaration prevents an ugly fatal warning to be emitted when # spawning a new thread package GLUquadricObjPtr; 1; Slic3r-version_1.39.1/lib/Slic3r/000077500000000000000000000000001324354444700164305ustar00rootroot00000000000000Slic3r-version_1.39.1/lib/Slic3r/Config.pm000066400000000000000000000046761324354444700202100ustar00rootroot00000000000000# 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(); # Generate accessors. { no strict 'refs'; for my $opt_key (keys %$Options) { *{$opt_key} = sub { #print "Slic3r::Config::accessor $opt_key\n"; $_[0]->get($opt_key) }; } } # From command line parameters, used by slic3r.pl 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; } 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 } 1; Slic3r-version_1.39.1/lib/Slic3r/ExPolygon.pm000066400000000000000000000020471324354444700207150ustar00rootroot00000000000000package Slic3r::ExPolygon; use strict; use warnings; # an ExPolygon is a polygon with holes use List::Util qw(first); 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 noncollapsing_offset_ex { my $self = shift; my ($distance, @params) = @_; return $self->offset_ex($distance + 1, @params); } sub bounding_box { my $self = shift; return $self->contour->bounding_box; } package Slic3r::ExPolygon::Collection; sub size { my $self = shift; return [ Slic3r::Geometry::size_2D([ map @$_, map @$_, @$self ]) ]; } 1; Slic3r-version_1.39.1/lib/Slic3r/ExtrusionLoop.pm000066400000000000000000000003501324354444700216160ustar00rootroot00000000000000package 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-version_1.39.1/lib/Slic3r/ExtrusionPath.pm000066400000000000000000000006561324354444700216120ustar00rootroot00000000000000package 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-version_1.39.1/lib/Slic3r/Flow.pm000066400000000000000000000005211324354444700176730ustar00rootroot00000000000000package 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-version_1.39.1/lib/Slic3r/GCode/000077500000000000000000000000001324354444700174115ustar00rootroot00000000000000Slic3r-version_1.39.1/lib/Slic3r/GCode/Reader.pm000066400000000000000000000054431324354444700211570ustar00rootroot00000000000000# Helper module to parse and interpret a G-code file, # invoking a callback for each move extracted from the G-code. # Currently used by the automatic tests only. 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-version_1.39.1/lib/Slic3r/GUI.pm000066400000000000000000000250201324354444700174110ustar00rootroot00000000000000package Slic3r::GUI; use strict; use warnings; use utf8; use File::Basename qw(basename); use FindBin; use List::Util qw(first); 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::Preferences; use Slic3r::GUI::ProgressStatusBar; use Slic3r::GUI::OptionsGroup; use Slic3r::GUI::OptionsGroup::Field; use Slic3r::GUI::SystemInfo; use Slic3r::GUI::Tab; our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1"; our $have_LWP = eval "use LWP::UserAgent; 1"; use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow :filedialog :font); use Wx::Event qw(EVT_IDLE EVT_COMMAND EVT_MENU); use base 'Wx::App'; use constant FILE_WILDCARDS => { known => 'Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA', stl => 'STL files (*.stl)|*.stl;*.STL', obj => 'OBJ files (*.obj)|*.obj;*.OBJ', amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML', prusa => 'Prusa Control files (*.prusa)|*.prusa;*.PRUSA', 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 prusa)}; # Datadir provided on the command line. our $datadir; # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. our $no_plater; our @cb; 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); sub OnInit { my ($self) = @_; $self->SetAppName('Slic3rPE'); $self->SetAppDisplayName('Slic3r Prusa Edition'); Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION; # Set the Slic3r data directory at the Slic3r XS module. # Unix: ~/.Slic3r # Windows: "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" # Mac: "~/Library/Application Support/Slic3r" Slic3r::set_data_dir($datadir || Wx::StandardPaths::Get->GetUserDataDir); Slic3r::GUI::set_wxapp($self); $self->{notifier} = Slic3r::GUI::Notifier->new; $self->{app_config} = Slic3r::GUI::AppConfig->new; $self->{preset_bundle} = Slic3r::GUI::PresetBundle->new; # just checking for existence of Slic3r::data_dir is not enough: it may be an empty directory # supplied as argument to --datadir; in that case we should still run the wizard eval { $self->{preset_bundle}->setup_directories() }; if ($@) { warn $@ . "\n"; fatal_error(undef, $@); } my $run_wizard = ! $self->{app_config}->exists; # load settings $self->{app_config}->load if ! $run_wizard; $self->{app_config}->set('version', $Slic3r::VERSION); $self->{app_config}->save; # Suppress the '- default -' presets. $self->{preset_bundle}->set_default_suppressed($self->{app_config}->get('no_defaults') ? 1 : 0); eval { $self->{preset_bundle}->load_presets }; if ($@) { warn $@ . "\n"; show_error(undef, $@); } eval { $self->{preset_bundle}->load_selections($self->{app_config}) }; $run_wizard = 1 if $self->{preset_bundle}->has_defauls_only; # application frame Wx::Image::FindHandlerType(wxBITMAP_TYPE_PNG) || Wx::Image::AddHandler(Wx::PNGHandler->new); $self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new( # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. no_controller => $self->{app_config}->get('no_controller'), no_plater => $no_plater, ); $self->SetTopWindow($frame); EVT_IDLE($frame, sub { while (my $cb = shift @cb) { $cb->(); } $self->{app_config}->save if $self->{app_config}->dirty; }); if ($run_wizard) { # On OSX the UI was not initialized correctly if the wizard was called # before the UI was up and running. $self->CallAfter(sub { # Run the config wizard, don't offer the "reset user profile" checkbox. $self->{mainframe}->config_wizard(1); }); } return 1; } sub about { my ($self) = @_; my $about = Slic3r::GUI::AboutDialog->new(undef); $about->ShowModal; $about->Destroy; } sub system_info { my ($self) = @_; my $slic3r_info = Slic3r::slic3r_info(format => 'html'); my $copyright_info = Slic3r::copyright_info(format => 'html'); my $system_info = Slic3r::system_info(format => 'html'); my $opengl_info; my $opengl_info_txt = ''; if (defined($self->{mainframe}) && defined($self->{mainframe}->{plater}) && defined($self->{mainframe}->{plater}->{canvas3D})) { $opengl_info = $self->{mainframe}->{plater}->{canvas3D}->opengl_info(format => 'html'); $opengl_info_txt = $self->{mainframe}->{plater}->{canvas3D}->opengl_info; } my $about = Slic3r::GUI::SystemInfo->new( parent => undef, slic3r_info => $slic3r_info, # copyright_info => $copyright_info, system_info => $system_info, opengl_info => $opengl_info, text_info => Slic3r::slic3r_info . Slic3r::system_info . $opengl_info_txt, ); $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); } # Called after the Preferences dialog is closed and the program settings are saved. # Update the UI based on the current preferences. sub update_ui_from_settings { my ($self) = @_; $self->{mainframe}->update_ui_from_settings; } sub open_model { my ($self, $window) = @_; my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF/PRUSA):', $self->{app_config}->get_last_dir, "", MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; } my @input_files = $dialog->GetPaths; $dialog->Destroy; return @input_files; } sub CallAfter { my ($self, $cb) = @_; push @cb, $cb; } 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) = @_; $self->{app_config}->set("${name}_pos", join ',', $window->GetScreenPositionXY); $self->{app_config}->set("${name}_size", join ',', $window->GetSizeWH); $self->{app_config}->set("${name}_maximized", $window->IsMaximized); $self->{app_config}->save; } sub restore_window_pos { my ($self, $window, $name) = @_; if ($self->{app_config}->has("${name}_pos")) { my $size = [ split ',', $self->{app_config}->get("${name}_size"), 2 ]; $window->SetSize($size); my $display = Wx::Display->new->GetClientArea(); my $pos = [ split ',', $self->{app_config}->get("${name}_pos"), 2 ]; if (($pos->[0] + $size->[0]/2) < $display->GetRight && ($pos->[1] + $size->[1]/2) < $display->GetBottom) { $window->Move($pos); } $window->Maximize(1) if $self->{app_config}->get("${name}_maximized"); } } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/000077500000000000000000000000001324354444700170545ustar00rootroot00000000000000Slic3r-version_1.39.1/lib/Slic3r/GUI/2DBed.pm000066400000000000000000000166471324354444700203100ustar00rootroot00000000000000# Bed shape dialog package Slic3r::GUI::2DBed; use strict; use warnings; use List::Util qw(min max); use Slic3r::Geometry qw(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); __PACKAGE__->mk_accessors(qw(bed_shape interactive pos _scale_factor _shift on_move _painted)); sub new { my ($class, $parent, $bed_shape) = @_; 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 $color = Wx::SystemSettings::GetSystemColour(wxSYS_COLOUR_3DLIGHT); $dc->SetPen(Wx::Pen->new($color, 1, wxSOLID)); $dc->SetBrush(Wx::Brush->new($color, wxSOLID)); 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(255,255,255), 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(230,230,230), 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-version_1.39.1/lib/Slic3r/GUI/3DScene.pm000066400000000000000000002314201324354444700206400ustar00rootroot00000000000000# Implements pure perl packages # # Slic3r::GUI::3DScene::Base; # Slic3r::GUI::3DScene; # # Slic3r::GUI::Plater::3D derives from Slic3r::GUI::3DScene, # Slic3r::GUI::Plater::3DPreview, Slic3r::GUI::Plater::3DToolpaths, # Slic3r::GUI::Plater::ObjectCutDialog and Slic3r::GUI::Plater::ObjectPartsPanel # own $self->{canvas} of the Slic3r::GUI::3DScene type. # # Therefore the 3DScene supports renderng of STLs, extrusions and cutting planes, # and camera manipulation. package Slic3r::GUI::3DScene::Base; use strict; use warnings; use Wx qw(wxTheApp :timer :bitmap :icon :dialog); use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS EVT_CHAR EVT_TIMER); # 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 tan); use List::Util qw(reduce min max first); use Slic3r::Geometry qw(X Y normalize scale unscale scaled_epsilon); use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl JT_ROUND); use Wx::GLCanvas qw(:all); use Slic3r::Geometry qw(PI); # _dirty: boolean flag indicating, that the screen has to be redrawn on EVT_IDLE. # volumes: reference to vector of Slic3r::GUI::3DScene::Volume. # _camera_type: 'perspective' or 'ortho' __PACKAGE__->mk_accessors( qw(_quat _dirty init enable_picking enable_moving use_plain_shader on_viewport_changed on_hover on_select on_double_click on_right_click on_move on_model_update volumes _sphi _stheta cutting_plane_z cut_lines_vertices bed_shape bed_triangles bed_grid_lines bed_polygon background origin _mouse_pos _hover_volume_idx _drag_volume_idx _drag_start_pos _drag_volume_center_offset _drag_start_xy _dragged _layer_height_edited _camera_type _camera_target _camera_distance _zoom ) ); use constant TRACKBALLSIZE => 0.8; use constant TURNTABLE_MODE => 1; use constant GROUND_Z => -0.02; # For mesh selection: Not selected - bright yellow. use constant DEFAULT_COLOR => [1,1,0]; # For mesh selection: Selected - bright green. use constant SELECTED_COLOR => [0,1,0,1]; # For mesh selection: Mouse hovers over the object, but object not selected yet - dark green. use constant HOVER_COLOR => [0.4,0.9,0,1]; # phi / theta angles to orient the camera. use constant VIEW_DEFAULT => [45.0,45.0]; use constant VIEW_LEFT => [90.0,90.0]; use constant VIEW_RIGHT => [-90.0,90.0]; use constant VIEW_TOP => [0.0,0.0]; use constant VIEW_BOTTOM => [0.0,180.0]; use constant VIEW_FRONT => [0.0,90.0]; use constant VIEW_REAR => [180.0,90.0]; use constant MANIPULATION_IDLE => 0; use constant MANIPULATION_DRAGGING => 1; use constant MANIPULATION_LAYER_HEIGHT => 2; use constant GIMBALL_LOCK_THETA_MAX => 170; use constant VARIABLE_LAYER_THICKNESS_BAR_WIDTH => 70; use constant VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT => 22; # make OpenGL::Array thread-safe { no warnings 'redefine'; *OpenGL::Array::CLONE_SKIP = sub { 1 }; } sub new { my ($class, $parent) = @_; # We can only enable multi sample anti aliasing wih 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 = ! wxTheApp->{app_config}->get('use_legacy_opengl') && 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); $self->use_plain_shader(0); # Collection of GLVolume objects $self->volumes(Slic3r::GUI::_3DScene::GLVolume::Collection->new); # 3D point in model space $self->_camera_type('ortho'); # $self->_camera_type('perspective'); $self->_camera_target(Slic3r::Pointf3->new(0,0,0)); $self->_camera_distance(0.); $self->layer_editing_enabled(0); $self->{layer_height_edit_band_width} = 2.; $self->{layer_height_edit_strength} = 0.005; $self->{layer_height_edit_last_object_id} = -1; $self->{layer_height_edit_last_z} = 0.; $self->{layer_height_edit_last_action} = 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, \&mouse_wheel_event); EVT_MOUSE_EVENTS($self, \&mouse_event); # EVT_KEY_DOWN($self, sub { EVT_CHAR($self, sub { my ($s, $event) = @_; if ($event->HasModifiers) { $event->Skip; } else { my $key = $event->GetKeyCode; if ($key == ord('0')) { $self->select_view('iso'); } elsif ($key == ord('1')) { $self->select_view('top'); } elsif ($key == ord('2')) { $self->select_view('bottom'); } elsif ($key == ord('3')) { $self->select_view('front'); } elsif ($key == ord('4')) { $self->select_view('rear'); } elsif ($key == ord('5')) { $self->select_view('left'); } elsif ($key == ord('6')) { $self->select_view('right'); } else { $event->Skip; } } }); $self->{layer_height_edit_timer_id} = &Wx::NewId(); $self->{layer_height_edit_timer} = Wx::Timer->new($self, $self->{layer_height_edit_timer_id}); EVT_TIMER($self, $self->{layer_height_edit_timer_id}, sub { my ($self, $event) = @_; return if $self->_layer_height_edited != 1; return if $self->{layer_height_edit_last_object_id} == -1; $self->_variable_layer_thickness_action(undef); }); return $self; } sub Destroy { my ($self) = @_; $self->{layer_height_edit_timer}->Stop; $self->DestroyGL; return $self->SUPER::Destroy; } sub layer_editing_enabled { my ($self, $value) = @_; if (@_ == 2) { $self->{layer_editing_enabled} = $value; if ($value) { if (! $self->{layer_editing_initialized}) { # Enabling the layer editing for the first time. This triggers compilation of the necessary OpenGL shaders. # If compilation fails, a message box is shown with the error codes. $self->SetCurrent($self->GetContext); my $shader = new Slic3r::GUI::_3DScene::GLShader; my $error_message; if (! $shader->load($self->_fragment_shader_variable_layer_height, $self->_vertex_shader_variable_layer_height)) { # Compilation or linking of the shaders failed. $error_message = "Cannot compile an OpenGL Shader, therefore the Variable Layer Editing will be disabled.\n\n" . $shader->last_error; $shader = undef; } else { $self->{layer_height_edit_shader} = $shader; ($self->{layer_preview_z_texture_id}) = glGenTextures_p(1); glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); glBindTexture(GL_TEXTURE_2D, 0); } if (defined($error_message)) { # Don't enable the layer editing tool. $self->{layer_editing_enabled} = 0; # 2 means failed $self->{layer_editing_initialized} = 2; # Show the error message. Wx::MessageBox($error_message, "Slic3r Error", wxOK | wxICON_EXCLAMATION, $self); } else { $self->{layer_editing_initialized} = 1; } } elsif ($self->{layer_editing_initialized} == 2) { # Initilization failed before. Don't try to initialize and disable layer editing. $self->{layer_editing_enabled} = 0; } } } return $self->{layer_editing_enabled}; } sub layer_editing_allowed { my ($self) = @_; # Allow layer editing if either the shaders were not initialized yet and we don't know # whether it will be possible to initialize them, # or if the initialization was done already and it failed. return ! (defined($self->{layer_editing_initialized}) && $self->{layer_editing_initialized} == 2); } sub _first_selected_object_id_for_variable_layer_height_editing { my ($self) = @_; for my $i (0..$#{$self->volumes}) { if ($self->volumes->[$i]->selected) { my $object_id = int($self->volumes->[$i]->select_group_id / 1000000); # Objects with object_id >= 1000 have a specific meaning, for example the wipe tower proxy. return ($object_id >= $self->{print}->object_count) ? -1 : $object_id if $object_id < 10000; } } return -1; } # Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen. sub _variable_layer_thickness_bar_rect_screen { my ($self) = @_; my ($cw, $ch) = $self->GetSizeWH; return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, 0, $cw, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT); } sub _variable_layer_thickness_bar_rect_viewport { my ($self) = @_; my ($cw, $ch) = $self->GetSizeWH; return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom, $cw/(2*$self->_zoom), $ch/(2*$self->_zoom)); } # Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen. sub _variable_layer_thickness_reset_rect_screen { my ($self) = @_; my ($cw, $ch) = $self->GetSizeWH; return ($cw - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, $ch - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT, $cw, $ch); } sub _variable_layer_thickness_reset_rect_viewport { my ($self) = @_; my ($cw, $ch) = $self->GetSizeWH; return ((0.5*$cw-VARIABLE_LAYER_THICKNESS_BAR_WIDTH)/$self->_zoom, -$ch/(2*$self->_zoom), $cw/(2*$self->_zoom), (-0.5*$ch+VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT)/$self->_zoom); } sub _variable_layer_thickness_bar_rect_mouse_inside { my ($self, $mouse_evt) = @_; my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen; return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom; } sub _variable_layer_thickness_reset_rect_mouse_inside { my ($self, $mouse_evt) = @_; my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_reset_rect_screen; return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom; } sub _variable_layer_thickness_bar_mouse_cursor_z_relative { my ($self) = @_; my $mouse_pos = $self->ScreenToClientPoint(Wx::GetMousePosition()); my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen; return ($mouse_pos->x >= $bar_left && $mouse_pos->x <= $bar_right && $mouse_pos->y >= $bar_top && $mouse_pos->y <= $bar_bottom) ? # Inside the bar. ($bar_bottom - $mouse_pos->y - 1.) / ($bar_bottom - $bar_top - 1) : # Outside the bar. -1000.; } sub _variable_layer_thickness_action { my ($self, $mouse_event, $do_modification) = @_; # A volume is selected. Test, whether hovering over a layer thickness bar. return if $self->{layer_height_edit_last_object_id} == -1; if (defined($mouse_event)) { my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect_screen; $self->{layer_height_edit_last_z} = unscale($self->{print}->get_object($self->{layer_height_edit_last_object_id})->size->z) * ($bar_bottom - $mouse_event->GetY - 1.) / ($bar_bottom - $bar_top); $self->{layer_height_edit_last_action} = $mouse_event->ShiftDown ? ($mouse_event->RightIsDown ? 3 : 2) : ($mouse_event->RightIsDown ? 0 : 1); } # Mark the volume as modified, so Print will pick its layer height profile? Where to mark it? # Start a timer to refresh the print? schedule_background_process() ? # The PrintObject::adjust_layer_height_profile() call adjusts the profile of its associated ModelObject, it does not modify the profile of the PrintObject itself. $self->{print}->get_object($self->{layer_height_edit_last_object_id})->adjust_layer_height_profile( $self->{layer_height_edit_last_z}, $self->{layer_height_edit_strength}, $self->{layer_height_edit_band_width}, $self->{layer_height_edit_last_action}); $self->volumes->[$self->{layer_height_edit_last_object_id}]->generate_layer_height_texture( $self->{print}->get_object($self->{layer_height_edit_last_object_id}), 1); $self->Refresh; # Automatic action on mouse down with the same coordinate. $self->{layer_height_edit_timer}->Start(100, wxTIMER_CONTINUOUS); } sub mouse_event { my ($self, $e) = @_; my $pos = Slic3r::Pointf->new($e->GetPositionXY); my $object_idx_selected = $self->{layer_height_edit_last_object_id} = ($self->layer_editing_enabled && $self->{print}) ? $self->_first_selected_object_id_for_variable_layer_height_editing : -1; if ($e->Entering && &Wx::wxMSW) { # wxMSW needs focus in order to catch mouse wheel events $self->SetFocus; $self->_drag_start_xy(undef); } elsif ($e->LeftDClick) { if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) { } elsif ($self->on_double_click) { $self->on_double_click->(); } } elsif ($e->LeftDown || $e->RightDown) { # 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; $self->_layer_height_edited(0); if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) { # A volume is selected and the mouse is hovering over a layer thickness bar. # Start editing the layer height. $self->_layer_height_edited(1); $self->_variable_layer_thickness_action($e); } elsif ($object_idx_selected != -1 && $self->_variable_layer_thickness_reset_rect_mouse_inside($e)) { $self->{print}->get_object($object_idx_selected)->reset_layer_height_profile; # Index 2 means no editing, just wait for mouse up event. $self->_layer_height_edited(2); $self->Refresh; $self->Update; } else { # Select volume in this 3D canvas. # Don't deselect a volume if layer editing is enabled. We want the object to stay selected # during the scene manipulation. if ($self->enable_picking && ($volume_idx != -1 || ! $self->layer_editing_enabled)) { $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; $self->Update; } # propagate event through callback $self->on_select->($volume_idx) if $self->on_select; if ($volume_idx != -1) { if ($e->LeftDown && $self->enable_moving) { # The mouse_to_3d gets the Z coordinate from the Z buffer at the screen coordinate $pos->x,y, # an converts the screen space coordinate to unscaled object space. my $pos3d = $self->mouse_to_3d(@$pos); # Only accept the initial position, if it is inside the volume bounding box. my $volume_bbox = $self->volumes->[$volume_idx]->transformed_bounding_box; $volume_bbox->offset(1.); if ($volume_bbox->contains_point($pos3d)) { # The dragging operation is initiated. $self->_drag_volume_idx($volume_idx); $self->_drag_start_pos($pos3d); # Remember the shift to to the object center. The object center will later be used # to limit the object placement close to the bed. $self->_drag_volume_center_offset($pos3d->vector_to($volume_bbox->center)); } } 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 && ! $self->_layer_height_edited && defined($self->_drag_volume_idx)) { # Get new position at the same Z of the initial click point. my $cur_pos = Slic3r::Linef3->new( $self->mouse_to_3d($e->GetX, $e->GetY, 0), $self->mouse_to_3d($e->GetX, $e->GetY, 1)) ->intersect_plane($self->_drag_start_pos->z); # Clip the new position, so the object center remains close to the bed. { $cur_pos->translate(@{$self->_drag_volume_center_offset}); my $cur_pos2 = Slic3r::Point->new(scale($cur_pos->x), scale($cur_pos->y)); if (! $self->bed_polygon->contains_point($cur_pos2)) { my $ip = $self->bed_polygon->point_projection($cur_pos2); $cur_pos->set_x(unscale($ip->x)); $cur_pos->set_y(unscale($ip->y)); } $cur_pos->translate(@{$self->_drag_volume_center_offset->negative}); } # Calculate the translation vector. my $vector = $self->_drag_start_pos->vector_to($cur_pos); # Get the volume being dragged. my $volume = $self->volumes->[$self->_drag_volume_idx]; # Get all volumes belonging to the same group, if any. my @volumes = ($volume->drag_group_id == -1) ? ($volume) : grep $_->drag_group_id == $volume->drag_group_id, @{$self->volumes}; # Apply new temporary volume origin and ignore Z. $_->translate($vector->x, $vector->y, 0) for @volumes; $self->_drag_start_pos($cur_pos); $self->_dragged(1); $self->Refresh; $self->Update; } elsif ($e->Dragging) { if ($self->_layer_height_edited && $object_idx_selected != -1) { $self->_variable_layer_thickness_action($e) if ($self->_layer_height_edited == 1); } 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) { # Turntable mode is enabled by default. $self->_sphi($self->_sphi + ($pos->x - $orig->x) * TRACKBALLSIZE); $self->_stheta($self->_stheta - ($pos->y - $orig->y) * TRACKBALLSIZE); #- $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_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->Update; } $self->_drag_start_pos($pos); } elsif ($e->MiddleIsDown || $e->RightIsDown) { # If dragging over blank area with right button, pan. if (defined $self->_drag_start_xy) { # get point in model space at Z = 0 my $cur_pos = $self->mouse_to_3d($e->GetX, $e->GetY, 0); my $orig = $self->mouse_to_3d($self->_drag_start_xy->x, $self->_drag_start_xy->y, 0); $self->_camera_target->translate(@{$orig->vector_to($cur_pos)->negative}); $self->on_viewport_changed->() if $self->on_viewport_changed; $self->Refresh; $self->Update; } $self->_drag_start_xy($pos); } } elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) { if ($self->_layer_height_edited) { $self->_layer_height_edited(undef); $self->{layer_height_edit_timer}->Stop; $self->on_model_update->() if ($object_idx_selected != -1 && $self->on_model_update); } elsif ($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); # Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor # hovers over. if ($self->enable_picking) { $self->Update; $self->Refresh; } } else { $e->Skip(); } } sub mouse_wheel_event { my ($self, $e) = @_; if ($e->MiddleIsDown) { # Ignore the wheel events if the middle button is pressed. return; } if ($self->layer_editing_enabled && $self->{print}) { my $object_idx_selected = $self->_first_selected_object_id_for_variable_layer_height_editing; if ($object_idx_selected != -1) { # A volume is selected. Test, whether hovering over a layer thickness bar. if ($self->_variable_layer_thickness_bar_rect_mouse_inside($e)) { # Adjust the width of the selection. $self->{layer_height_edit_band_width} = max(min($self->{layer_height_edit_band_width} * (1 + 0.1 * $e->GetWheelRotation() / $e->GetWheelDelta()), 10.), 1.5); $self->Refresh; return; } } } # Calculate the zoom delta and apply it to the current zoom factor my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta(); $zoom = max(min($zoom, 4), -4); $zoom /= 10; $zoom = $self->_zoom / (1-$zoom); # Don't allow to zoom too far outside the scene. my $zoom_min = $self->get_zoom_to_bounding_box_factor($self->max_bounding_box); $zoom_min *= 0.4 if defined $zoom_min; $zoom = $zoom_min if defined $zoom_min && $zoom < $zoom_min; $self->_zoom($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->Resize($self->GetSizeWH) if $self->IsShownOnScreen; $self->Refresh; } # Reset selection. sub reset_objects { my ($self) = @_; if ($self->GetContext) { $self->SetCurrent($self->GetContext); $self->volumes->release_geometry; } $self->volumes->erase; $self->_dirty(1); } # Setup camera to view all objects. 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); } # 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 'iso') { $dirvec = VIEW_DEFAULT; } elsif ($direction eq 'left') { $dirvec = VIEW_LEFT; } elsif ($direction eq 'right') { $dirvec = VIEW_RIGHT; } elsif ($direction eq 'top') { $dirvec = VIEW_TOP; } elsif ($direction eq 'bottom') { $dirvec = VIEW_BOTTOM; } elsif ($direction eq 'front') { $dirvec = VIEW_FRONT; } elsif ($direction eq 'rear') { $dirvec = VIEW_REAR; } } my $bb = $self->volumes_bounding_box; if (! $bb->empty) { $self->_sphi($dirvec->[0]); $self->_stheta($dirvec->[1]); # Avoid gimball lock. $self->_stheta(GIMBALL_LOCK_THETA_MAX) if $self->_stheta > GIMBALL_LOCK_THETA_MAX; $self->_stheta(0) if $self->_stheta < 0; # View everything. $self->zoom_to_bounding_box($bb); $self->on_viewport_changed->() if $self->on_viewport_changed; $self->Refresh; } } sub get_zoom_to_bounding_box_factor { my ($self, $bb) = @_; return undef if ($bb->empty); my $max_size = max(@{$bb->size}) * 2; return ($max_size == 0) ? undef : min($self->GetSizeWH) / $max_size; } sub zoom_to_bounding_box { my ($self, $bb) = @_; # Calculate the zoom factor needed to adjust viewport to bounding box. my $zoom = $self->get_zoom_to_bounding_box_factor($bb); if (defined $zoom) { $self->_zoom($zoom); # 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; } # Used by ObjectCutDialog and ObjectPartsPanel to generate a rectangular ground plane # to support the scene objects. 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 ], #++ ]); # Set the origin for painting of the coordinate system axes. $self->origin(Slic3r::Pointf->new(@$center[X,Y])); } # Set the bed shape to a single closed 2D polygon (array of two element arrays), # triangulate the bed and store the triangles into $self->bed_triangles, # fills the $self->bed_grid_lines and sets $self->origin. # Sets $self->bed_polygon to limit the object placement. 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)); } # Set the origin for painting of the coordinate system axes. $self->origin(Slic3r::Pointf->new(0,0)); $self->bed_polygon(offset_ex([$expolygon->contour], $bed_bb->radius * 1.7, JT_ROUND, scale(0.5))->[0]->contour->clone); } sub deselect_volumes { my ($self) = @_; $_->set_selected(0) for @{$self->volumes}; } sub select_volume { my ($self, $volume_idx) = @_; $self->volumes->[$volume_idx]->set_selected(1) if $volume_idx != -1; } sub SetCuttingPlane { my ($self, $z, $expolygons) = @_; $self->cutting_plane_z($z); # grow slices in order to display them better $expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1); my @verts = (); foreach my $line (map @{$_->lines}, map @$_, @$expolygons) { 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]) } # Convert the screen space coordinate to an object space coordinate. # If the Z screen space coordinate is not provided, a depth buffer value is substituted. 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 GetContext { my ($self) = @_; return $self->{context} ||= Wx::GLContext->new($self); } sub SetCurrent { my ($self, $context) = @_; return $self->SUPER::SetCurrent($context); } sub UseVBOs { my ($self) = @_; if (! defined ($self->{use_VBOs})) { my $use_legacy = wxTheApp->{app_config}->get('use_legacy_opengl'); if ($use_legacy eq '1') { # Disable OpenGL 2.0 rendering. $self->{use_VBOs} = 0; # Don't enable the layer editing tool. $self->{layer_editing_enabled} = 0; # 2 means failed $self->{layer_editing_initialized} = 2; return 0; } # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized # first when an OpenGL widget is shown for the first time. How ugly. return 0 if (! $self->init && $^O eq 'linux'); # Don't use VBOs if anything fails. $self->{use_VBOs} = 0; if ($self->GetContext) { $self->SetCurrent($self->GetContext); Slic3r::GUI::_3DScene::_glew_init; my @gl_version = split(/\./, glGetString(GL_VERSION)); $self->{use_VBOs} = int($gl_version[0]) >= 2; # print "UseVBOs $self OpenGL major: $gl_version[0], minor: $gl_version[1]. Use VBOs: ", $self->{use_VBOs}, "\n"; } } return $self->{use_VBOs}; } 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(); if ($self->_camera_type eq 'ortho') { #FIXME setting the size of the box 10x larger than necessary # is only a workaround for an incorrectly set camera. # This workaround harms Z-buffer accuracy! # my $depth = 1.05 * $self->max_bounding_box->radius(); my $depth = 10.0 * $self->max_bounding_box->radius(); glOrtho( -$x/2, $x/2, -$y/2, $y/2, -$depth, $depth, ); } else { die "Invalid camera type: ", $self->_camera_type, "\n" if ($self->_camera_type ne 'perspective'); my $bbox_r = $self->max_bounding_box->radius(); my $fov = PI * 45. / 180.; my $fov_tan = tan(0.5 * $fov); my $cam_distance = 0.5 * $bbox_r / $fov_tan; $self->_camera_distance($cam_distance); my $nr = $cam_distance - $bbox_r * 1.1; my $fr = $cam_distance + $bbox_r * 1.1; $nr = 1 if ($nr < 1); $fr = $nr + 1 if ($fr < $nr + 1); my $h2 = $fov_tan * $nr; my $w2 = $h2 * $x / $y; glFrustum(-$w2, $w2, -$h2, $h2, $nr, $fr); } glMatrixMode(GL_MODELVIEW); } sub InitGL { my $self = shift; return if $self->init; return unless $self->GetContext; $self->init(1); # This is a special path for wxWidgets on GTK, where an OpenGL context is initialized # first when an OpenGL widget is shown for the first time. How ugly. # In that case the volumes are wainting to be moved to Vertex Buffer Objects # after the OpenGL context is being initialized. $self->volumes->finalize_geometry(1) if ($^O eq 'linux' && $self->UseVBOs); 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); # See "GL_MULTISAMPLE and GL_ARRAY_BUFFER_ARB messages on failed launch" # https://github.com/alexrj/Slic3r/issues/4085 eval { # Disable the multi sampling by default, so the picking by color will work correctly. glDisable(GL_MULTISAMPLE); }; # Disable multi sampling if the eval failed. $self->{can_multisample} = 0 if $@; # ambient lighting glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.3, 0.3, 0.3, 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.3, 0.3, 0.3, 1); glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 0.2, 0.2, 0.2, 1); # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. glShadeModel(GL_SMOOTH); # glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.5, 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, 0, 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}); if ($self->UseVBOs) { my $shader = new Slic3r::GUI::_3DScene::GLShader; if (! $shader->load($self->_fragment_shader_Gouraud, $self->_vertex_shader_Gouraud)) { # if (! $shader->load($self->_fragment_shader_Phong, $self->_vertex_shader_Phong)) { print "Compilaton of path shader failed: \n" . $shader->last_error . "\n"; $shader = undef; } else { $self->{plain_shader} = $shader; } } } sub DestroyGL { my $self = shift; if ($self->GetContext) { $self->SetCurrent($self->GetContext); if ($self->{plain_shader}) { $self->{plain_shader}->release; delete $self->{plain_shader}; } if ($self->{layer_height_edit_shader}) { $self->{layer_height_edit_shader}->release; delete $self->{layer_height_edit_shader}; } $self->volumes->release_geometry; } } 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; glClearColor(1, 1, 1, 1); glClearDepth(1); glDepthFunc(GL_LESS); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); { # Shift the perspective camera. my $camera_pos = Slic3r::Pointf3->new(0,0,-$self->_camera_distance); glTranslatef(@$camera_pos); } if (TURNTABLE_MODE) { # Turntable mode is enabled by default. 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); # Head light glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0); if ($self->enable_picking) { if (my $pos = $self->_mouse_pos) { # Render the object for picking. # FIXME This cannot possibly work in a multi-sampled context as the color gets mangled by the anti-aliasing. # Better to use software ray-casting on a bounding-box hierarchy. glPushAttrib(GL_ENABLE_BIT); glDisable(GL_MULTISAMPLE) if ($self->{can_multisample}); glDisable(GL_LIGHTING); glDisable(GL_BLEND); $self->draw_volumes(1); glPopAttrib(); glFlush(); 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); $_->set_hover(0) for @{$self->volumes}; if ($volume_idx <= $#{$self->volumes}) { $self->_hover_volume_idx($volume_idx); $self->volumes->[$volume_idx]->set_hover(1); my $group_id = $self->volumes->[$volume_idx]->select_group_id; if ($group_id != -1) { $_->set_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); } } # draw fixed background if ($self->background) { glDisable(GL_LIGHTING); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); # Draws a bluish bottom to top gradient over the complete screen. glDisable(GL_DEPTH_TEST); glBegin(GL_QUADS); glColor3f(0.0,0.0,0.0); glVertex3f(-1.0,-1.0, 1.0); glVertex3f( 1.0,-1.0, 1.0); glColor3f(10/255,98/255,144/255); glVertex3f( 1.0, 1.0, 1.0); glVertex3f(-1.0, 1.0, 1.0); glEnd(); glPopMatrix(); glEnable(GL_DEPTH_TEST); 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); glColor4f(0.8, 0.6, 0.5, 0.4); glNormal3d(0,0,1); glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_triangles->ptr()); 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(3); glColor4f(0.2, 0.2, 0.2, 0.4); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer_c(3, GL_FLOAT, 0, $self->bed_grid_lines->ptr()); glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3); glDisableClientState(GL_VERTEX_ARRAY); glDisable(GL_BLEND); } 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( 0.3 * max(@{ $self->bed_bounding_box->size }), 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 if (! $self->use_plain_shader) { $self->draw_volumes; } elsif ($self->UseVBOs) { $self->{plain_shader}->enable if $self->{plain_shader}; $self->volumes->render_VBOs; $self->{plain_shader}->disable; } else { $self->volumes->render_legacy; } # 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(0.8, 0.8, 0.8, 0.5); glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z); glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z); glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z); glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z); glEnd(); glEnable(GL_CULL_FACE); glDisable(GL_BLEND); } $self->draw_active_object_annotations; $self->SwapBuffers(); } sub draw_volumes { # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking. my ($self, $fakecolor) = @_; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); my $z_cursor_relative = $self->_variable_layer_thickness_bar_mouse_cursor_z_relative; foreach my $volume_idx (0..$#{$self->volumes}) { my $volume = $self->volumes->[$volume_idx]; my $shader_active = 0; my $object_id = int($volume->select_group_id / 1000000); if ($self->layer_editing_enabled && ! $fakecolor && $volume->selected && $self->{layer_height_edit_shader} && $volume->has_layer_height_texture && $object_id < $self->{print}->object_count) { # Update the height texture if the ModelObject::layer_height_texture is invalid. $volume->generate_layer_height_texture($self->{print}->get_object($object_id), 0); $self->{layer_height_edit_shader}->enable; $self->{layer_height_edit_shader}->set_uniform('z_to_texture_row', $volume->layer_height_texture_z_to_row_id); $self->{layer_height_edit_shader}->set_uniform('z_texture_row_to_normalized', 1. / $volume->layer_height_texture_height); $self->{layer_height_edit_shader}->set_uniform('z_cursor', $volume->bounding_box->z_max * $z_cursor_relative); $self->{layer_height_edit_shader}->set_uniform('z_cursor_band_width', $self->{layer_height_edit_band_width}); glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LEVEL, 0); # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $volume->layer_height_texture_width, $volume->layer_height_texture_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); # glPixelStorei(GL_UNPACK_ALIGNMENT, 1); # glPixelStorei(GL_UNPACK_ROW_LENGTH, $self->{layer_preview_z_texture_width}); glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $volume->layer_height_texture_width, $volume->layer_height_texture_height, GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level0); glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2, GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level1); $shader_active = 1; } elsif ($fakecolor) { # Object picking mode. Render the object with a color encoding the object index. 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 }); } elsif ($volume->hover) { glColor4f(@{ &HOVER_COLOR }); } else { glColor4f(@{ $volume->color }); } $volume->render; if ($shader_active) { glBindTexture(GL_TEXTURE_2D, 0); $self->{layer_height_edit_shader}->disable; } } glDisableClientState(GL_NORMAL_ARRAY); glDisable(GL_BLEND); if (defined $self->cutting_plane_z) { glLineWidth(2); glColor3f(0, 0, 0); glVertexPointer_c(3, GL_FLOAT, 0, $self->cut_lines_vertices->ptr()); glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3); glVertexPointer_c(3, GL_FLOAT, 0, 0); } glDisableClientState(GL_VERTEX_ARRAY); } sub _load_image_set_texture { my ($self, $file_name) = @_; # Load a PNG with an alpha channel. my $img = Wx::Image->new; $img->LoadFile(Slic3r::var($file_name), wxBITMAP_TYPE_PNG); # Get RGB & alpha raw data from wxImage, interleave them into a Perl array. my @rgb = unpack 'C*', $img->GetData(); my @alpha = $img->HasAlpha ? unpack 'C*', $img->GetAlpha() : (255) x (int(@rgb) / 3); my $n_pixels = int(@alpha); my @data = (0)x($n_pixels * 4); for (my $i = 0; $i < $n_pixels; $i += 1) { $data[$i*4 ] = $rgb[$i*3]; $data[$i*4+1] = $rgb[$i*3+1]; $data[$i*4+2] = $rgb[$i*3+2]; $data[$i*4+3] = $alpha[$i]; } # Initialize a raw bitmap data. my $params = { loaded => 1, valid => $n_pixels > 0, width => $img->GetWidth, height => $img->GetHeight, data => OpenGL::Array->new_list(GL_UNSIGNED_BYTE, @data), texture_id => glGenTextures_p(1) }; # Create and initialize a texture with the raw data. glBindTexture(GL_TEXTURE_2D, $params->{texture_id}); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $params->{width}, $params->{height}, 0, GL_RGBA, GL_UNSIGNED_BYTE, $params->{data}->ptr); glBindTexture(GL_TEXTURE_2D, 0); return $params; } sub _variable_layer_thickness_load_overlay_image { my ($self) = @_; $self->{layer_preview_annotation} = $self->_load_image_set_texture('variable_layer_height_tooltip.png') if (! $self->{layer_preview_annotation}->{loaded}); return $self->{layer_preview_annotation}->{valid}; } sub _variable_layer_thickness_load_reset_image { my ($self) = @_; $self->{layer_preview_reset_image} = $self->_load_image_set_texture('variable_layer_height_reset.png') if (! $self->{layer_preview_reset_image}->{loaded}); return $self->{layer_preview_reset_image}->{valid}; } # Paint the tooltip. sub _render_image { my ($self, $image, $l, $r, $b, $t) = @_; glColor4f(1.,1.,1.,1.); glDisable(GL_LIGHTING); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, $image->{texture_id}); glBegin(GL_QUADS); glTexCoord2d(0.,1.); glVertex3f($l, $b, 0); glTexCoord2d(1.,1.); glVertex3f($r, $b, 0); glTexCoord2d(1.,0.); glVertex3f($r, $t, 0); glTexCoord2d(0.,0.); glVertex3f($l, $t, 0); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); glEnable(GL_LIGHTING); } sub draw_active_object_annotations { # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking. my ($self) = @_; return if (! $self->{layer_height_edit_shader} || ! $self->layer_editing_enabled); # Find the selected volume, over which the layer editing is active. my $volume; foreach my $volume_idx (0..$#{$self->volumes}) { my $v = $self->volumes->[$volume_idx]; if ($v->selected && $v->has_layer_height_texture) { $volume = $v; last; } } return if (! $volume); # If the active object was not allocated at the Print, go away. This should only be a momentary case between an object addition / deletion # and an update by Platter::async_apply_config. my $object_idx = int($volume->select_group_id / 1000000); return if $object_idx >= $self->{print}->object_count; # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), # where x, y is the window size divided by $self->_zoom. my ($bar_left, $bar_bottom, $bar_right, $bar_top) = $self->_variable_layer_thickness_bar_rect_viewport; my ($reset_left, $reset_bottom, $reset_right, $reset_top) = $self->_variable_layer_thickness_reset_rect_viewport; my $z_cursor_relative = $self->_variable_layer_thickness_bar_mouse_cursor_z_relative; $self->{layer_height_edit_shader}->enable; $self->{layer_height_edit_shader}->set_uniform('z_to_texture_row', $volume->layer_height_texture_z_to_row_id); $self->{layer_height_edit_shader}->set_uniform('z_texture_row_to_normalized', 1. / $volume->layer_height_texture_height); $self->{layer_height_edit_shader}->set_uniform('z_cursor', $volume->bounding_box->z_max * $z_cursor_relative); $self->{layer_height_edit_shader}->set_uniform('z_cursor_band_width', $self->{layer_height_edit_band_width}); glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $volume->layer_height_texture_width, $volume->layer_height_texture_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $volume->layer_height_texture_width, $volume->layer_height_texture_height, GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level0); glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $volume->layer_height_texture_width / 2, $volume->layer_height_texture_height / 2, GL_RGBA, GL_UNSIGNED_BYTE, $volume->layer_height_texture_data_ptr_level1); # Render the color bar. glDisable(GL_DEPTH_TEST); # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), # where x, y is the window size divided by $self->_zoom. glPushMatrix(); glLoadIdentity(); # Paint the overlay. glBegin(GL_QUADS); glVertex3f($bar_left, $bar_bottom, 0); glVertex3f($bar_right, $bar_bottom, 0); glVertex3f($bar_right, $bar_top, $volume->bounding_box->z_max); glVertex3f($bar_left, $bar_top, $volume->bounding_box->z_max); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); $self->{layer_height_edit_shader}->disable; # Paint the tooltip. if ($self->_variable_layer_thickness_load_overlay_image) { my $gap = 10/$self->_zoom; my ($l, $r, $b, $t) = ($bar_left - $self->{layer_preview_annotation}->{width}/$self->_zoom - $gap, $bar_left - $gap, $reset_bottom + $self->{layer_preview_annotation}->{height}/$self->_zoom + $gap, $reset_bottom + $gap); $self->_render_image($self->{layer_preview_annotation}, $l, $r, $t, $b); } # Paint the reset button. if ($self->_variable_layer_thickness_load_reset_image) { $self->_render_image($self->{layer_preview_reset_image}, $reset_left, $reset_right, $reset_bottom, $reset_top); } # Paint the graph. #FIXME show some kind of legend. my $print_object = $self->{print}->get_object($object_idx); my $max_z = unscale($print_object->size->z); my $profile = $print_object->model_object->layer_height_profile; my $layer_height = $print_object->config->get('layer_height'); my $layer_height_max = 10000000000.; { # Get a maximum layer height value. #FIXME This is a duplicate code of Slicing.cpp. my $nozzle_diameters = $print_object->print->config->get('nozzle_diameter'); my $layer_heights_min = $print_object->print->config->get('min_layer_height'); my $layer_heights_max = $print_object->print->config->get('max_layer_height'); for (my $i = 0; $i < scalar(@{$nozzle_diameters}); $i += 1) { my $lh_min = ($layer_heights_min->[$i] == 0.) ? 0.07 : max(0.01, $layer_heights_min->[$i]); my $lh_max = ($layer_heights_max->[$i] == 0.) ? (0.75 * $nozzle_diameters->[$i]) : $layer_heights_max->[$i]; $layer_height_max = min($layer_height_max, max($lh_min, $lh_max)); } } # Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. $layer_height_max *= 1.12; # Baseline glColor3f(0., 0., 0.); glBegin(GL_LINE_STRIP); glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max, $bar_bottom); glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / $layer_height_max, $bar_top); glEnd(); # Curve glColor3f(0., 0., 1.); glBegin(GL_LINE_STRIP); for (my $i = 0; $i < int(@{$profile}); $i += 2) { my $z = $profile->[$i]; my $h = $profile->[$i+1]; glVertex3f($bar_left + $h * ($bar_right - $bar_left) / $layer_height_max, $bar_bottom + $z * ($bar_top - $bar_bottom) / $max_z, $z); } glEnd(); # Revert the matrices. glPopMatrix(); glEnable(GL_DEPTH_TEST); } sub opengl_info { my ($self, %params) = @_; my %tag = Slic3r::tags($params{format}); my $gl_version = glGetString(GL_VERSION); my $gl_vendor = glGetString(GL_VENDOR); my $gl_renderer = glGetString(GL_RENDERER); my $glsl_version = glGetString(GL_SHADING_LANGUAGE_VERSION); my $out = ''; $out .= "$tag{h2start}OpenGL installation$tag{h2end}$tag{eol}"; $out .= " $tag{bstart}Using POGL$tag{bend} v$OpenGL::BUILD_VERSION$tag{eol}"; $out .= " $tag{bstart}GL version: $tag{bend}${gl_version}$tag{eol}"; $out .= " $tag{bstart}vendor: $tag{bend}${gl_vendor}$tag{eol}"; $out .= " $tag{bstart}renderer: $tag{bend}${gl_renderer}$tag{eol}"; $out .= " $tag{bstart}GLSL version: $tag{bend}${glsl_version}$tag{eol}"; # Check for other OpenGL extensions $out .= "$tag{h2start}Installed extensions (* implemented in the module):$tag{h2end}$tag{eol}"; my $extensions = glGetString(GL_EXTENSIONS); my @extensions = split(' ',$extensions); foreach my $ext (sort @extensions) { my $stat = glpCheckExtension($ext); $out .= sprintf("%s ${ext}$tag{eol}", $stat?' ':'*'); $out .= sprintf(" ${stat}$tag{eol}") if ($stat && $stat !~ m|^$ext |); } return $out; } sub _report_opengl_state { my ($self, $comment) = @_; my $err = glGetError(); return 0 if ($err == 0); # gluErrorString() hangs. Don't use it. # my $errorstr = gluErrorString(); my $errorstr = ''; if ($err == 0x0500) { $errorstr = 'GL_INVALID_ENUM'; } elsif ($err == GL_INVALID_VALUE) { $errorstr = 'GL_INVALID_VALUE'; } elsif ($err == GL_INVALID_OPERATION) { $errorstr = 'GL_INVALID_OPERATION'; } elsif ($err == GL_STACK_OVERFLOW) { $errorstr = 'GL_STACK_OVERFLOW'; } elsif ($err == GL_OUT_OF_MEMORY) { $errorstr = 'GL_OUT_OF_MEMORY'; } else { $errorstr = 'unknown'; } if (defined($comment)) { printf("OpenGL error at %s, nr %d (0x%x): %s\n", $comment, $err, $err, $errorstr); } else { printf("OpenGL error nr %d (0x%x): %s\n", $err, $err, $errorstr); } } sub _vertex_shader_Gouraud { return <<'VERTEX'; #version 110 #define INTENSITY_CORRECTION 0.7 #define LIGHT_TOP_DIR -0.6/1.31, 0.6/1.31, 1./1.31 #define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) #define LIGHT_TOP_SPECULAR (0.5 * INTENSITY_CORRECTION) #define LIGHT_TOP_SHININESS 50. #define LIGHT_FRONT_DIR 1./1.43, 0.2/1.43, 1./1.43 #define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) #define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) #define LIGHT_FRONT_SHININESS 50. #define INTENSITY_AMBIENT 0.3 varying float intensity_specular; varying float intensity_tainted; void main() { vec3 eye, normal, lightDir, viewVector, halfVector; float NdotL, NdotHV; eye = vec3(0., 0., 1.); // First transform the normal into eye space and normalize the result. normal = normalize(gl_NormalMatrix * gl_Normal); // Now normalize the light's direction. Note that according to the OpenGL specification, the light is stored in eye space. // Also since we're talking about a directional light, the position field is actually direction. lightDir = vec3(LIGHT_TOP_DIR); halfVector = normalize(lightDir + eye); // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. NdotL = max(dot(normal, lightDir), 0.0); intensity_tainted = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; intensity_specular = 0.; if (NdotL > 0.0) intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, halfVector), 0.0), LIGHT_TOP_SHININESS); // Perform the same lighting calculation for the 2nd light source. lightDir = vec3(LIGHT_FRONT_DIR); // halfVector = normalize(lightDir + eye); NdotL = max(dot(normal, lightDir), 0.0); intensity_tainted += NdotL * LIGHT_FRONT_DIFFUSE; // compute the specular term if NdotL is larger than zero // if (NdotL > 0.0) // intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(normal, halfVector), 0.0), LIGHT_FRONT_SHININESS); gl_Position = ftransform(); } VERTEX } sub _fragment_shader_Gouraud { return <<'FRAGMENT'; #version 110 varying float intensity_specular; varying float intensity_tainted; uniform vec4 uniform_color; void main() { gl_FragColor = vec4(intensity_specular, intensity_specular, intensity_specular, 0.) + uniform_color * intensity_tainted; gl_FragColor.a = uniform_color.a; } FRAGMENT } sub _vertex_shader_Phong { return <<'VERTEX'; #version 110 varying vec3 normal; varying vec3 eye; void main(void) { eye = normalize(vec3(gl_ModelViewMatrix * gl_Vertex)); normal = normalize(gl_NormalMatrix * gl_Normal); gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; } VERTEX } sub _fragment_shader_Phong { return <<'FRAGMENT'; #version 110 #define INTENSITY_CORRECTION 0.7 #define LIGHT_TOP_DIR -0.6/1.31, 0.6/1.31, 1./1.31 #define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) #define LIGHT_TOP_SPECULAR (0.5 * INTENSITY_CORRECTION) //#define LIGHT_TOP_SHININESS 50. #define LIGHT_TOP_SHININESS 10. #define LIGHT_FRONT_DIR 1./1.43, 0.2/1.43, 1./1.43 #define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) #define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) #define LIGHT_FRONT_SHININESS 50. #define INTENSITY_AMBIENT 0.0 varying vec3 normal; varying vec3 eye; uniform vec4 uniform_color; void main() { float intensity_specular = 0.; float intensity_tainted = 0.; float intensity = max(dot(normal,vec3(LIGHT_TOP_DIR)), 0.0); // if the vertex is lit compute the specular color if (intensity > 0.0) { intensity_tainted = LIGHT_TOP_DIFFUSE * intensity; // compute the half vector vec3 h = normalize(vec3(LIGHT_TOP_DIR) + eye); // compute the specular term into spec intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(h, normal), 0.0), LIGHT_TOP_SHININESS); } intensity = max(dot(normal,vec3(LIGHT_FRONT_DIR)), 0.0); // if the vertex is lit compute the specular color if (intensity > 0.0) { intensity_tainted += LIGHT_FRONT_DIFFUSE * intensity; // compute the half vector // vec3 h = normalize(vec3(LIGHT_FRONT_DIR) + eye); // compute the specular term into spec // intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(h,normal), 0.0), LIGHT_FRONT_SHININESS); } gl_FragColor = max( vec4(intensity_specular, intensity_specular, intensity_specular, 0.) + uniform_color * intensity_tainted, INTENSITY_AMBIENT * uniform_color); gl_FragColor.a = uniform_color.a; } FRAGMENT } sub _vertex_shader_variable_layer_height { return <<'VERTEX'; #version 110 #define LIGHT_TOP_DIR 0., 1., 0. #define LIGHT_TOP_DIFFUSE 0.2 #define LIGHT_TOP_SPECULAR 0.3 #define LIGHT_TOP_SHININESS 50. #define LIGHT_FRONT_DIR 0., 0., 1. #define LIGHT_FRONT_DIFFUSE 0.5 #define LIGHT_FRONT_SPECULAR 0.3 #define LIGHT_FRONT_SHININESS 50. #define INTENSITY_AMBIENT 0.1 uniform float z_to_texture_row; varying float intensity_specular; varying float intensity_tainted; varying float object_z; void main() { vec3 eye, normal, lightDir, viewVector, halfVector; float NdotL, NdotHV; // eye = gl_ModelViewMatrixInverse[3].xyz; eye = vec3(0., 0., 1.); // First transform the normal into eye space and normalize the result. normal = normalize(gl_NormalMatrix * gl_Normal); // Now normalize the light's direction. Note that according to the OpenGL specification, the light is stored in eye space. // Also since we're talking about a directional light, the position field is actually direction. lightDir = vec3(LIGHT_TOP_DIR); halfVector = normalize(lightDir + eye); // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. NdotL = max(dot(normal, lightDir), 0.0); intensity_tainted = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; intensity_specular = 0.; // if (NdotL > 0.0) // intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, halfVector), 0.0), LIGHT_TOP_SHININESS); // Perform the same lighting calculation for the 2nd light source. lightDir = vec3(LIGHT_FRONT_DIR); halfVector = normalize(lightDir + eye); NdotL = max(dot(normal, lightDir), 0.0); intensity_tainted += NdotL * LIGHT_FRONT_DIFFUSE; // compute the specular term if NdotL is larger than zero if (NdotL > 0.0) intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(normal, halfVector), 0.0), LIGHT_FRONT_SHININESS); // Scaled to widths of the Z texture. object_z = gl_Vertex.z / gl_Vertex.w; gl_Position = ftransform(); } VERTEX } sub _fragment_shader_variable_layer_height { return <<'FRAGMENT'; #version 110 #define M_PI 3.1415926535897932384626433832795 // 2D texture (1D texture split by the rows) of color along the object Z axis. uniform sampler2D z_texture; // Scaling from the Z texture rows coordinate to the normalized texture row coordinate. uniform float z_to_texture_row; uniform float z_texture_row_to_normalized; varying float intensity_specular; varying float intensity_tainted; varying float object_z; uniform float z_cursor; uniform float z_cursor_band_width; void main() { float object_z_row = z_to_texture_row * object_z; // Index of the row in the texture. float z_texture_row = floor(object_z_row); // Normalized coordinate from 0. to 1. float z_texture_col = object_z_row - z_texture_row; float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25; // Calculate level of detail from the object Z coordinate. // This makes the slowly sloping surfaces to be show with high detail (with stripes), // and the vertical surfaces to be shown with low detail (no stripes) float z_in_cells = object_z_row * 190.; // Gradient of Z projected on the screen. float dx_vtc = dFdx(z_in_cells); float dy_vtc = dFdy(z_in_cells); float lod = clamp(0.5 * log2(max(dx_vtc*dx_vtc, dy_vtc*dy_vtc)), 0., 1.); // Sample the Z texture. Texture coordinates are normalized to <0, 1>. vec4 color = (1. - lod) * texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.) + lod * texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.); // Mix the final color. gl_FragColor = vec4(intensity_specular, intensity_specular, intensity_specular, 1.) + (1. - z_blend) * intensity_tainted * color + z_blend * vec4(1., 1., 0., 0.); // and reset the transparency. gl_FragColor.a = 1.; } FRAGMENT } # The 3D canvas to display objects and tool paths. 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'; __PACKAGE__->mk_accessors(qw( color_by select_by drag_by )); sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->color_by('volume'); # object | volume $self->select_by('object'); # object | volume | instance $self->drag_by('instance'); # object | instance return $self; } sub load_object { my ($self, $model, $print, $obj_idx, $instance_idxs) = @_; $self->SetCurrent($self->GetContext) if $self->UseVBOs; 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 $volume_indices = $self->volumes->load_object( $model_object, $obj_idx, $instance_idxs, $self->color_by, $self->select_by, $self->drag_by, $self->UseVBOs); return @{$volume_indices}; } # Create 3D thick extrusion lines for a skirt and brim. # Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes. sub load_print_toolpaths { my ($self, $print, $colors) = @_; $self->SetCurrent($self->GetContext) if $self->UseVBOs; Slic3r::GUI::_3DScene::_load_print_toolpaths($print, $self->volumes, $colors, $self->UseVBOs) if ($print->step_done(STEP_SKIRT) && $print->step_done(STEP_BRIM)); } # Create 3D thick extrusion lines for object forming extrusions. # Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes, # one for perimeters, one for infill and one for supports. sub load_print_object_toolpaths { my ($self, $object, $colors) = @_; $self->SetCurrent($self->GetContext) if $self->UseVBOs; Slic3r::GUI::_3DScene::_load_print_object_toolpaths($object, $self->volumes, $colors, $self->UseVBOs); } # Create 3D thick extrusion lines for wipe tower extrusions. sub load_wipe_tower_toolpaths { my ($self, $print, $colors) = @_; $self->SetCurrent($self->GetContext) if $self->UseVBOs; Slic3r::GUI::_3DScene::_load_wipe_tower_toolpaths($print, $self->volumes, $colors, $self->UseVBOs) if ($print->step_done(STEP_WIPE_TOWER)); } sub set_toolpaths_range { my ($self, $min_z, $max_z) = @_; $self->volumes->set_range($min_z, $max_z); } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/AboutDialog.pm000066400000000000000000000104311324354444700216030ustar00rootroot00000000000000package 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::FORK_NAME, 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 © 2016 Vojtech Bubnik, Prusa Research.
' . 'Copyright © 2011-2016 Alessandro Ranellucci.
' . 'Slic3r is licensed under the ' . 'GNU Affero General Public License, version 3.' . '


' . 'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake 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-version_1.39.1/lib/Slic3r/GUI/BedShapeDialog.pm000066400000000000000000000252771324354444700222220ustar00rootroot00000000000000# 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(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 unscale scaled_epsilon); 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/PRUSA):', "", "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; } my $input_file = $dialog->GetPaths; $dialog->Destroy; my $model = Slic3r::Model->read_from_file($input_file); my $mesh = $model->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-version_1.39.1/lib/Slic3r/GUI/BonjourBrowser.pm000066400000000000000000000030071324354444700223740ustar00rootroot00000000000000# 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-version_1.39.1/lib/Slic3r/GUI/ConfigWizard.pm000066400000000000000000000370021324354444700220020ustar00rootroot00000000000000# 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'; # adhere to various human interface guidelines our $wizard = 'Wizard'; $wizard = 'Assistant' if &Wx::wxMAC || &Wx::wxGTK; sub new { my ($class, $parent, $presets, $fresh_start) = @_; my $self = $class->SUPER::new($parent, -1, "Configuration $wizard"); # initialize an empty repository $self->{config} = Slic3r::Config->new; my $welcome_page = Slic3r::GUI::ConfigWizard::Page::Welcome->new($self, $fresh_start); $self->add_page($welcome_page); $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}}; $welcome_page->set_selection_presets([@{$presets}, 'Other']); return $self; } sub add_page { my ($self, $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) = @_; my $result; if (Wx::Wizard::RunWizard($self, $self->{pages}[0])) { my $preset_name = $self->{pages}[0]->{preset_name}; $result = { preset_name => $preset_name, reset_user_profile => $self->{pages}[0]->{reset_user_profile} }; if ($preset_name eq 'Other') { # 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] > 0) ? ($self->{config}->bed_temperature->[0] + 5) : 0 ]); $result->{config} = $self->{config}; } } $self->Destroy; return $result; } 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)); # Only show the first bullet if this is the only wizard page to be displayed. last if $i == 0 && $self->{just_welcome}; $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_keys([$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'; use Wx qw(:misc :sizer wxID_FORWARD); use Wx::Event qw(EVT_ACTIVATE EVT_CHOICE EVT_CHECKBOX); sub new { my ($class, $parent, $fresh_start) = @_; my $self = $class->SUPER::new($parent, "Welcome to the Slic3r Configuration $wizard", 'Welcome'); $self->{full_wizard_workflow} = 1; $self->{reset_user_profile} = 0; # Test for the existence of the old config path. my $message_has_legacy; { my $datadir = Slic3r::data_dir; if ($datadir =~ /Slic3rPE/) { # Check for existence of the legacy Slic3r directory. my $datadir_legacy = substr $datadir, 0, -2; my $dir_enc = Slic3r::encode_path($datadir_legacy); if (-e $dir_enc && -d $dir_enc && -e ($dir_enc . '/print') && -d ($dir_enc . '/print') && -e ($dir_enc . '/filament') && -d ($dir_enc . '/filament') && -e ($dir_enc . '/printer') && -d ($dir_enc . '/printer') && -e ($dir_enc . '/slic3r.ini')) { $message_has_legacy = "Starting with Slic3r 1.38.4, the user profile directory has been renamed to $datadir. You may consider closing Slic3r and renaming $datadir_legacy to $datadir."; } } } $self->append_text('Hello, welcome to Slic3r Prusa Edition! This '.lc($wizard).' helps you with the initial configuration; just a few settings and you will be ready to print.'); $self->append_text('Please select your printer vendor and printer type. If your printer is not listed, you may try your luck and select a similar one. If you select "Other", this ' . lc($wizard) . ' will let you set the basic 3D printer parameters.'); $self->append_text($message_has_legacy) if defined $message_has_legacy; # 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('If you received a configuration file or a config bundle from your 3D printer vendor, cancel this '.lc($wizard).' and use the "File->Load Config" or "File->Load Config Bundle" menu.'); $self->{choice} = my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, wxDefaultSize, []); $self->{vsizer}->Add($choice, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); if (! $fresh_start) { $self->{reset_checkbox} = Wx::CheckBox->new($self, -1, "Reset user profile, install from scratch"); $self->{vsizer}->Add($self->{reset_checkbox}, 0, wxEXPAND | wxTOP | wxBOTTOM, 10); } EVT_CHOICE($parent, $choice, sub { my $sel = $self->{choice}->GetStringSelection; $self->{preset_name} = $sel; $self->set_full_wizard_workflow(($sel eq 'Other') || ($sel eq '')); }); if (! $fresh_start) { EVT_CHECKBOX($self, $self->{reset_checkbox}, sub { $self->{reset_user_profile} = $self->{reset_checkbox}->GetValue(); }); } EVT_ACTIVATE($parent, sub { $self->set_full_wizard_workflow($self->{preset_name} eq 'Other'); }); return $self; } sub set_full_wizard_workflow { my ($self, $full_workflow) = @_; $self->{full_wizard_workflow} = $full_workflow; $self->{index}->{just_welcome} = !$full_workflow; $self->{index}->Refresh; my $next_button = $self->GetParent->FindWindow(wxID_FORWARD); $next_button->SetLabel($full_workflow ? "&Next >" : "&Finish"); } # Set the preset names, select the first item. sub set_selection_presets { my ($self, $names) = @_; $self->{choice}->Append($names); $self->{choice}->SetSelection(0); $self->{preset_name} = $names->[0]; } sub GetNext { my $self = shift; return $self->{full_wizard_workflow} ? $self->{next_page} : undef; } 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_keys(['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#0'); 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-version_1.39.1/lib/Slic3r/GUI/Controller.pm000066400000000000000000000150061324354444700215370ustar00rootroot00000000000000# 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 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); use List::Util qw(first); __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 @panels = $self->print_panels; # remove printers that already exist # update configs of currently loaded print panels foreach my $preset (@{wxTheApp->{preset_bundle}->printer}) { my $preset_name = $preset->name; next if ! $preset->config->serial_port || defined first { defined $_ && $_->printer_name eq $preset_name } @panels; my $myconfig = $preset->config->clone_only(\@ConfigOptions); my $id = &Wx::NewId(); $menu->Append($id, $preset_name); EVT_MENU($menu, $id, sub { $self->add_printer($preset_name, $myconfig); }); } $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 = map { $_->name => $_->config->clone_only(\@ConfigOptions) } grep { $_->config->serial_port } @{wxTheApp->{preset_bundle}->printer}; # 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 } Slic3r::GUI::scan_serial_ports; $active{$_} = 1 for grep exists $ports{$presets{$_}->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, $printer_name, $config) = @_; # check that printer doesn't exist already foreach my $panel ($self->print_panels) { if ($panel->printer_name eq $printer_name) { return $panel; } } my $printer_panel = Slic3r::GUI::Controller::PrinterPanel->new($self, $printer_name, $config); $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; } # Called by Slic3r::GUI::Tab::Printer::_on_presets_changed # when the presets are loaded or the user selects another preset. sub update_presets { my ($self, $presets) = @_; # update configs of currently loaded print panels my @presets = @$presets; foreach my $panel ($self->print_panels) { my $preset = $presets->find_preset($panel->printer_name, 0); $panel->config($preset->config->clone_only(\@ConfigOptions)) if defined $preset; } $self->_selected_printer_preset($presets->get_selected_preset->name); } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/Controller/000077500000000000000000000000001324354444700211775ustar00rootroot00000000000000Slic3r-version_1.39.1/lib/Slic3r/GUI/Controller/ManualControlDialog.pm000066400000000000000000000145271324354444700254440ustar00rootroot00000000000000# A printer "Controller" -> "ManualControlDialog" subtab, opened per 3D printer connected? package Slic3r::GUI::Controller::ManualControlDialog; use strict; use warnings; use utf8; use Wx qw(:dialog :id :misc :sizer :choicebook :button :bitmap wxBORDER_NONE wxTAB_TRAVERSAL); use Wx::Event qw(EVT_CLOSE EVT_BUTTON); use base qw(Wx::Dialog Class::Accessor); __PACKAGE__->mk_accessors(qw(sender config2 x_homed y_homed)); sub new { my ($class, $parent, $config, $sender) = @_; my $self = $class->SUPER::new($parent, -1, "Manual Control", wxDefaultPosition, [500,380], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); $self->sender($sender); $self->config2({ xy_travel_speed => 130, z_travel_speed => 10, }); 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); $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 => '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 $main_sizer = Wx::BoxSizer->new(wxVERTICAL); $main_sizer->Add($bed_sizer, 1, wxEXPAND | wxALL, 10); $main_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10); #$main_sizer->Add($self->CreateButtonSizer(wxCLOSE), 0, wxEXPAND); #EVT_BUTTON($self, wxID_CLOSE, sub { $self->Close }); $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-version_1.39.1/lib/Slic3r/GUI/Controller/PrinterPanel.pm000066400000000000000000000624441324354444700241520ustar00rootroot00000000000000package Slic3r::GUI::Controller::PrinterPanel; use strict; use warnings; use utf8; 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)); 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, $printer_name, $config) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, [500, 250]); $self->printer_name($printer_name || 'Printer'); $self->config($config); $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->{log_textctrl}->AppendText("$_\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($box, -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($box, -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($box, -1, $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($box, -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($box, -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($box, -1, $config->serial_speed, wxDefaultPosition, wxDefaultSize, ["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($box, -1, "Disconnect", wxDefaultPosition, wxDefaultSize); $btn->SetFont($Slic3r::GUI::small_font); $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($box, -1, "Connect to printer", wxDefaultPosition, [-1, 40]); my $font = $btn->GetFont; $font->SetPointSize($font->GetPointSize + 2); $btn->SetFont($font); $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($box, -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($box, -1, "Manual control", wxDefaultPosition, wxDefaultSize); $btn->SetFont($Slic3r::GUI::small_font); $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 { my $dlg = Slic3r::GUI::Controller::ManualControlDialog->new ($self, $self->config, $self->sender); $dlg->ShowModal; }); } # temperature { my $temp_panel = $self->{temp_panel} = Wx::Panel->new($box, -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($box, -1, "Queue:", wxDefaultPosition, wxDefaultSize); $text->SetFont($Slic3r::GUI::small_font); $print_jobs_sizer->Add($text, 0, wxEXPAND, 0); $self->{jobs_panel} = Wx::ScrolledWindow->new($box, -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($box, -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($box, -1, "", wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxBORDER_NONE); $log->SetBackgroundColour($box->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 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); } 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 Slic3r::GUI::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->{log_textctrl}->AppendText(sprintf "=====\n"); $self->{log_textctrl}->AppendText(sprintf "Printing %s\n", $job->name); $self->{log_textctrl}->AppendText(sprintf "Print started at %s\n", $self->_timestamp); } 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->{log_textctrl}->AppendText(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->{log_textctrl}->AppendText(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)); sub new { my ($class, $parent, $job) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize); $self->job($job); $self->SetBackgroundColour(wxWHITE); { my $white_brush = Wx::Brush->new(wxWHITE, 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); } $left_sizer->Add($text, 0, wxEXPAND, 0); } { my $filament_stats = join "\n", map "$_ (" . sprintf("%.2f", $job->filament_stats->{$_}/1000) . "m)", sort keys %{$job->filament_stats}; my $text = Wx::StaticText->new($self, -1, $filament_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); $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); $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; } $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; } $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; } $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-version_1.39.1/lib/Slic3r/GUI/MainFrame.pm000066400000000000000000000744621324354444700212660ustar00rootroot00000000000000# The main frame, the parent of all. package Slic3r::GUI::MainFrame; use strict; use warnings; use utf8; use File::Basename qw(basename dirname); use FindBin; use List::Util qw(min first); use Slic3r::Geometry qw(X Y); use Wx qw(:frame :bitmap :id :misc :notebook :panel :sizer :menu :dialog :filedialog :font :icon wxTheApp); use Wx::Event qw(EVT_CLOSE EVT_MENU EVT_NOTEBOOK_PAGE_CHANGED); use base 'Wx::Frame'; our $qs_last_input_file; our $qs_last_output_file; our $last_config; sub new { my ($class, %params) = @_; my $self = $class->SUPER::new(undef, -1, $Slic3r::FORK_NAME . ' - ' . $Slic3r::VERSION, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE); Slic3r::GUI::set_main_frame($self); if ($^O eq 'MSWin32') { # Load the icon either from the exe, or from the ico file. my $iconfile = Slic3r::decode_path($FindBin::Bin) . '\slic3r.exe'; $iconfile = Slic3r::var("Slic3r.ico") unless -f $iconfile; $self->SetIcon(Wx::Icon->new($iconfile, wxBITMAP_TYPE_ICO)); } else { $self->SetIcon(Wx::Icon->new(Slic3r::var("Slic3r_128px.png"), wxBITMAP_TYPE_PNG)); } # store input params # If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. $self->{no_controller} = $params{no_controller}; $self->{no_plater} = $params{no_plater}; $self->{loaded} = 0; # 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://github.com/prusa3d/slic3r/releases"); $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 && !$self->check_unsaved_changes) { $event->Veto; return; } # save window size wxTheApp->save_window_pos($self, "main_frame"); # Save the slic3r.ini. Usually the ini file is saved from "on idle" callback, # but in rare cases it may not have been called yet. wxTheApp->{app_config}->save; # propagate event $event->Skip; }); $self->update_ui_from_settings; return $self; } sub _init_tabpanel { my ($self) = @_; $self->{tabpanel} = my $panel = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); Slic3r::GUI::set_tab_panel($panel); EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{tabpanel}, sub { my $panel = $self->{tabpanel}->GetCurrentPage; $panel->OnActivate if $panel->can('OnActivate'); }); if (!$self->{no_plater}) { $panel->AddPage($self->{plater} = Slic3r::GUI::Plater->new($panel), "Plater"); if (!$self->{no_controller}) { $panel->AddPage($self->{controller} = Slic3r::GUI::Controller->new($panel), "Controller"); } } $self->{options_tabs} = {}; for my $tab_name (qw(print filament printer)) { my $tab; $tab = $self->{options_tabs}{$tab_name} = ("Slic3r::GUI::Tab::" . ucfirst $tab_name)->new( $panel, no_controller => $self->{no_controller}); # Callback to be executed after any of the configuration fields (Perl class Slic3r::GUI::OptionsGroup::Field) change their value. $tab->on_value_change(sub { my ($opt_key, $value) = @_; my $config = $tab->{presets}->get_current_preset->config; if ($self->{plater}) { $self->{plater}->on_config_change($config); # propagate config change events to the plater $self->{plater}->on_extruders_change($value) if $opt_key eq 'extruders_count'; } # don't save while loading for the first time $self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave && $self->{loaded}; }); # Install a callback for the tab to update the platter and print controller presets, when # a preset changes at Slic3r::GUI::Tab. $tab->on_presets_changed(sub { if ($self->{plater}) { # Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs. $self->{plater}->update_presets($tab_name, @_); if ($tab_name eq 'printer') { # Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. my ($presets, $reload_dependent_tabs) = @_; for my $tab_name_other (qw(print filament)) { # If the printer tells us that the print or filament preset has been switched or invalidated, # refresh the print or filament tab page. Otherwise just refresh the combo box. my $update_action = ($reload_dependent_tabs && (first { $_ eq $tab_name_other } (@{$reload_dependent_tabs}))) ? 'load_current_preset' : 'update_tab_ui'; $self->{options_tabs}{$tab_name_other}->$update_action; } # Update the controller printers. $self->{controller}->update_presets(@_) if $self->{controller}; } $self->{plater}->on_config_change($tab->{presets}->get_current_preset->config); } }); # Load the currently selected preset into the GUI, update the preset selection box. $tab->load_current_preset; $panel->AddPage($tab, $tab->title); } #TODO this is an example of a Slic3r XS interface call to add a new preset editor page to the main view. # Slic3r::GUI::create_preset_tab("print"); if ($self->{plater}) { $self->{plater}->on_select_preset(sub { my ($group, $name) = @_; $self->{options_tabs}{$group}->select_preset($name); }); # load initial config my $full_config = wxTheApp->{preset_bundle}->full_config; $self->{plater}->on_config_change($full_config); # Show a correct number of filament fields. $self->{plater}->on_extruders_change(int(@{$full_config->nozzle_diameter})); } } sub _init_menubar { my ($self) = @_; # File menu my $fileMenu = Wx::Menu->new; { wxTheApp->append_menu_item($fileMenu, "Open STL/OBJ/AMF…\tCtrl+O", 'Open a model', sub { $self->{plater}->add if $self->{plater}; }, undef, undef); #'brick_add.png'); $self->_append_menu_item($fileMenu, "&Load Config…\tCtrl+L", 'Load exported configuration file', sub { $self->load_config_file; }, undef, 'plugin_add.png'); $self->_append_menu_item($fileMenu, "&Export Config…\tCtrl+E", 'Export current configuration to file', sub { $self->export_config; }, undef, 'plugin_go.png'); $self->_append_menu_item($fileMenu, "&Load Config Bundle…", 'Load presets from a bundle', sub { $self->load_configbundle; }, undef, 'lorry_add.png'); $self->_append_menu_item($fileMenu, "&Export Config Bundle…", 'Export all presets to file', sub { $self->export_configbundle; }, undef, 'lorry_go.png'); $fileMenu->AppendSeparator(); my $repeat; $self->_append_menu_item($fileMenu, "Q&uick Slice…\tCtrl+U", 'Slice a file into a G-code', sub { wxTheApp->CallAfter(sub { $self->quick_slice; $repeat->Enable(defined $Slic3r::GUI::MainFrame::last_input_file); }); }, undef, 'cog_go.png'); $self->_append_menu_item($fileMenu, "Quick Slice and Save &As…\tCtrl+Alt+U", 'Slice a file into a G-code, 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 = $self->_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(); $self->_append_menu_item($fileMenu, "Slice to SV&G…\tCtrl+G", 'Slice file to a multi-layer SVG', sub { $self->quick_slice(save_as => 1, export_svg => 1); }, undef, 'shape_handles.png'); $self->{menu_item_reslice_now} = $self->_append_menu_item( $fileMenu, "(&Re)Slice Now\tCtrl+S", 'Start new slicing process', sub { $self->reslice_now; }, undef, 'shape_handles.png'); $fileMenu->AppendSeparator(); $self->_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? $self->_append_menu_item($fileMenu, "Preferences…\tCtrl+,", 'Application preferences', sub { Slic3r::GUI::Preferences->new($self)->ShowModal; }, wxID_PREFERENCES); $fileMenu->AppendSeparator(); $self->_append_menu_item($fileMenu, "&Quit", 'Quit Slic3r', sub { $self->Close(0); }, wxID_EXIT); } # Plater menu unless ($self->{no_plater}) { my $plater = $self->{plater}; $self->{plater_menu} = Wx::Menu->new; $self->_append_menu_item($self->{plater_menu}, "Export G-code...", 'Export current plate as G-code', sub { $plater->export_gcode; }, undef, 'cog_go.png'); $self->_append_menu_item($self->{plater_menu}, "Export plate as STL...", 'Export current plate as STL', sub { $plater->export_stl; }, undef, 'brick_go.png'); $self->_append_menu_item($self->{plater_menu}, "Export plate as AMF...", 'Export current plate as AMF', sub { $plater->export_amf; }, undef, 'brick_go.png'); $self->{object_menu} = $self->{plater}->object_menu; $self->on_plater_selection_changed(0); } # Window menu my $windowMenu = Wx::Menu->new; { my $tab_offset = 0; if (!$self->{no_plater}) { $self->_append_menu_item($windowMenu, "Select &Plater Tab\tCtrl+1", 'Show the plater', sub { $self->select_tab(0); }, undef, 'application_view_tile.png'); $tab_offset += 1; } if (!$self->{no_controller}) { $self->_append_menu_item($windowMenu, "Select &Controller Tab\tCtrl+T", 'Show the printer controller', sub { $self->select_tab(1); }, undef, 'printer_empty.png'); $tab_offset += 1; } if ($tab_offset > 0) { $windowMenu->AppendSeparator(); } $self->_append_menu_item($windowMenu, "Select P&rint Settings Tab\tCtrl+2", 'Show the print settings', sub { $self->select_tab($tab_offset+0); }, undef, 'cog.png'); $self->_append_menu_item($windowMenu, "Select &Filament Settings Tab\tCtrl+3", 'Show the filament settings', sub { $self->select_tab($tab_offset+1); }, undef, 'spool.png'); $self->_append_menu_item($windowMenu, "Select Print&er Settings Tab\tCtrl+4", 'Show the printer settings', sub { $self->select_tab($tab_offset+2); }, undef, 'printer_empty.png'); } # View menu if (!$self->{no_plater}) { $self->{viewMenu} = Wx::Menu->new; # \xA0 is a non-breaing space. It is entered here to spoil the automatic accelerators, # as the simple numeric accelerators spoil all numeric data entry. # The camera control accelerators are captured by 3DScene Perl module instead. my $accel = ($^O eq 'MSWin32') ? sub { $_[0] . "\t\xA0" . $_[1] } : sub { $_[0] }; $self->_append_menu_item($self->{viewMenu}, $accel->('Iso', '0'), 'Iso View' , sub { $self->select_view('iso' ); }); $self->_append_menu_item($self->{viewMenu}, $accel->('Top', '1'), 'Top View' , sub { $self->select_view('top' ); }); $self->_append_menu_item($self->{viewMenu}, $accel->('Bottom', '2'), 'Bottom View' , sub { $self->select_view('bottom' ); }); $self->_append_menu_item($self->{viewMenu}, $accel->('Front', '3'), 'Front View' , sub { $self->select_view('front' ); }); $self->_append_menu_item($self->{viewMenu}, $accel->('Rear', '4'), 'Rear View' , sub { $self->select_view('rear' ); }); $self->_append_menu_item($self->{viewMenu}, $accel->('Left', '5'), 'Left View' , sub { $self->select_view('left' ); }); $self->_append_menu_item($self->{viewMenu}, $accel->('Right', '6'), 'Right View' , sub { $self->select_view('right' ); }); } # Help menu my $helpMenu = Wx::Menu->new; { $self->_append_menu_item($helpMenu, "&Configuration $Slic3r::GUI::ConfigWizard::wizard…", "Run Configuration $Slic3r::GUI::ConfigWizard::wizard", sub { # Run the config wizard, offer the "reset user profile" checkbox. $self->config_wizard(0); }); $helpMenu->AppendSeparator(); $self->_append_menu_item($helpMenu, "Prusa 3D Drivers", 'Open the Prusa3D drivers download page in your browser', sub { Wx::LaunchDefaultBrowser('http://www.prusa3d.com/drivers/'); }); $self->_append_menu_item($helpMenu, "Prusa Edition Releases", 'Open the Prusa Edition releases page in your browser', sub { Wx::LaunchDefaultBrowser('http://github.com/prusa3d/slic3r/releases'); }); # my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", 'Check for new Slic3r versions', sub { # wxTheApp->check_version(1); # }); # $versioncheck->Enable(wxTheApp->have_version_check); $self->_append_menu_item($helpMenu, "Slic3r &Website", 'Open the Slic3r website in your browser', sub { Wx::LaunchDefaultBrowser('http://slic3r.org/'); }); $self->_append_menu_item($helpMenu, "Slic3r &Manual", 'Open the Slic3r manual in your browser', sub { Wx::LaunchDefaultBrowser('http://manual.slic3r.org/'); }); $helpMenu->AppendSeparator(); $self->_append_menu_item($helpMenu, "System Info", 'Show system information', sub { wxTheApp->system_info; }); $self->_append_menu_item($helpMenu, "Report an Issue", 'Report an issue on the Slic3r Prusa Edition', sub { Wx::LaunchDefaultBrowser('http://github.com/prusa3d/slic3r/issues/new'); }); $self->_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($windowMenu, "&Window"); $menubar->Append($self->{viewMenu}, "&View") if $self->{viewMenu}; $menubar->Append($helpMenu, "&Help"); # Add an optional debug menu. In production code, the add_debug_menu() call should do nothing. Slic3r::GUI::add_debug_menu($menubar); $self->SetMenuBar($menubar); } } sub is_loaded { my ($self) = @_; return $self->{loaded}; } # Selection of a 3D object changed on the platter. 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; } # To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG". sub quick_slice { my ($self, %params) = @_; my $progress_dialog; eval { # validate configuration my $config = wxTheApp->{preset_bundle}->full_config(); $config->validate; # select input file my $input_file; if (!$params{reslice}) { my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/PRUSA):', wxTheApp->{app_config}->get_last_dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; } $input_file = $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); wxTheApp->{app_config}->update_skein_dir(dirname($input_file)); 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) = @_; $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); # Copy the names of active presets into the placeholder parser. wxTheApp->{preset_bundle}->export_selections_pp($sprint->placeholder_parser); # 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}) { # The following line may die if the output_filename_format template substitution fails. $output_file = $sprint->output_filepath; $output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/ if $params{export_svg}; my $dlg = Wx::FileDialog->new($self, 'Save ' . ($params{export_svg} ? 'SVG' : 'G-code') . ' file as:', wxTheApp->{app_config}->get_last_output_dir(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 = $dlg->GetPath; $qs_last_output_file = $output_file unless $params{export_svg}; wxTheApp->{app_config}->update_last_output_dir(dirname($output_file)); $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 reslice_now { my ($self) = @_; $self->{plater}->reslice if $self->{plater}; } sub repair_stl { my $self = shift; my $input_file; { my $dialog = Wx::FileDialog->new($self, 'Select the STL file to repair:', wxTheApp->{app_config}->get_last_dir, "", &Slic3r::GUI::FILE_WILDCARDS->{stl}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { $dialog->Destroy; return; } $input_file = $dialog->GetPaths; $dialog->Destroy; } my $output_file = $input_file; { $output_file =~ s/\.[sS][tT][lL]$/_fixed.obj/; 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 = $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; # Generate a cummulative configuration for the selected print, filaments and printer. my $config = wxTheApp->{preset_bundle}->full_config(); # Validate the cummulative configuration. eval { $config->validate; }; Slic3r::GUI::catch_error($self) and return; # Ask user for the file name for the config file. my $dlg = Wx::FileDialog->new($self, 'Save configuration as:', $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, $last_config ? basename($last_config) : "config.ini", &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef; $dlg->Destroy; if (defined $file) { wxTheApp->{app_config}->update_config_dir(dirname($file)); $last_config = $file; $config->save($file); } } # Load a config file containing a Print, Filament & Printer preset. sub load_config_file { my ($self, $file) = @_; if (!$file) { return unless $self->check_unsaved_changes; my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, "config.ini", 'INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g', wxFD_OPEN | wxFD_FILE_MUST_EXIST); return unless $dlg->ShowModal == wxID_OK; $file = $dlg->GetPaths; $dlg->Destroy; } eval { wxTheApp->{preset_bundle}->load_config_file($file); }; # Dont proceed further if the config file cannot be loaded. return if Slic3r::GUI::catch_error($self); $_->load_current_preset for (values %{$self->{options_tabs}}); wxTheApp->{app_config}->update_config_dir(dirname($file)); $last_config = $file; } sub export_configbundle { my ($self) = @_; return unless $self->check_unsaved_changes; # validate current configuration in case it's dirty eval { wxTheApp->{preset_bundle}->full_config->validate; }; Slic3r::GUI::catch_error($self) and return; # Ask user for a file name. my $dlg = Wx::FileDialog->new($self, 'Save presets bundle as:', $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, "Slic3r_config_bundle.ini", &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); my $file = ($dlg->ShowModal == wxID_OK) ? $dlg->GetPath : undef; $dlg->Destroy; if (defined $file) { # Export the config bundle. wxTheApp->{app_config}->update_config_dir(dirname($file)); eval { wxTheApp->{preset_bundle}->export_configbundle($file); }; Slic3r::GUI::catch_error($self) and return; } } # Loading a config bundle with an external file name used to be used # to auto-install a config bundle on a fresh user account, # but that behavior was not documented and likely buggy. sub load_configbundle { my ($self, $file, $reset_user_profile) = @_; return unless $self->check_unsaved_changes; if (!$file) { my $dlg = Wx::FileDialog->new($self, 'Select configuration to load:', $last_config ? dirname($last_config) : wxTheApp->{app_config}->get_last_dir, "config.ini", &Slic3r::GUI::FILE_WILDCARDS->{ini}, wxFD_OPEN | wxFD_FILE_MUST_EXIST); return unless $dlg->ShowModal == wxID_OK; $file = $dlg->GetPaths; $dlg->Destroy; } wxTheApp->{app_config}->update_config_dir(dirname($file)); my $presets_imported = 0; eval { $presets_imported = wxTheApp->{preset_bundle}->load_configbundle($file); }; Slic3r::GUI::catch_error($self) and return; # Load the currently selected preset into the GUI, update the preset selection box. foreach my $tab (values %{$self->{options_tabs}}) { $tab->load_current_preset; } my $message = sprintf "%d presets successfully imported.", $presets_imported; Slic3r::GUI::show_info($self, $message); } # Load a provied DynamicConfig into the Print / Filament / Printer tabs, thus modifying the active preset. # Also update the platter with the new presets. sub load_config { my ($self, $config) = @_; $_->load_config($config) foreach values %{$self->{options_tabs}}; $self->{plater}->on_config_change($config) if $self->{plater}; } sub config_wizard { my ($self, $fresh_start) = @_; # Exit wizard if there are unsaved changes and the user cancels the action. return unless $self->check_unsaved_changes; # Enumerate the profiles bundled with the Slic3r installation under resources/profiles. my $directory = Slic3r::resources_dir() . "/profiles"; my @profiles = (); if (opendir(DIR, Slic3r::encode_path($directory))) { while (my $file = readdir(DIR)) { if ($file =~ /\.ini$/) { $file =~ s/\.ini$//; push @profiles, Slic3r::decode_path($file); } } closedir(DIR); } # Open the wizard. if (my $result = Slic3r::GUI::ConfigWizard->new($self, \@profiles, $fresh_start)->run) { eval { if ($result->{reset_user_profile}) { wxTheApp->{preset_bundle}->reset(1); } if (defined $result->{config}) { # Load and save the settings into print, filament and printer presets. wxTheApp->{preset_bundle}->load_config('My Settings', $result->{config}); } else { # Wizard returned a name of a preset bundle bundled with the installation. Unpack it. wxTheApp->{preset_bundle}->load_configbundle($directory . '/' . $result->{preset_name} . '.ini'); } }; Slic3r::GUI::catch_error($self) and return; # Load the currently selected preset into the GUI, update the preset selection box. foreach my $tab (values %{$self->{options_tabs}}) { $tab->load_current_preset; } } } # This is called when closing the application, when loading a config file or when starting the config wizard # to notify the user whether he is aware that some preset changes will be lost. sub check_unsaved_changes { my $self = shift; my @dirty = (); foreach my $tab (values %{$self->{options_tabs}}) { push @dirty, $tab->title if $tab->{presets}->current_is_dirty; } if (@dirty) { my $titles = join ', ', @dirty; my $confirm = Wx::MessageDialog->new($self, "You have unsaved changes ($titles). Discard changes and continue anyway?", 'Unsaved Presets', wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); return $confirm->ShowModal == wxID_YES; } return 1; } sub select_tab { my ($self, $tab) = @_; $self->{tabpanel}->SetSelection($tab); } # Set a camera direction, zoom to all objects. sub select_view { my ($self, $direction) = @_; if (! $self->{no_plater}) { $self->{plater}->select_view($direction); } } sub _append_menu_item { my ($self, $menu, $string, $description, $cb, $id, $icon) = @_; $id //= &Wx::NewId(); my $item = $menu->Append($id, $string, $description); $self->_set_menu_item_icon($item, $icon); EVT_MENU($self, $id, $cb); 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)); } } # Called after the Preferences dialog is closed and the program settings are saved. # Update the UI based on the current preferences. sub update_ui_from_settings { my ($self) = @_; $self->{menu_item_reslice_now}->Enable(! wxTheApp->{app_config}->get("background_processing")); $self->{plater}->update_ui_from_settings if ($self->{plater}); for my $tab_name (qw(print filament printer)) { $self->{options_tabs}{$tab_name}->update_ui_from_settings; } } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/Notifier.pm000066400000000000000000000026621324354444700211770ustar00rootroot00000000000000# 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-version_1.39.1/lib/Slic3r/GUI/OptionsGroup.pm000066400000000000000000000407741324354444700220760ustar00rootroot00000000000000# A dialog group object. Used by the Tab, 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 '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 { [] }); # Extra UI components after the label and the edit widget of the option. has '_extra_widgets' => (is => 'ro', default => sub { [] }); # 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 get_options { my ($self) = @_; return [ @{$self->_options} ]; } sub get_extra_widgets { my ($self) = @_; return [ @{$self->_extra_widgets} ]; } # Configuration of an option. # This very much reflects the content of the C++ ConfigOptionDef class. 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) = @_; $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 ]; # Slic3r::Config::Options is a C++ Slic3r::PrintConfigDef exported as a Perl hash of hashes. # The C++ counterpart is a constant singleton. 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->{gui_flags} =~ /\bserialized\b/); 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}, ); } sub create_single_option_line { my ($self, $opt_key, $opt_index) = @_; my $option; if (ref($opt_key)) { $option = $opt_key; } else { $option = $self->get_option($opt_key, $opt_index); } return $self->SUPER::create_single_option_line($option); } sub append_single_option_line { my ($self, $option, $opt_index) = @_; return $self->append_line($self->create_single_option_line($option, $opt_index)); } # Initialize UI components with the config values. 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->gui_flags =~ /\bserialized\b/)); } } 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, $deserialize) = @_; if ($deserialize) { # Want to edit a vector value (currently only multi-strings) in a single edit box. # Aggregate the strings the old way. # Currently used for the post_process config value only. die "Can't deserialize option indexed value" if $opt_index != -1; return join(';', @{$self->config->get($opt_key)}); } else { return $opt_index == -1 ? $self->config->get($opt_key) : $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 ($option->gui_flags =~ /\bserialized\b/) { die "Can't set serialized option indexed value" if $opt_index != -1; # Split a string to multiple strings by a semi-colon. This is the old way of storing multi-string values. # Currently used for the post_process config value only. my @values = split /;/, $field_value; $self->config->set($opt_key, \@values); } else { if ($opt_index == -1) { $self->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; } # Static text shown among the options. # Currently used for the filament cooling legend only. 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-version_1.39.1/lib/Slic3r/GUI/OptionsGroup/000077500000000000000000000000001324354444700215245ustar00rootroot00000000000000Slic3r-version_1.39.1/lib/Slic3r/GUI/OptionsGroup/Field.pm000066400000000000000000000403571324354444700231160ustar00rootroot00000000000000# An input field class prototype. package Slic3r::GUI::OptionsGroup::Field; use Moo; # This is a base class for option fields. has 'parent' => (is => 'ro', required => 1); # Slic3r::GUI::OptionsGroup::Option has 'option' => (is => 'ro', required => 1); # On change callback has 'on_change' => (is => 'rw', default => sub { sub {} }); has 'on_kill_focus' => (is => 'rw', default => sub { sub {} }); # If set, the callback $self->on_change is not called. # This is used to avoid recursive invocation of the field change/update by wxWidgets. 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) = @_; $self->SetToolTipString($tooltip) if $tooltip && $self->can('SetToolTipString'); } 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->wxWindow->SetToolTipString($self->option->tooltip) if $self->option->tooltip && $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); }); } sub get_value { my ($self) = @_; return $self->wxWindow->GetValue ? 1 : 0; } 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/^#//; # If the color is in an invalid format, set it to white. $string = 'FFFFFF' if ($string !~ m/^[[:xdigit:]]{6}/); 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::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 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 { if (! $self->disable_change_event) { $self->textctrl->SetLabel($self->get_value); $self->_on_change($self->option->opt_id); } }); EVT_TEXT($self->parent, $textctrl, sub { my $value = $textctrl->GetValue; if ($value =~ /^-?\d+(\.\d*)?$/) { $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->_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->textctrl->SetLabel($self->get_value); $self->disable_change_event(0); } sub get_value { my ($self) = @_; return $self->slider->GetValue/$self->scale; } 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); } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/Plater.pm000066400000000000000000002600521324354444700206460ustar00rootroot00000000000000# The "Plater" tab. It contains the "3D", "2D", "Preview" and "Layers" subtabs. package Slic3r::GUI::Plater; use strict; use warnings; use utf8; use File::Basename qw(basename dirname); use List::Util qw(sum first max); use Slic3r::Geometry qw(X Y Z scale unscale deg2rad rad2deg); use LWP::UserAgent; use threads::shared qw(shared_clone); use Wx qw(:button :colour :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc :panel :sizer :toolbar :window wxTheApp :notebook :combobox wxNullBitmap); use Wx::Event qw(EVT_BUTTON EVT_TOGGLEBUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_LEFT_DOWN EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED); use base 'Wx::Panel'; 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_SETTINGS => &Wx::NewId; use constant TB_LAYER_EDITING => &Wx::NewId; # package variables to avoid passing lexicals to threads our $PROGRESS_BAR_EVENT : shared = Wx::NewEventType; our $ERROR_EVENT : shared = Wx::NewEventType; # Emitted from the worker thread when the G-code export is finished. 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 my $PreventListEvents = 0; sub new { my ($class, $parent) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{config} = Slic3r::Config::new_from_defaults_keys([qw( bed_shape complete_objects extruder_clearance_radius skirts skirt_distance brim_width variable_layer_height serial_port serial_speed octoprint_host octoprint_apikey nozzle_diameter single_extruder_multi_material wipe_tower wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe extruder_colour filament_colour )]); # C++ Slic3r::Model with Perl extensions in Slic3r/Model.pm $self->{model} = Slic3r::Model->new; # C++ Slic3r::Print with Perl extensions in Slic3r/Print.pm $self->{print} = Slic3r::Print->new; # List of Perl objects Slic3r::GUI::Plater::Object, representing a 2D preview of the platter. $self->{objects} = []; $self->{print}->set_status_cb(sub { my ($percent, $message) = @_; Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $PROGRESS_BAR_EVENT, shared_clone([$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) = @_; # Ignore the special objects (the wipe tower proxy and such). $self->select_object((defined($obj_idx) && $obj_idx < 1000) ? $obj_idx : undef); }; 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->update; }; # Initialize 3D plater if ($Slic3r::GUI::have_OpenGL) { $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{print}, $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_arrange(sub { $self->arrange }); $self->{canvas3D}->set_on_rotate_object_left(sub { $self->rotate(-45, Z, 'relative') }); $self->{canvas3D}->set_on_rotate_object_right(sub { $self->rotate( 45, Z, 'relative') }); $self->{canvas3D}->set_on_scale_object_uniformly(sub { $self->changescale(undef) }); $self->{canvas3D}->set_on_increase_objects(sub { $self->increase() }); $self->{canvas3D}->set_on_decrease_objects(sub { $self->decrease() }); $self->{canvas3D}->set_on_remove_object(sub { $self->remove() }); $self->{canvas3D}->set_on_instances_moved($on_instances_moved); $self->{canvas3D}->set_on_wipe_tower_moved(sub { my ($new_pos_3f) = @_; my $cfg = Slic3r::Config->new; $cfg->set('wipe_tower_x', $new_pos_3f->x); $cfg->set('wipe_tower_y', $new_pos_3f->y); $self->GetFrame->{options_tabs}{print}->load_config($cfg); }); $self->{canvas3D}->set_on_model_update(sub { if (wxTheApp->{app_config}->get("background_processing")) { $self->schedule_background_process; } else { # Hide the print info box, it is no more valid. $self->{"print_info_box_show"}->(0); } }); $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 if ($Slic3r::GUI::have_OpenGL) { $self->{preview3D} = Slic3r::GUI::Plater::3DPreview->new($self->{preview_notebook}, $self->{print}, $self->{config}); $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 if ($Slic3r::GUI::have_OpenGL) { $self->{toolpaths2D} = Slic3r::GUI::Plater::2DToolpaths->new($self->{preview_notebook}, $self->{print}); $self->{preview_notebook}->AddPage($self->{toolpaths2D}, 'Layers'); } EVT_NOTEBOOK_PAGE_CHANGED($self, $self->{preview_notebook}, sub { my $preview = $self->{preview_notebook}->GetCurrentPage; $self->{preview3D}->load_print(1) if ($preview == $self->{preview3D}); $preview->OnActivate if $preview->can('OnActivate'); }); # 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_LAYER_EDITING, 'Layer Editing', Wx::Bitmap->new(Slic3r::var("variable_layer_height.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, 0, 'Layer Editing'); } else { my %tbar_buttons = ( add => "Add…", remove => "Delete", reset => "Delete All", arrange => "Arrange", increase => "", decrease => "", rotate45ccw => "", rotate45cw => "", changescale => "Scale…", split => "Split", cut => "Cut…", settings => "Settings…", layer_editing => "Layer editing", ); $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) { $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); $self->{btoolbar}->Add($self->{"btn_$_"}); } $self->{"btn_layer_editing"} = Wx::ToggleButton->new($self, -1, $tbar_buttons{'layer_editing'}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); $self->{btoolbar}->Add($self->{"btn_layer_editing"}); } $self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, wxDefaultSize, wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS ); $self->{list}->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 145); $self->{list}->InsertColumn(1, "Copies", wxLIST_FORMAT_CENTER, 45); $self->{list}->InsertColumn(2, "Scale", wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER); EVT_LIST_ITEM_SELECTED($self, $self->{list}, \&list_item_selected); EVT_LIST_ITEM_DESELECTED($self, $self->{list}, \&list_item_deselected); EVT_LIST_ITEM_ACTIVATED($self, $self->{list}, \&list_item_activated); EVT_KEY_DOWN($self->{list}, sub { my ($list, $event) = @_; if ($event->GetKeyCode == WXK_TAB) { $list->Navigate($event->ShiftDown ? &Wx::wxNavigateBackward : &Wx::wxNavigateForward); } else { $event->Skip; } }); # right pane buttons $self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, [-1, 30], wxBU_LEFT); $self->{btn_reslice} = Wx::Button->new($self, -1, "Slice now", 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; 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 reslice reslice.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 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_BUTTON($self, $self->{btn_send_gcode}, sub { $self->{send_gcode_file} = $self->export_gcode(Wx::StandardPaths::Get->GetTempDir()); }); EVT_BUTTON($self, $self->{btn_reslice}, \&reslice); 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, Z, 'relative') }); EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45, Z, 'relative') }); 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_SETTINGS, sub { $_[0]->object_settings_dialog }); EVT_TOOL($self, TB_LAYER_EDITING, sub { my $state = $self->{canvas3D}->layer_editing_enabled; $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, ! $state); $self->on_layer_editing_toggled(! $state); }); } 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, Z, 'relative') }); EVT_BUTTON($self, $self->{btn_rotate45ccw}, sub { $_[0]->rotate(45, Z, 'relative') }); 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_settings}, sub { $_[0]->object_settings_dialog }); EVT_TOGGLEBUTTON($self, $self->{btn_layer_editing}, sub { $self->on_layer_editing_toggled($self->{btn_layer_editing}->GetValue); }); } $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) for grep defined($_), $self, $self->{canvas}, $self->{canvas3D}, $self->{preview3D}, $self->{list}; 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); }); { 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); } $self->update; { my $presets; { $presets = $self->{presets_sizer} = Wx::FlexGridSizer->new(3, 2, 1, 2); $presets->AddGrowableCol(1, 1); $presets->SetFlexibleDirection(wxHORIZONTAL); my %group_labels = ( print => 'Print settings', filament => 'Filament', printer => 'Printer', ); # UI Combo boxes for a print, multiple filaments, and a printer. # Initially a single filament combo box is created, but the number of combo boxes for the filament selection may increase, # once a printer preset with multiple extruders is activated. # $self->{preset_choosers}{$group}[$idx] $self->{preset_choosers} = {}; for my $group (qw(print filament printer)) { my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); $text->SetFont($Slic3r::GUI::small_font); my $choice = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, wxDefaultSize, [], wxCB_READONLY); if ($group eq 'filament') { EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down(0, @_); } ); } $self->{preset_choosers}{$group} = [$choice]; # setup the listener EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { $self->_on_select_preset($group, $choice, 0); }); }); $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); $presets->Add($choice, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND | wxBOTTOM, 0); } } 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 $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 = ( 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, "Sliced Info"); $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_m => "Used Filament (m)", fil_mm3 => "Used Filament (mm^3)", fil_g => "Used Filament (g)", cost => "Cost", time => "Estimated printing time", ); 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); } } my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL); $self->{buttons_sizer} = $buttons_sizer; $buttons_sizer->AddStretchSpacer(1); $buttons_sizer->Add($self->{btn_export_stl}, 0, wxALIGN_RIGHT, 0); $buttons_sizer->Add($self->{btn_reslice}, 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); 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->{list}, 1, wxEXPAND, 5); $right_sizer->Add($object_info_sizer, 0, wxEXPAND, 0); $right_sizer->Add($print_info_sizer, 0, wxEXPAND, 0); # Callback for showing / hiding the print info box. $self->{"print_info_box_show"} = sub { if ($right_sizer->IsShown(4) != $_[0]) { $right_sizer->Show(4, $_[0]); $self->Layout } }; # Show the box initially, let it be shown after the slicing is finished. $self->{"print_info_box_show"}->(0); 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->update_ui_from_settings(); return $self; } # sets the callback sub on_select_preset { my ($self, $cb) = @_; $self->{on_select_preset} = $cb; } # Called from the platter combo boxes selecting the active print, filament or printer. sub _on_select_preset { my ($self, $group, $choice, $idx) = @_; # If user changed a filament preset and the selected machine is equipped with multiple extruders, # there are multiple filament selection combo boxes shown at the platter. In that case # don't propagate the filament selection changes to the tab. if ($group eq 'filament') { wxTheApp->{preset_bundle}->set_filament_preset($idx, $choice->GetStringSelection); } if ($group eq 'filament' && @{$self->{preset_choosers}{filament}} > 1) { # Only update the platter UI for the 2nd and other filaments. wxTheApp->{preset_bundle}->update_platter_filament_ui($idx, $choice); } else { # call GetSelection() in scalar context as it's context-aware $self->{on_select_preset}->($group, $choice->GetStringSelection) if $self->{on_select_preset}; } # Synchronize config.ini with the current selections. wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); # get new config and generate on_config_change() event for updating plater and other things $self->on_config_change(wxTheApp->{preset_bundle}->full_config); } sub on_layer_editing_toggled { my ($self, $new_state) = @_; $self->{canvas3D}->layer_editing_enabled($new_state); if ($new_state && ! $self->{canvas3D}->layer_editing_enabled) { # Initialization of the OpenGL shaders failed. Disable the tool. if ($self->{htoolbar}) { $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0); $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, 0); } else { $self->{"btn_layer_editing"}->Disable; $self->{"btn_layer_editing"}->SetValue(0); } } $self->{canvas3D}->Refresh; $self->{canvas3D}->Update; } sub GetFrame { my ($self) = @_; return &Wx::GetTopLevelParent($self); } # Called after the Preferences dialog is closed and the program settings are saved. # Update the UI based on the current preferences. sub update_ui_from_settings { my ($self) = @_; if (defined($self->{btn_reslice}) && $self->{buttons_sizer}->IsShown($self->{btn_reslice}) != (! wxTheApp->{app_config}->get("background_processing"))) { $self->{buttons_sizer}->Show($self->{btn_reslice}, ! wxTheApp->{app_config}->get("background_processing")); $self->{buttons_sizer}->Layout; } } # Update preset combo boxes (Print settings, Filament, Printer) from their respective tabs. # Called by # Slic3r::GUI::Tab::Print::_on_presets_changed # Slic3r::GUI::Tab::Filament::_on_presets_changed # Slic3r::GUI::Tab::Printer::_on_presets_changed # when the presets are loaded or the user selects another preset. # For Print settings and Printer, synchronize the selection index with their tabs. # For Filament, synchronize the selection index for a single extruder printer only, otherwise keep the selection. sub update_presets { # $group: one of qw(print filament printer) # $presets: PresetCollection my ($self, $group, $presets) = @_; my @choosers = @{$self->{preset_choosers}{$group}}; if ($group eq 'filament') { my $choice_idx = 0; if (int(@choosers) == 1) { # Single filament printer, synchronize the filament presets. wxTheApp->{preset_bundle}->set_filament_preset(0, wxTheApp->{preset_bundle}->filament->get_selected_preset->name); } foreach my $choice (@choosers) { wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice); $choice_idx += 1; } } elsif ($group eq 'print') { wxTheApp->{preset_bundle}->print->update_platter_ui($choosers[0]); } elsif ($group eq 'printer') { # Update the print choosers to only contain the compatible presets, update the dirty flags. wxTheApp->{preset_bundle}->print->update_platter_ui($self->{preset_choosers}{print}->[0]); # Update the printer choosers, update the dirty flags. wxTheApp->{preset_bundle}->printer->update_platter_ui($choosers[0]); # Update the filament choosers to only contain the compatible presets, update the color preview, # update the dirty flags. my $choice_idx = 0; foreach my $choice (@{$self->{preset_choosers}{filament}}) { wxTheApp->{preset_bundle}->update_platter_filament_ui($choice_idx, $choice); $choice_idx += 1; } } # Synchronize config.ini with the current selections. wxTheApp->{preset_bundle}->export_selections(wxTheApp->{app_config}); } sub add { my ($self) = @_; my @input_files = wxTheApp->open_model($self); $self->load_files(\@input_files); } sub load_files { my ($self, $input_files) = @_; return if ! defined($input_files) || ! scalar(@$input_files); my $nozzle_dmrs = $self->{config}->get('nozzle_diameter'); # One of the files is potentionally a bundle of files. Don't bundle them, but load them one by one. # Only bundle .stls or .objs if the printer has multiple extruders. my $one_by_one = (@$nozzle_dmrs <= 1) || (@$input_files == 1) || defined(first { $_ =~ /.[aA][mM][fF]$/ || $_ =~ /.3[mM][fF]$/ || $_ =~ /.[pP][rR][uI][sS][aA]$/ } @$input_files); my $process_dialog = Wx::ProgressDialog->new('Loading…', "Processing input file\n" . basename($input_files->[0]), 100, $self, 0); $process_dialog->Pulse; local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); # new_model to collect volumes, if all of them come from .stl or .obj and there is a chance, that they will be # possibly merged into a single multi-part object. my $new_model = $one_by_one ? undef : Slic3r::Model->new; # Object indices for the UI. my @obj_idx = (); # Collected file names to display the final message on the status bar. my @loaded_files = (); # For all input files. for (my $i = 0; $i < @$input_files; $i += 1) { my $input_file = $input_files->[$i]; $process_dialog->Update(100. * $i / @$input_files, "Processing input file\n" . basename($input_file)); my $model = eval { Slic3r::Model->read_from_file($input_file, 0) }; Slic3r::GUI::show_error($self, $@) if $@; next 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); $model->convert_multipart_object if $dialog->ShowModal() == wxID_YES; } if ($one_by_one) { push @obj_idx, $self->load_model_objects(@{$model->objects}); } else { # This must be an .stl or .obj file, which may contain a maximum of one volume. $new_model->add_object($_) for (@{$model->objects}); } } if ($new_model) { my $dialog = Wx::MessageDialog->new($self, "Multiple objects were loaded for a multi-material printer.\n" . "Instead of considering them as multiple objects, should I consider\n" . "these files to represent a single object having multiple parts?\n", 'Multi-part object detected', wxICON_WARNING | wxYES | wxNO); $new_model->convert_multipart_object if $dialog->ShowModal() == wxID_YES; push @obj_idx, $self->load_model_objects(@{$new_model->objects}); } # Note the current directory for the file open dialog. wxTheApp->{app_config}->update_skein_dir(dirname($input_files->[-1])); $process_dialog->Destroy; $self->statusbar->SetStatusText("Loaded " . join(',', @loaded_files)); return @obj_idx; } sub load_model_objects { my ($self, @model_objects) = @_; 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 $scaled_down = 0; my @obj_idx = (); foreach my $model_object (@model_objects) { my $o = $self->{model}->add_object($model_object); my $object_name = $model_object->name; $object_name = basename($model_object->input_file) if ($object_name eq ''); push @{ $self->{objects} }, Slic3r::GUI::Plater::Object->new(name => $object_name); push @obj_idx, $#{ $self->{objects} }; if ($model_object->instances_count == 0) { # if object has no defined position(s) we need to rearrange everything after loading $need_arrange = 1; # add a default instance and center object around origin $o->center_around_origin; # also aligns object to Z = 0 $o->add_instance(offset => $bed_centerf); } { # if 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}; $scaled_down = 1; } } $self->{print}->auto_assign_extruders($o); $self->{print}->add_model_object($o); } # if user turned autocentering off, automatic arranging would disappoint them if (! wxTheApp->{app_config}->get("autocenter")) { $need_arrange = 0; } if ($scaled_down) { Slic3r::GUI::show_info( $self, 'Your object appears to be too large, so it was automatically scaled down to fit your print bed.', 'Object too large?', ); } foreach my $obj_idx (@obj_idx) { my $object = $self->{objects}[$obj_idx]; my $model_object = $self->{model}->objects->[$obj_idx]; $self->{list}->InsertStringItem($obj_idx, $object->name); $self->{list}->SetItemFont($obj_idx, Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)) if $self->{list}->can('SetItemFont'); # legacy code for wxPerl < 0.9918 not supporting SetItemFont() $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); $self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%"); $self->reset_thumbnail($obj_idx); } $self->arrange if $need_arrange; $self->update; # zoom to objects $self->{canvas3D}->zoom_to_volumes if $self->{canvas3D}; $self->{list}->Update; $self->{list}->Select($obj_idx[-1], 1); $self->object_list_changed; $self->schedule_background_process; 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) = @_; $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; return if ! defined $obj_idx; } splice @{$self->{objects}}, $obj_idx, 1; $self->{model}->delete_object($obj_idx); $self->{print}->delete_object($obj_idx); $self->{list}->DeleteItem($obj_idx); $self->object_list_changed; $self->select_object(undef); $self->update; $self->schedule_background_process; } sub reset { my $self = shift; $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}; @{$self->{objects}} = (); $self->{model}->clear_objects; $self->{print}->clear_objects; $self->{list}->DeleteAllItems; $self->object_list_changed; $self->select_object(undef); $self->update; } sub increase { my ($self, $copies) = @_; $copies //= 1; my ($obj_idx, $object) = $self->selected_object; return if ! defined $obj_idx; 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}), scaling_factor => $instance->scaling_factor, rotation => $instance->rotation, ); $self->{print}->objects->[$obj_idx]->add_copy($instance->offset); } $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); # only autoarrange if user has autocentering enabled $self->stop_background_process; if (wxTheApp->{app_config}->get("autocenter")) { $self->arrange; } else { $self->update; } $self->schedule_background_process; } sub decrease { my ($self, $copies_asked) = @_; my $copies = $copies_asked // 1; my ($obj_idx, $object) = $self->selected_object; return if ! defined $obj_idx; $self->stop_background_process; 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; } $self->{list}->SetItem($obj_idx, 1, $model_object->instances_count); } elsif (defined $copies_asked) { # The "decrease" came from the "set number of copies" dialog. $self->remove; } else { # The "decrease" came from the "-" button. Don't allow the object to disappear. $self->resume_background_process; return; } if ($self->{objects}[$obj_idx]) { $self->{list}->Select($obj_idx, 0); $self->{list}->Select($obj_idx, 1); } $self->update; $self->schedule_background_process; } 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); 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 _get_number_from_user { my ($self, $title, $prompt_message, $error_message, $default, $only_positive) = @_; for (;;) { my $value = Wx::GetTextFromUser($prompt_message, $title, $default, $self); # Accept both dashes and dots as a decimal separator. $value =~ s/,/./; # If scaling value is being entered, remove the trailing percent sign. $value =~ s/%$// if $only_positive; # User canceled the selection, return undef. return if $value eq ''; # Validate a numeric value. return $value if ($value =~ /^-?\d*(?:\.\d*)?$/) && (! $only_positive || $value > 0); Wx::MessageBox( $error_message . (($only_positive && $value <= 0) ? ": $value\nNon-positive value." : ": $value\nNot a numeric value."), "Slic3r Error", wxOK | wxICON_EXCLAMATION, $self); $default = $value; } } sub rotate { my ($self, $angle, $axis, $relative_key) = @_; $relative_key //= 'absolute'; # relative or absolute coordinates $axis //= Z; # angle is in degrees my $relative = $relative_key eq 'relative'; 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]; if (!defined $angle) { my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z'; my $default = $axis == Z ? rad2deg($model_instance->rotation) : 0; $angle = $self->_get_number_from_user("Enter the rotation angle:", "Rotate around $axis_name axis", "Invalid rotation angle entered", $default); return if $angle eq ''; } $self->stop_background_process; if ($axis == Z) { my $new_angle = deg2rad($angle); $_->set_rotation(($relative ? $_->rotation : 0.) + $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 if ($model_instance->rotation != 0) { $model_object->rotate($model_instance->rotation, Z); $_->set_rotation(0) for @{ $model_object->instances }; } $model_object->rotate(deg2rad($angle), $axis); # realign object to Z = 0 $model_object->center_around_origin; $self->reset_thumbnail($obj_idx); } # update print and start background processing $self->{print}->add_model_object($model_object, $obj_idx); $self->selection_changed; # refresh info (size etc.) $self->update; $self->schedule_background_process; } sub mirror { my ($self, $axis) = @_; 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 if ($model_instance->rotation != 0) { $model_object->rotate($model_instance->rotation, Z); $_->set_rotation(0) for @{ $model_object->instances }; } $model_object->mirror($axis); # realign object to Z = 0 $model_object->center_around_origin; $self->reset_thumbnail($obj_idx); # update print and start background processing $self->stop_background_process; $self->{print}->add_model_object($model_object, $obj_idx); $self->selection_changed; # refresh info (size etc.) $self->update; $self->schedule_background_process; } sub changescale { my ($self, $axis, $tosize) = @_; 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]; my $object_size = $model_object->bounding_box->size; my $bed_size = Slic3r::Polygon->new_scale(@{$self->{config}->bed_shape})->bounding_box->size; if (defined $axis) { my $axis_name = $axis == X ? 'X' : $axis == Y ? 'Y' : 'Z'; my $scale; if ($tosize) { my $cursize = $object_size->[$axis]; my $newsize = $self->_get_number_from_user( sprintf('Enter the new size for the selected object (print bed: %smm):', unscale($bed_size->[$axis])), "Scale along $axis_name", 'Invalid scaling value entered', $cursize, 1); return if $newsize eq ''; $scale = $newsize / $cursize * 100; } else { $scale = $self->_get_number_from_user('Enter the scale % for the selected object:', "Scale along $axis_name", 'Invalid scaling value entered', 100, 1); return if $scale eq ''; } # apply Z rotation before scaling if ($model_instance->rotation != 0) { $model_object->rotate($model_instance->rotation, Z); $_->set_rotation(0) for @{ $model_object->instances }; } my $versor = [1,1,1]; $versor->[$axis] = $scale/100; $model_object->scale_xyz(Slic3r::Pointf3->new(@$versor)); #FIXME Scale the layer height profile when $axis == Z? #FIXME Scale the layer height ranges $axis == Z? # object was already aligned to Z = 0, so no need to realign it $self->reset_thumbnail($obj_idx); } else { my $scale; if ($tosize) { my $cursize = max(@$object_size); my $newsize = $self->_get_number_from_user('Enter the new max size for the selected object:', 'Scale', 'Invalid scaling value entered', $cursize, 1); return if ! defined($newsize) || $newsize eq ''; $scale = $model_instance->scaling_factor * $newsize / $cursize * 100; } else { # max scale factor should be above 2540 to allow importing files exported in inches $scale = $self->_get_number_from_user('Enter the scale % for the selected object:', 'Scale', 'Invalid scaling value entered', $model_instance->scaling_factor*100, 1); return if ! defined($scale) || $scale eq ''; } $self->{list}->SetItem($obj_idx, 2, "$scale%"); $scale /= 100; # turn percent into factor my $variation = $scale / $model_instance->scaling_factor; #FIXME Scale the layer height profile? 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); } # 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->update; $self->schedule_background_process; } 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(wxTheApp->{preset_bundle}->full_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 # Force auto center of the aligned grid of of objects on the print bed. $self->update(1); } sub split_object { my $self = shift; 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; 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; } $_->center_around_origin for (@model_objects); $self->remove($obj_idx); $current_object = $obj_idx = undef; # 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); } sub schedule_background_process { my ($self) = @_; 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(wxTheApp->{preset_bundle}->full_config); # Just redraw the 3D canvas without reloading the scene. # $self->{canvas3D}->Refresh if ($invalidated && $self->{canvas3D}->layer_editing_enabled); $self->{canvas3D}->Refresh if ($self->{canvas3D}->layer_editing_enabled); # Hide the slicing results if the current slicing status is no more valid. $self->{"print_info_box_show"}->(0) if $invalidated; if (wxTheApp->{app_config}->get("background_processing")) { if ($invalidated) { # kill current thread if any $self->stop_background_process; } else { $self->resume_background_process; } # schedule a new process thread in case it wasn't running $self->start_background_process; } # Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared. # Otherwise they will be just refreshed. if ($invalidated) { $self->{toolpaths2D}->reload_print if $self->{toolpaths2D}; $self->{preview3D}->reload_print if $self->{preview3D}; } } sub start_background_process { my ($self) = @_; return if !@{$self->{objects}}; return if $self->{process_thread}; # 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 wxTheApp->{preset_bundle}->full_config->validate; $self->{print}->validate; }; if ($@) { $self->statusbar->SetStatusText($@); return; } # Copy the names of active presets into the placeholder parser. wxTheApp->{preset_bundle}->export_selections_pp($self->{print}->placeholder_parser); # 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}; 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 1; } return 0; } sub resume_background_process { my ($self) = @_; if ($self->{process_thread} || $self->{export_thread}) { Slic3r::resume_all_threads(); } } sub reslice { # explicitly cancel a previous thread and start a new one. my ($self) = @_; # Don't reslice if export of G-code or sending to OctoPrint is running. if (! defined($self->{export_gcode_output_file}) && ! defined($self->{send_gcode_file})) { # Stop the background processing threads, stop the async update timer. $self->stop_background_process; # Rather perform one additional unnecessary update of the print object instead of skipping a pending async update. $self->async_apply_config; $self->statusbar->SetCancelCallback(sub { $self->stop_background_process; $self->statusbar->SetStatusText("Slicing cancelled"); # this updates buttons status $self->object_list_changed; }); $self->start_background_process; } } 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 wxTheApp->{preset_bundle}->full_config->validate; $self->{print}->validate; }; Slic3r::GUI::catch_error($self) and return; # apply config and validate print my $config = wxTheApp->{preset_bundle}->full_config; eval { # this will throw errors if config is not valid $config->validate; $self->{print}->apply_config($config); $self->{print}->validate; }; Slic3r::GUI::catch_error($self) and return; # select output file if ($output_file) { $self->{export_gcode_output_file} = eval { $self->{print}->output_filepath($output_file) }; Slic3r::GUI::catch_error($self) and return; } else { my $default_output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; Slic3r::GUI::catch_error($self) and return; my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', wxTheApp->{app_config}->get_last_output_dir(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 = $dlg->GetPath; wxTheApp->{app_config}->update_last_output_dir(dirname($path)); $self->{export_gcode_output_file} = $path; $dlg->Destroy; } $self->statusbar->StartBusy; $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; # this updates buttons status $self->object_list_changed; 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; # 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}; # 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…"); } # Called when the G-code export finishes, either successfully or with an error. # 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) { # G-code file exported successfully. 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 OctoPrint 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; # Send $self->{send_gcode_file} to OctoPrint. $self->send_gcode if $send_gcode; $self->{print_file} = undef; $self->{send_gcode_file} = undef; $self->{"print_info_cost"}->SetLabel(sprintf("%.2f" , $self->{print}->total_cost)); $self->{"print_info_fil_g"}->SetLabel(sprintf("%.2f" , $self->{print}->total_weight)); $self->{"print_info_fil_mm3"}->SetLabel(sprintf("%.2f" , $self->{print}->total_extruded_volume)); $self->{"print_info_time"}->SetLabel($self->{print}->estimated_print_time); $self->{"print_info_fil_m"}->SetLabel(sprintf("%.2f" , $self->{print}->total_used_filament / 1000)); $self->{"print_info_box_show"}->(1); # this updates buttons status $self->object_list_changed; } sub do_print { my ($self) = @_; my $controller = $self->GetFrame->{controller}; my $printer_preset = wxTheApp->{preset_bundle}->printer->get_edited_preset; my $printer_panel = $controller->add_printer($printer_preset->name, $printer_preset->config); my $filament_stats = $self->{print}->filament_stats; my $filament_names = wxTheApp->{preset_bundle}->filament_presets; $filament_stats = { map { $filament_names->[$_] => $filament_stats->{$_} } keys %$filament_stats }; $printer_panel->load_print_job($self->{print_file}, $filament_stats); $self->GetFrame->select_tab(1); } # Send $self->{send_gcode_file} to OctoPrint. #FIXME Currently this call blocks the UI. Make it asynchronous. sub send_gcode { my ($self) = @_; $self->statusbar->StartBusy; my $ua = LWP::UserAgent->new; $ua->timeout(180); my $res = $ua->post( "http://" . $self->{config}->octoprint_host . "/api/files/local", Content_Type => 'form-data', 'X-Api-Key' => $self->{config}->octoprint_apikey, Content => [ file => [ # On Windows, the path has to be encoded in local code page for perl to be able to open it. Slic3r::encode_path($self->{send_gcode_file}), # Remove the UTF-8 flag from the perl string, so the LWP::UserAgent can insert # the UTF-8 encoded string into the request as a byte stream. Slic3r::path_to_filename_raw($self->{send_gcode_file}) ], print => $self->{send_gcode_file_print} ? 1 : 0, ], ); $self->statusbar->StopBusy; if ($res->is_success) { $self->statusbar->SetStatusText("G-code file successfully uploaded to the OctoPrint server"); } else { my $message = "Error while uploading to the OctoPrint server: " . $res->status_line; Slic3r::GUI::show_error($self, $message); $self->statusbar->SetStatusText($message); } } sub export_stl { my ($self) = @_; return if !@{$self->{objects}}; # Ask user for a file name to write into. my $output_file = $self->_get_export_file('STL') or return; # Store a binary STL. $self->{model}->store_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; my $model_object = $self->{model}->objects->[$obj_idx]; #FIXME convert to local file encoding return if !$model_object->input_file || !-e $model_object->input_file; my @new_obj_idx = $self->load_files([$model_object->input_file]); return if !@new_obj_idx; foreach my $new_obj_idx (@new_obj_idx) { my $o = $self->{model}->objects->[$new_obj_idx]; $o->clear_instances; $o->add_instance($_) for @{$model_object->instances}; #$o->invalidate_bounding_box; if ($o->volumes_count == $model_object->volumes_count) { for my $i (0..($o->volumes_count-1)) { $o->get_volume($i)->config->apply($model_object->get_volume($i)->config); } } #FIXME restore volumes and their configs, layer_height_ranges, layer_height_profile, layer_height_profile_valid, } $self->remove($obj_idx); } sub export_object_stl { my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; return if !defined $obj_idx; my $model_object = $self->{model}->objects->[$obj_idx]; # Ask user for a file name to write into. 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"); } sub export_amf { my ($self) = @_; return if !@{$self->{objects}}; # Ask user for a file name to write into. my $output_file = $self->_get_export_file('AMF') or return; $self->{model}->store_amf($output_file); $self->statusbar->SetStatusText("AMF file exported to $output_file"); } # Ask user to select an output file for a given file format (STl, AMF, 3MF). # Propose a default file name based on the 'output_filename_format' configuration value. sub _get_export_file { my ($self, $format) = @_; my $suffix = $format eq 'STL' ? '.stl' : '.amf.xml'; my $output_file = eval { $self->{print}->output_filepath($main::opt{output} // '') }; Slic3r::GUI::catch_error($self) and return undef; $output_file =~ s/\.[gG][cC][oO][dD][eE]$/$suffix/; my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file), basename($output_file), &Slic3r::GUI::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if ($dlg->ShowModal != wxID_OK) { $dlg->Destroy; return undef; } $output_file = $dlg->GetPath; $dlg->Destroy; return $output_file; } sub reset_thumbnail { my ($self, $obj_idx) = @_; $self->{objects}[$obj_idx]->thumbnail(undef); } # 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 update { my ($self, $force_autocenter) = @_; if (wxTheApp->{app_config}->get("autocenter") || $force_autocenter) { $self->{model}->center_instances_around_point($self->bed_centerf); } my $running = $self->pause_background_process; my $invalidated = $self->{print}->reload_model_instances(); # 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. if ($invalidated || !$running) { $self->schedule_background_process; } else { $self->resume_background_process; } $self->{canvas}->reload_scene if $self->{canvas}; $self->{canvas3D}->reload_scene if $self->{canvas3D}; $self->{preview3D}->reload_print if $self->{preview3D}; } # When a number of extruders changes, the UI needs to be updated to show a single filament selection combo box per extruder. # Also the wxTheApp->{preset_bundle}->filament_presets needs to be resized accordingly # and some reasonable default has to be selected for the additional extruders. sub on_extruders_change { my ($self, $num_extruders) = @_; my $choices = $self->{preset_choosers}{filament}; while (int(@$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); my $extruder_idx = scalar @$choices; EVT_LEFT_DOWN($choice, sub { $self->filament_color_box_lmouse_down($extruder_idx, @_); } ); push @$choices, $choice; # copy icons from first choice $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; # insert new choice into sizer $self->{presets_sizer}->Insert(4 + ($#$choices-1)*2, 0, 0); $self->{presets_sizer}->Insert(5 + ($#$choices-1)*2, $choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); # setup the listener EVT_COMBOBOX($choice, $choice, sub { my ($choice) = @_; wxTheApp->CallAfter(sub { $self->_on_select_preset('filament', $choice, $extruder_idx); }); }); # initialize selection wxTheApp->{preset_bundle}->update_platter_filament_ui($extruder_idx, $choice); } # remove unused choices if any while (@$choices > $num_extruders) { $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # label $self->{presets_sizer}->Remove(4 + ($#$choices-1)*2); # wxChoice $choices->[-1]->Destroy; pop @$choices; } $self->Layout; } sub on_config_change { my ($self, $config) = @_; my $update_scheduled; foreach my $opt_key (@{$self->{config}->diff($config)}) { $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}; $update_scheduled = 1; } elsif ($opt_key =~ '^wipe_tower' || $opt_key eq 'single_extruder_multi_material') { $update_scheduled = 1; } elsif ($opt_key eq 'serial_port') { $self->{btn_print}->Show($config->get('serial_port')); $self->Layout; } elsif ($opt_key eq 'octoprint_host') { $self->{btn_send_gcode}->Show($config->get('octoprint_host')); $self->Layout; } elsif ($opt_key eq 'variable_layer_height') { if ($config->get('variable_layer_height') != 1) { if ($self->{htoolbar}) { $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0); $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, 0); } else { $self->{"btn_layer_editing"}->Disable; $self->{"btn_layer_editing"}->SetValue(0); } $self->{canvas3D}->layer_editing_enabled(0); $self->{canvas3D}->Refresh; $self->{canvas3D}->Update; } elsif ($self->{canvas3D}->layer_editing_allowed) { # Want to allow the layer editing, but do it only if the OpenGL supports it. if ($self->{htoolbar}) { $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 1); } else { $self->{"btn_layer_editing"}->Enable; } } } elsif ($opt_key eq 'extruder_colour') { $update_scheduled = 1; my $extruder_colors = $config->get('extruder_colour'); $self->{preview3D}->set_number_extruders(scalar(@{$extruder_colors})); } } $self->update if $update_scheduled; return if !$self->GetFrame->is_loaded; # (re)start timer $self->schedule_background_process; } sub list_item_deselected { my ($self, $event) = @_; return if $PreventListEvents; if ($self->{list}->GetFirstSelected == -1) { $self->select_object(undef); $self->{canvas}->Refresh; #FIXME VBOs are being refreshed just to change a selection color? $self->{canvas3D}->reload_scene if $self->{canvas3D}; } } sub list_item_selected { my ($self, $event) = @_; return if $PreventListEvents; my $obj_idx = $event->GetIndex; $self->select_object($obj_idx); $self->{canvas}->Refresh; #FIXME VBOs are being refreshed just to change a selection color? $self->{canvas3D}->reload_scene if $self->{canvas3D}; } sub list_item_activated { my ($self, $event, $obj_idx) = @_; $obj_idx //= $event->GetIndex; $self->object_settings_dialog($obj_idx); } # Called when clicked on the filament preset combo box. # When clicked on the icon, show the color picker. sub filament_color_box_lmouse_down { my ($self, $extruder_idx, $combobox, $event) = @_; my $pos = $event->GetLogicalPosition(Wx::ClientDC->new($combobox)); my( $x, $y ) = ( $pos->x, $pos->y ); if ($x > 24) { # Let the combo box process the mouse click. $event->Skip; } else { # Swallow the mouse click and open the color picker. my $data = Wx::ColourData->new; $data->SetChooseFull(1); my $dialog = Wx::ColourDialog->new($self->GetFrame, $data); if ($dialog->ShowModal == wxID_OK) { my $cfg = Slic3r::Config->new; my $colors = wxTheApp->{preset_bundle}->full_config->get('extruder_colour'); $colors->[$extruder_idx] = $dialog->GetColourData->GetColour->GetAsString(wxC2S_HTML_SYNTAX); $cfg->set('extruder_colour', $colors); $self->GetFrame->{options_tabs}{printer}->load_config($cfg); wxTheApp->{preset_bundle}->update_platter_filament_ui($extruder_idx, $combobox); } $dialog->Destroy(); } } sub object_cut_dialog { my ($self, $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) { $self->remove($obj_idx); $self->load_model_objects(grep defined($_), @new_objects); $self->arrange; } } sub object_settings_dialog { my ($self, $obj_idx) = @_; ($obj_idx, undef) = $self->selected_object if !defined $obj_idx; 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 eval { wxTheApp->{preset_bundle}->full_config->validate; }; return if Slic3r::GUI::catch_error($_[0]); my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog->new($self, object => $self->{objects}[$obj_idx], model_object => $model_object, config => wxTheApp->{preset_bundle}->full_config, ); $self->pause_background_process; $dlg->ShowModal; # update thumbnail since parts may have changed if ($dlg->PartsChanged) { # recenter and re-align to Z = 0 $model_object->center_around_origin; $self->reset_thumbnail($obj_idx); } # update print if ($dlg->PartsChanged || $dlg->PartSettingsChanged) { $self->stop_background_process; $self->{print}->reload_object($obj_idx); $self->schedule_background_process; $self->{canvas}->reload_scene if $self->{canvas}; $self->{canvas3D}->reload_scene if $self->{canvas3D}; } else { $self->resume_background_process; } } # Called to update various buttons depending on whether there are any objects or # whether background processing (export of a G-code, sending to Octoprint, forced background re-slicing) is active. sub object_list_changed { my $self = shift; # Enable/disable buttons depending on whether there are any objects on the platter. my $have_objects = @{$self->{objects}} ? 1 : 0; my $variable_layer_height_allowed = $self->{config}->variable_layer_height && $self->{canvas3D}->layer_editing_allowed; if ($self->{htoolbar}) { # On OSX or Linux $self->{htoolbar}->EnableTool($_, $have_objects) for (TB_RESET, TB_ARRANGE, TB_LAYER_EDITING); $self->{htoolbar}->EnableTool(TB_LAYER_EDITING, 0) if (! $variable_layer_height_allowed); } else { # On MSW my $method = $have_objects ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode layer_editing); $self->{"btn_layer_editing"}->Disable if (! $variable_layer_height_allowed); } my $export_in_progress = $self->{export_gcode_output_file} || $self->{send_gcode_file}; my $method = ($have_objects && ! $export_in_progress) ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method for grep $self->{"btn_$_"}, qw(reslice export_gcode print send_gcode); } # Selection of an active 3D object changed. sub selection_changed { my ($self) = @_; my ($obj_idx, $object) = $self->selected_object; my $have_sel = defined $obj_idx; if ($self->{htoolbar}) { # On OSX or Linux $self->{htoolbar}->EnableTool($_, $have_sel) for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_SCALE, TB_SPLIT, TB_CUT, TB_SETTINGS); } else { # On MSW my $method = $have_sel ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw changescale split cut settings); } if ($self->{object_info_size}) { # have we already loaded the info pane? if ($have_sel) { my $model_object = $self->{model}->objects->[$obj_idx]; #FIXME print_info runs model fixing in two rounds, it is very slow, it should not be performed here! # $model_object->print_info; my $model_instance = $model_object->instances->[0]; $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size})); $self->{object_info_materials}->SetLabel($model_object->materials_count); if (my $stats = $model_object->mesh_stats) { $self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{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_$_"}->SetLabel("") for qw(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} }; if (defined $obj_idx) { $self->{objects}->[$obj_idx]->selected(1); # We use this flag to avoid circular event handling # Select() happens to fire a wxEVT_LIST_ITEM_SELECTED on Windows, # whose event handler calls this method again and again and again $PreventListEvents = 1; $self->{list}->Select($obj_idx, 1); $PreventListEvents = 0; } else { # TODO: deselect all in list } $self->selection_changed(1); } sub selected_object { my ($self) = @_; my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} }; return defined $obj_idx ? ($obj_idx, $self->{objects}[$obj_idx]) : undef; } sub statusbar { return $_[0]->GetFrame->{statusbar}; } sub object_menu { my ($self) = @_; my $frame = $self->GetFrame; my $menu = Wx::Menu->new; my $accel = ($^O eq 'MSWin32') ? sub { $_[0] . "\t\xA0" . $_[1] } : sub { $_[0] }; $frame->_append_menu_item($menu, $accel->('Delete', 'Del'), 'Remove the selected object', sub { $self->remove; }, undef, 'brick_delete.png'); $frame->_append_menu_item($menu, $accel->('Increase copies', '+'), 'Place one more copy of the selected object', sub { $self->increase; }, undef, 'add.png'); $frame->_append_menu_item($menu, $accel->('Decrease copies', '-'), 'Remove one copy of the selected object', sub { $self->decrease; }, undef, 'delete.png'); $frame->_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(); $frame->_append_menu_item($menu, $accel->('Rotate 45° clockwise', 'l'), 'Rotate the selected object by 45° clockwise', sub { $self->rotate(-45, Z, 'relative'); }, undef, 'arrow_rotate_clockwise.png'); $frame->_append_menu_item($menu, $accel->('Rotate 45° counter-clockwise', 'r'), 'Rotate the selected object by 45° counter-clockwise', sub { $self->rotate(+45, Z, 'relative'); }, undef, 'arrow_rotate_anticlockwise.png'); my $rotateMenu = Wx::Menu->new; my $rotateMenuItem = $menu->AppendSubMenu($rotateMenu, "Rotate", 'Rotate the selected object by an arbitrary angle'); $frame->_set_menu_item_icon($rotateMenuItem, 'textfield.png'); $frame->_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'); $frame->_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'); $frame->_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'); my $mirrorMenu = Wx::Menu->new; my $mirrorMenuItem = $menu->AppendSubMenu($mirrorMenu, "Mirror", 'Mirror the selected object'); $frame->_set_menu_item_icon($mirrorMenuItem, 'shape_flip_horizontal.png'); $frame->_append_menu_item($mirrorMenu, "Along X axis…", 'Mirror the selected object along the X axis', sub { $self->mirror(X); }, undef, 'bullet_red.png'); $frame->_append_menu_item($mirrorMenu, "Along Y axis…", 'Mirror the selected object along the Y axis', sub { $self->mirror(Y); }, undef, 'bullet_green.png'); $frame->_append_menu_item($mirrorMenu, "Along Z axis…", 'Mirror the selected object along the Z axis', sub { $self->mirror(Z); }, undef, 'bullet_blue.png'); my $scaleMenu = Wx::Menu->new; my $scaleMenuItem = $menu->AppendSubMenu($scaleMenu, "Scale", 'Scale the selected object along a single axis'); $frame->_set_menu_item_icon($scaleMenuItem, 'arrow_out.png'); $frame->_append_menu_item($scaleMenu, $accel->('Uniformly…', 's'), 'Scale the selected object along the XYZ axes', sub { $self->changescale(undef); }); $frame->_append_menu_item($scaleMenu, "Along X axis…", 'Scale the selected object along the X axis', sub { $self->changescale(X); }, undef, 'bullet_red.png'); $frame->_append_menu_item($scaleMenu, "Along Y axis…", 'Scale the selected object along the Y axis', sub { $self->changescale(Y); }, undef, 'bullet_green.png'); $frame->_append_menu_item($scaleMenu, "Along Z axis…", 'Scale the selected object along the Z axis', sub { $self->changescale(Z); }, undef, 'bullet_blue.png'); my $scaleToSizeMenu = Wx::Menu->new; my $scaleToSizeMenuItem = $menu->AppendSubMenu($scaleToSizeMenu, "Scale to size", 'Scale the selected object along a single axis'); $frame->_set_menu_item_icon($scaleToSizeMenuItem, 'arrow_out.png'); $frame->_append_menu_item($scaleToSizeMenu, "Uniformly…", 'Scale the selected object along the XYZ axes', sub { $self->changescale(undef, 1); }); $frame->_append_menu_item($scaleToSizeMenu, "Along X axis…", 'Scale the selected object along the X axis', sub { $self->changescale(X, 1); }, undef, 'bullet_red.png'); $frame->_append_menu_item($scaleToSizeMenu, "Along Y axis…", 'Scale the selected object along the Y axis', sub { $self->changescale(Y, 1); }, undef, 'bullet_green.png'); $frame->_append_menu_item($scaleToSizeMenu, "Along Z axis…", 'Scale the selected object along the Z axis', sub { $self->changescale(Z, 1); }, undef, 'bullet_blue.png'); $frame->_append_menu_item($menu, "Split", 'Split the selected object into individual parts', sub { $self->split_object; }, undef, 'shape_ungroup.png'); $frame->_append_menu_item($menu, "Cut…", 'Open the 3D cutting tool', sub { $self->object_cut_dialog; }, undef, 'package.png'); $menu->AppendSeparator(); $frame->_append_menu_item($menu, "Settings…", 'Open the object editor dialog', sub { $self->object_settings_dialog; }, undef, 'cog.png'); $menu->AppendSeparator(); $frame->_append_menu_item($menu, "Reload from Disk", 'Reload the selected file from Disk', sub { $self->reload_from_disk; }, undef, 'arrow_refresh.png'); $frame->_append_menu_item($menu, "Export object as STL…", 'Export this single object as STL file', sub { $self->export_object_stl; }, 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}); } } package Slic3r::GUI::Plater::DropTarget; use Wx::DND; use base 'Wx::FileDropTarget'; sub new { my ($class, $window) = @_; my $self = $class->SUPER::new; $self->{window} = $window; return $self; } sub OnDropFiles { my ($self, $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 !/\.(?:[sS][tT][lL]|[oO][bB][jJ]|[aA][mM][fF](?:\.[xX][mM][lL])?|[pP][rR][uU][sS][aA])$/, @$filenames; $self->{window}->load_files($filenames); } # 2D preview of an object. Each object is previewed by its convex hull. package Slic3r::GUI::Plater::Object; use Moo; has 'name' => (is => 'rw', required => 1); 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 }); sub make_thumbnail { my ($self, $model, $obj_idx) = @_; # make method idempotent $self->thumbnail->clear; # raw_mesh is the non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. my $mesh = $model->objects->[$obj_idx]->raw_mesh; #FIXME The "correct" variant could be extremely slow. # 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); } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/Plater/000077500000000000000000000000001324354444700203035ustar00rootroot00000000000000Slic3r-version_1.39.1/lib/Slic3r/GUI/Plater/2D.pm000066400000000000000000000352141324354444700211130ustar00rootroot00000000000000# 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(wxTheApp :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'; sub new { my $class = shift; my ($parent, $size, $objects, $model, $config) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $size, wxTAB_TRAVERSAL); # This has only effect on MacOS. On Windows and Linux/GTK, the background is painted by $self->repaint(). $self->SetBackgroundColour(Wx::wxWHITE); $self->{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(210,210,210), wxSOLID); $self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(255,128,128), wxSOLID); $self->{dragged_brush} = Wx::Brush->new(Wx::Colour->new(128,128,255), wxSOLID); $self->{transparent_brush} = Wx::Brush->new(Wx::Colour->new(0,0,0), wxTRANSPARENT); $self->{grid_pen} = Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID); $self->{print_center_pen} = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID); $self->{clearance_pen} = Wx::Pen->new(Wx::Colour->new(0,0,200), 1, wxSOLID); $self->{skirt_pen} = Wx::Pen->new(Wx::Colour->new(150,150,150), 1, wxSOLID); $self->{user_drawn_background} = $^O ne 'darwin'; 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; }); 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) = @_; 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::wxWHITE, wxSOLID); $dc->SetPen(wxWHITE_PEN); $dc->SetBrush($brush_background); my $rect = $self->GetUpdateRegion()->GetBox(); $dc->DrawRectangle($rect->GetLeft(), $rect->GetTop(), $rect->GetWidth(), $rect->GetHeight()); } # draw grid $dc->SetPen($self->{grid_pen}); $dc->DrawLine(map @$_, @$_) for @{$self->{grid}}; # draw bed { $dc->SetPen($self->{print_center_pen}); $dc->SetBrush($self->{transparent_brush}); $dc->DrawPolygon($self->scaled_points_to_pixel($self->{bed_polygon}, 1), 0, 0); } # draw print center if (@{$self->{objects}} && wxTheApp->{app_config}->get("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(wxBLACK_PEN); $dc->SetBrush($self->{transparent_brush}); $dc->DrawRectangle(0, 0, @size); } # draw text if plate is empty if (!@{$self->{objects}}) { $dc->SetTextForeground(Wx::Colour->new(150,50,50)); $dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL)); $dc->DrawLabel( 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', Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL); } # draw thumbnails $dc->SetPen(wxBLACK_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) { $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), 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), 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) { $self->{on_select_object}->(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 ]; } 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]), )); $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 update_bed_size { my ($self) = @_; # 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}, ); } sub reload_scene { my ($self, $force) = @_; if (! $self->IsShown && ! $force) { $self->{reload_delayed} = 1; return; } $self->{reload_delayed} = 0; foreach my $obj_idx (0..$#{$self->{model}->objects}) { my $plater_object = $self->{objects}[$obj_idx]; next if $plater_object->thumbnail; # The thumbnail is not valid, update it with a convex hull of an object. $plater_object->thumbnail(Slic3r::ExPolygon::Collection->new); $plater_object->make_thumbnail($self->{model}, $obj_idx); $plater_object->transform_thumbnail($self->{model}, $obj_idx); } $self->Refresh; } # Called by the Platter wxNotebook when this page is activated. sub OnActivate { my ($self) = @_; $self->reload_scene(1) if ($self->{reload_delayed}); } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/Plater/2DToolpaths.pm000066400000000000000000000736731324354444700230240ustar00rootroot00000000000000# 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 :keycode wxWHITE wxWANTS_CHARS); use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN); use base qw(Wx::Panel Class::Accessor); __PACKAGE__->mk_accessors(qw(print enabled)); sub new { my $class = shift; my ($parent, $print) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS); $self->SetBackgroundColour(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) = @_; if ($event->HasModifiers) { $event->Skip; } else { my $key = $event->GetKeyCode; if ($key == ord('D') || $key == WXK_LEFT) { # Keys: 'D' or WXK_LEFT $slider->SetValue($slider->GetValue - 1); $self->set_z($self->{layers_z}[$slider->GetValue]); } elsif ($key == ord('U') || $key == WXK_RIGHT) { # Keys: 'U' or WXK_RIGHT $slider->SetValue($slider->GetValue + 1); $self->set_z($self->{layers_z}[$slider->GetValue]); } elsif ($key >= ord('1') && $key <= ord('3')) { # Keys: '1' to '3' $canvas->set_simulation_mode($key - ord('1')); } 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 epsilon X Y); use Slic3r::Print::State ':steps'; __PACKAGE__->mk_accessors(qw( print z layers color init bb _camera_bb _dirty _zoom _camera_target _drag_start_xy _texture_name _texture_size _extrusion_simulator _simulation_mode )); # make OpenGL::Array thread-safe { no warnings 'redefine'; *OpenGL::Array::CLONE_SKIP = sub { 1 }; } sub new { my ($class, $parent, $print) = @_; 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)); # Texture for the extrusion simulator. The texture will be allocated / reallocated on Resize. $self->_texture_name(0); $self->_texture_size(Slic3r::Point->new(0,0)); $self->_extrusion_simulator(Slic3r::ExtrusionSimulator->new()); $self->_simulation_mode(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(); $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 Destroy { my ($self) = @_; # Deallocate the OpenGL resources. my $context = $self->GetContext; if ($context and $self->texture_id) { $self->SetCurrent($context); glDeleteTextures(1, ($self->texture_id)); $self->SetCurrent(0); $self->texture_id(0); $self->texture_size(new Slic3r::Point(0, 0)); } return $self->SUPER::Destroy; } 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 set_simulation_mode { my ($self, $mode) = @_; $self->_simulation_mode($mode); $self->_dirty(1); $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; glClearColor(1, 1, 1, 0); glClear(GL_COLOR_BUFFER_BIT); if (!$self->GetParent->enabled || !$self->layers) { $self->SwapBuffers; return; } glDisable(GL_DEPTH_TEST); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if ($self->_simulation_mode and $self->_texture_name and $self->_texture_size->x() > 0 and $self->_texture_size->y() > 0) { $self->_simulate_extrusion(); my ($x, $y) = $self->GetSizeWH; glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_REPLACE); glBindTexture(GL_TEXTURE_2D, $self->_texture_name); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D_c(GL_TEXTURE_2D, 0, # level (0 normal, heighr is form mip-mapping) GL_RGBA, # internal format $self->_texture_size->x(), $self->_texture_size->y(), 0, # border GL_RGBA, # format RGBA color data GL_UNSIGNED_BYTE, # unsigned byte data $self->_extrusion_simulator->image_ptr()); # ptr to texture data glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0, 1, 0, 1, 0, 1); glBegin(GL_QUADS); glTexCoord2f(0, 0); glVertex2f(0, 0); glTexCoord2f($x/$self->_texture_size->x(), 0); glVertex2f(1, 0); glTexCoord2f($x/$self->_texture_size->x(), $y/$self->_texture_size->y()); glVertex2f(1, 1); glTexCoord2f(0, $y/$self->_texture_size->y()); glVertex2f(0, 1); glEnd(); glPopMatrix(); glBindTexture(GL_TEXTURE_2D, 0); } # 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); } # Tesselator triangulates polygons with holes on the fly for the rendering purposes only. my $tess; if ($self->_simulation_mode() == 0 and !(&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(0.95, 0.95, 0.95); if ($tess) { gluTessBeginPolygon($tess); foreach my $polygon (@$slice) { gluTessBeginContour($tess); gluTessVertex_p($tess, @$_, 0) for @$polygon; gluTessEndContour($tess); } gluTessEndPolygon($tess); } glColor3f(0.9, 0.9, 0.9); 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([0, 0, 0]); $self->_draw(undef, $print_z, $_) for @{$self->print->brim}; $brim_drawn = 1; } if ($self->print->step_done(STEP_SKIRT) && ($self->print->has_infinite_skirt() || $self->print->config->skirt_height > $layer->id) && !$skirt_drawn) { $self->color([0, 0, 0]); $self->_draw(undef, $print_z, $_) for @{$self->print->skirt}; $skirt_drawn = 1; } foreach my $layerm (@{$layer->regions}) { if ($object->step_done(STEP_PERIMETERS)) { $self->color([0.7, 0, 0]); $self->_draw($object, $print_z, $_) for map @$_, @{$layerm->perimeters}; } if ($object->step_done(STEP_INFILL)) { $self->color([0, 0, 0.7]); $self->_draw($object, $print_z, $_) for map @$_, @{$layerm->fills}; } } if ($object->step_done(STEP_SUPPORTMATERIAL)) { if ($layer->isa('Slic3r::Layer::Support')) { $self->color([0, 0, 0]); $self->_draw($object, $print_z, $_) for @{$layer->support_fills}; } } } gluDeleteTess($tess) if $tess; $self->SwapBuffers; } sub _draw { my ($self, $object, $print_z, $path) = @_; my @paths = ($path->isa('Slic3r::ExtrusionLoop') || $path->isa('Slic3r::ExtrusionMultiPath')) ? @$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}); } 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 _simulate_extrusion { my ($self) = @_; $self->_extrusion_simulator->reset_accumulator(); foreach my $layer (@{$self->layers}) { if (abs($layer->print_z - $self->z) < epsilon) { my $object = $layer->object; my @shifts = (defined $object) ? @{$object->_shifted_copies} : (Slic3r::Point->new(0, 0)); foreach my $layerm (@{$layer->regions}) { my @extrusions = (); if ($object->step_done(STEP_PERIMETERS)) { push @extrusions, @$_ for @{$layerm->perimeters}; } if ($object->step_done(STEP_INFILL)) { push @extrusions, @$_ for @{$layerm->fills}; } foreach my $extrusion_entity (@extrusions) { my @paths = ($extrusion_entity->isa('Slic3r::ExtrusionLoop') || $extrusion_entity->isa('Slic3r::ExtrusionMultiPath')) ? @$extrusion_entity : ($extrusion_entity); foreach my $path (@paths) { print "width: ", $path->width, " height: ", $path->height, " mm3_per_mm: ", $path->mm3_per_mm, " height2: ", $path->mm3_per_mm / $path->height, "\n"; $self->_extrusion_simulator->extrude_to_accumulator($path, $_, $self->_simulation_mode()) for @shifts; } } } } } $self->_extrusion_simulator->evaluate_accumulator($self->_simulation_mode()); } sub InitGL { my $self = shift; return if $self->init; return unless $self->GetContext; my $texture_id = 0; ($texture_id) = glGenTextures_p(1); $self->_texture_name($texture_id); $self->init(1); } sub GetContext { my ($self) = @_; return $self->{context} ||= Wx::GLContext->new($self); } sub SetCurrent { my ($self, $context) = @_; return $self->SUPER::SetCurrent($context); } sub Resize { my ($self) = @_; return unless $self->GetContext; return unless $self->bb; $self->_dirty(0); $self->SetCurrent($self->GetContext); my ($x, $y) = $self->GetSizeWH; if ($self->_texture_size->x() < $x or $self->_texture_size->y() < $y) { # Allocate a large enough OpenGL texture with power of 2 dimensions. $self->_texture_size->set_x(1) if ($self->_texture_size->x() == 0); $self->_texture_size->set_y(1) if ($self->_texture_size->y() == 0); $self->_texture_size->set_x($self->_texture_size->x() * 2) while ($self->_texture_size->x() < $x); $self->_texture_size->set_y($self->_texture_size->y() * 2) while ($self->_texture_size->y() < $y); #print "screen size ", $x, "x", $y; #print "texture size ", $self->_texture_size->x(), "x", $self->_texture_size->y(); # Initialize an empty texture. glBindTexture(GL_TEXTURE_2D, $self->_texture_name); if (1) { glTexImage2D_c(GL_TEXTURE_2D, 0, # level (0 normal, heighr is form mip-mapping) GL_RGBA, # internal format $self->_texture_size->x(), $self->_texture_size->y(), 0, # border GL_RGBA, # format RGBA color data GL_UNSIGNED_BYTE, # unsigned byte data 0); # ptr to texture data } glBindTexture(GL_TEXTURE_2D, 0); $self->_extrusion_simulator->set_image_size($self->_texture_size); } $self->_extrusion_simulator->set_viewport(Slic3r::Geometry::BoundingBox->new_from_points( [Slic3r::Point->new(0, 0), Slic3r::Point->new($x, $y)])); 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); # Set the adjusted bounding box at the extrusion simulator. #print "Scene bbox ", $bb->x_min, ",", $bb->y_min, " ", $bb->x_max, ",", $bb->y_max, "\n"; #print "Setting simulator bbox ", $x1, ",", $y1, " ", $x2, ",", $y2, "\n"; $self->_extrusion_simulator->set_bounding_box( Slic3r::Geometry::BoundingBox->new_from_points( [Slic3r::Point->new($x1, $y1), Slic3r::Point->new($x2, $y2)])); glMatrixMode(GL_MODELVIEW); } # Thick line drawing is not used anywhere. Probably not tested? 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-version_1.39.1/lib/Slic3r/GUI/Plater/3D.pm000066400000000000000000000151611324354444700211130ustar00rootroot00000000000000package Slic3r::GUI::Plater::3D; use strict; use warnings; use utf8; use List::Util qw(); use Wx qw(:misc :pen :brush :sizer :font :cursor :keycode wxTAB_TRAVERSAL); use Wx::Event qw(EVT_KEY_DOWN EVT_CHAR); use base qw(Slic3r::GUI::3DScene Class::Accessor); __PACKAGE__->mk_accessors(qw( on_arrange on_rotate_object_left on_rotate_object_right on_scale_object_uniformly on_remove_object on_increase_objects on_decrease_objects)); sub new { my $class = shift; my ($parent, $objects, $model, $print, $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->{print} = $print; $self->{config} = $config; $self->{on_select_object} = sub {}; $self->{on_instances_moved} = sub {}; $self->{on_wipe_tower_moved} = sub {}; $self->on_select(sub { my ($volume_idx) = @_; $self->{on_select_object}->(($volume_idx == -1) ? undef : $self->volumes->[$volume_idx]->object_idx) if ($self->{on_select_object}); }); $self->on_move(sub { my @volume_idxs = @_; my %done = (); # prevent moving instances twice my $object_moved; my $wipe_tower_moved; foreach my $volume_idx (@volume_idxs) { my $volume = $self->volumes->[$volume_idx]; my $obj_idx = $volume->object_idx; my $instance_idx = $volume->instance_idx; next if $done{"${obj_idx}_${instance_idx}"}; $done{"${obj_idx}_${instance_idx}"} = 1; if ($obj_idx < 1000) { # Move a regular object. 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; $object_moved = 1; } elsif ($obj_idx == 1000) { # Move a wipe tower proxy. $wipe_tower_moved = $volume->origin; } } $self->{on_instances_moved}->() if $object_moved && $self->{on_instances_moved}; $self->{on_wipe_tower_moved}->($wipe_tower_moved) if $wipe_tower_moved && $self->{on_wipe_tower_moved}; }); EVT_KEY_DOWN($self, sub { my ($s, $event) = @_; if ($event->HasModifiers) { $event->Skip; } else { my $key = $event->GetKeyCode; if ($key == WXK_DELETE) { $self->on_remove_object->() if $self->on_remove_object; } else { $event->Skip; } } }); EVT_CHAR($self, sub { my ($s, $event) = @_; if ($event->HasModifiers) { $event->Skip; } else { my $key = $event->GetKeyCode; if ($key == ord('a')) { $self->on_arrange->() if $self->on_arrange; } elsif ($key == ord('l')) { $self->on_rotate_object_left->() if $self->on_rotate_object_left; } elsif ($key == ord('r')) { $self->on_rotate_object_right->() if $self->on_rotate_object_right; } elsif ($key == ord('s')) { $self->on_scale_object_uniformly->() if $self->on_scale_object_uniformly; } elsif ($key == ord('+')) { $self->on_increase_objects->() if $self->on_increase_objects; } elsif ($key == ord('-')) { $self->on_decrease_objects->() if $self->on_decrease_objects; } else { $event->Skip; } } }); 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_arrange { my ($self, $cb) = @_; $self->on_arrange($cb); } sub set_on_rotate_object_left { my ($self, $cb) = @_; $self->on_rotate_object_left($cb); } sub set_on_rotate_object_right { my ($self, $cb) = @_; $self->on_rotate_object_right($cb); } sub set_on_scale_object_uniformly { my ($self, $cb) = @_; $self->on_scale_object_uniformly($cb); } sub set_on_increase_objects { my ($self, $cb) = @_; $self->on_increase_objects($cb); } sub set_on_decrease_objects { my ($self, $cb) = @_; $self->on_decrease_objects($cb); } sub set_on_remove_object { my ($self, $cb) = @_; $self->on_remove_object($cb); } sub set_on_instances_moved { my ($self, $cb) = @_; $self->{on_instances_moved} = $cb; } sub set_on_wipe_tower_moved { my ($self, $cb) = @_; $self->{on_wipe_tower_moved} = $cb; } sub set_on_model_update { my ($self, $cb) = @_; $self->on_model_update($cb); } sub reload_scene { my ($self, $force) = @_; $self->reset_objects; $self->update_bed_size; if (! $self->IsShown && ! $force) { $self->{reload_delayed} = 1; return; } $self->{reload_delayed} = 0; foreach my $obj_idx (0..$#{$self->{model}->objects}) { my @volume_idxs = $self->load_object($self->{model}, $self->{print}, $obj_idx); if ($self->{objects}[$obj_idx]->selected) { $self->select_volume($_) for @volume_idxs; } } if (defined $self->{config}->nozzle_diameter) { # Should the wipe tower be visualized? my $extruders_count = scalar @{ $self->{config}->nozzle_diameter }; # Height of a print. my $height = $self->{model}->bounding_box->z_max; # Show at least a slab. $height = 10 if $height < 10; if ($extruders_count > 1 && $self->{config}->single_extruder_multi_material && $self->{config}->wipe_tower && ! $self->{config}->complete_objects) { $self->volumes->load_wipe_tower_preview(1000, $self->{config}->wipe_tower_x, $self->{config}->wipe_tower_y, $self->{config}->wipe_tower_width, $self->{config}->wipe_tower_per_color_wipe * ($extruders_count - 1), $self->{model}->bounding_box->z_max, $self->UseVBOs); } } } sub update_bed_size { my ($self) = @_; $self->set_bed_shape($self->{config}->bed_shape); } # Called by the Platter wxNotebook when this page is activated. sub OnActivate { my ($self) = @_; $self->reload_scene(1) if ($self->{reload_delayed}); } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/Plater/3DPreview.pm000066400000000000000000000273551324354444700224650ustar00rootroot00000000000000package Slic3r::GUI::Plater::3DPreview; use strict; use warnings; use utf8; use Slic3r::Print::State ':steps'; use Wx qw(:misc :sizer :slider :statictext :keycode wxWHITE); use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN EVT_CHECKBOX); use base qw(Wx::Panel Class::Accessor); __PACKAGE__->mk_accessors(qw(print enabled _loaded canvas slider_low slider_high single_layer)); sub new { my $class = shift; my ($parent, $print, $config) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition); $self->{config} = $config; $self->{number_extruders} = 1; $self->{preferred_color_mode} = 'feature'; # init GUI elements my $canvas = Slic3r::GUI::3DScene->new($self); $canvas->use_plain_shader(1); $self->canvas($canvas); my $slider_low = 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_low($slider_low); my $slider_high = 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_high($slider_high); my $z_label_low = $self->{z_label_low} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, [40,-1], wxALIGN_CENTRE_HORIZONTAL); $z_label_low->SetFont($Slic3r::GUI::small_font); my $z_label_high = $self->{z_label_high} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, [40,-1], wxALIGN_CENTRE_HORIZONTAL); $z_label_high->SetFont($Slic3r::GUI::small_font); $self->single_layer(0); $self->{color_by_extruder} = 0; my $checkbox_singlelayer = $self->{checkbox_singlelayer} = Wx::CheckBox->new($self, -1, "1 Layer"); my $checkbox_color_by_extruder = $self->{checkbox_color_by_extruder} = Wx::CheckBox->new($self, -1, "Tool"); my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); my $vsizer = Wx::BoxSizer->new(wxVERTICAL); my $vsizer_outer = Wx::BoxSizer->new(wxVERTICAL); $vsizer->Add($slider_low, 3, 0, 0); $vsizer->Add($z_label_low, 0, 0, 0); $hsizer->Add($vsizer, 0, wxEXPAND, 0); $vsizer = Wx::BoxSizer->new(wxVERTICAL); $vsizer->Add($slider_high, 3, 0, 0); $vsizer->Add($z_label_high, 0, 0, 0); $hsizer->Add($vsizer, 0, wxEXPAND, 0); $vsizer_outer->Add($hsizer, 3, wxALIGN_CENTER_HORIZONTAL, 0); $vsizer_outer->Add($checkbox_singlelayer, 0, wxTOP | wxALIGN_CENTER_HORIZONTAL, 5); $vsizer_outer->Add($checkbox_color_by_extruder, 0, wxTOP | wxALIGN_CENTER_HORIZONTAL, 5); my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); $sizer->Add($canvas, 1, wxALL | wxEXPAND, 0); $sizer->Add($vsizer_outer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5); EVT_SLIDER($self, $slider_low, sub { $slider_high->SetValue($slider_low->GetValue) if $self->single_layer; $self->set_z_idx_low ($slider_low ->GetValue) }); EVT_SLIDER($self, $slider_high, sub { $slider_low->SetValue($slider_high->GetValue) if $self->single_layer; $self->set_z_idx_high($slider_high->GetValue) }); EVT_KEY_DOWN($canvas, sub { my ($s, $event) = @_; my $key = $event->GetKeyCode; if ($event->HasModifiers) { $event->Skip; } else { if ($key == ord('U')) { $slider_high->SetValue($slider_high->GetValue + 1); $slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown()); $self->set_z_idx_high($slider_high->GetValue); } elsif ($key == ord('D')) { $slider_high->SetValue($slider_high->GetValue - 1); $slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown()); $self->set_z_idx_high($slider_high->GetValue); } elsif ($key == ord('S')) { $checkbox_singlelayer->SetValue(! $checkbox_singlelayer->GetValue()); $self->single_layer($checkbox_singlelayer->GetValue()); if ($self->single_layer) { $slider_low->SetValue($slider_high->GetValue); $self->set_z_idx_high($slider_high->GetValue); } } else { $event->Skip; } } }); EVT_KEY_DOWN($slider_low, sub { my ($s, $event) = @_; my $key = $event->GetKeyCode; if ($event->HasModifiers) { $event->Skip; } else { if ($key == WXK_LEFT) { } elsif ($key == WXK_RIGHT) { $slider_high->SetFocus; } else { $event->Skip; } } }); EVT_KEY_DOWN($slider_high, sub { my ($s, $event) = @_; my $key = $event->GetKeyCode; if ($event->HasModifiers) { $event->Skip; } else { if ($key == WXK_LEFT) { $slider_low->SetFocus; } elsif ($key == WXK_RIGHT) { } else { $event->Skip; } } }); EVT_CHECKBOX($self, $checkbox_singlelayer, sub { $self->single_layer($checkbox_singlelayer->GetValue()); if ($self->single_layer) { $slider_low->SetValue($slider_high->GetValue); $self->set_z_idx_high($slider_high->GetValue); } }); EVT_CHECKBOX($self, $checkbox_color_by_extruder, sub { $self->{color_by_extruder} = $checkbox_color_by_extruder->GetValue(); $self->{preferred_color_mode} = $self->{color_by_extruder} ? 'tool' : 'feature'; $self->reload_print; }); $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, $force) = @_; $self->canvas->reset_objects; $self->_loaded(0); if (! $self->IsShown && ! $force) { $self->{reload_delayed} = 1; return; } $self->load_print; } sub load_print { my ($self) = @_; 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) my $n_layers = 0; if ($self->print->object_step_done(STEP_SLICE)) { my %z = (); # z => 1 foreach my $object (@{$self->{print}->objects}) { foreach my $layer (@{$object->layers}, @{$object->support_layers}) { $z{$layer->print_z} = 1; } } $self->{layers_z} = [ sort { $a <=> $b } keys %z ]; $n_layers = scalar(@{$self->{layers_z}}); } if ($n_layers == 0) { $self->enabled(0); $self->set_z_range(0,0); $self->slider_low->Hide; $self->slider_high->Hide; $self->canvas->Refresh; # clears canvas return; } my $z_idx_low = $self->slider_low->GetValue; my $z_idx_high = $self->slider_high->GetValue; $self->enabled(1); $self->slider_low->SetRange(0, $n_layers - 1); $self->slider_high->SetRange(0, $n_layers - 1); if ($z_idx_high < $n_layers && ($self->single_layer || $z_idx_high != 0)) { # use $z_idx } else { # Out of range. Disable 'single layer' view. $self->single_layer(0); $self->{checkbox_singlelayer}->SetValue(0); $z_idx_low = 0; $z_idx_high = $n_layers - 1; } if ($self->single_layer) { $z_idx_low = $z_idx_high; } elsif ($z_idx_low > $z_idx_high) { $z_idx_low = 0; } $self->slider_low->SetValue($z_idx_low); $self->slider_high->SetValue($z_idx_high); $self->slider_low->Show; $self->slider_high->Show; $self->Layout; my $by_tool = $self->{color_by_extruder}; if ($self->{preferred_color_mode} eq 'tool_or_feature') { # It is left to Slic3r to decide whether the print shall be colored by the tool or by the feature. # Color by feature if it is a single extruder print. my $extruders = $self->{print}->extruders; $by_tool = scalar(@{$extruders}) > 1; $self->{color_by_extruder} = $by_tool; $self->{checkbox_color_by_extruder}->SetValue($by_tool); $self->{preferred_color_mode} = 'tool_or_feature'; } # Collect colors per extruder. # Leave it empty, if the print should be colored by a feature. my @colors = (); if ($by_tool) { my @extruder_colors = @{$self->{config}->extruder_colour}; my @filament_colors = @{$self->{config}->filament_colour}; for (my $i = 0; $i <= $#extruder_colors; $i += 1) { my $color = $extruder_colors[$i]; $color = $filament_colors[$i] if (! defined($color) || $color !~ m/^#[[:xdigit:]]{6}/); $color = '#FFFFFF' if (! defined($color) || $color !~ m/^#[[:xdigit:]]{6}/); push @colors, $color; } } if ($self->IsShown) { # load skirt and brim $self->canvas->load_print_toolpaths($self->print, \@colors); $self->canvas->load_wipe_tower_toolpaths($self->print, \@colors); foreach my $object (@{$self->print->objects}) { $self->canvas->load_print_object_toolpaths($object, \@colors); # Show the objects in very transparent color. #my @volume_ids = $self->canvas->load_object($object->model_object); #$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids; } $self->canvas->zoom_to_volumes; $self->_loaded(1); } $self->set_z_range($self->{layers_z}[$z_idx_low], $self->{layers_z}[$z_idx_high]); } sub set_z_range { my ($self, $z_low, $z_high) = @_; return if !$self->enabled; $self->{z_label_low}->SetLabel(sprintf '%.2f', $z_low); $self->{z_label_high}->SetLabel(sprintf '%.2f', $z_high); $self->canvas->set_toolpaths_range($z_low - 1e-6, $z_high + 1e-6); $self->canvas->Refresh if $self->IsShown; } sub set_z_idx_low { my ($self, $idx_low) = @_; if ($self->enabled) { my $idx_high = $self->slider_high->GetValue; if ($idx_low >= $idx_high) { $idx_high = $idx_low; $self->slider_high->SetValue($idx_high); } $self->set_z_range($self->{layers_z}[$idx_low], $self->{layers_z}[$idx_high]); } } sub set_z_idx_high { my ($self, $idx_high) = @_; if ($self->enabled) { my $idx_low = $self->slider_low->GetValue; if ($idx_low > $idx_high) { $idx_low = $idx_high; $self->slider_low->SetValue($idx_low); } $self->set_z_range($self->{layers_z}[$idx_low], $self->{layers_z}[$idx_high]); } } sub set_bed_shape { my ($self, $bed_shape) = @_; $self->canvas->set_bed_shape($bed_shape); } sub set_number_extruders { my ($self, $number_extruders) = @_; if ($self->{number_extruders} != $number_extruders) { $self->{number_extruders} = $number_extruders; my $by_tool = $number_extruders > 1; $self->{color_by_extruder} = $by_tool; $self->{checkbox_color_by_extruder}->SetValue($by_tool); $self->{preferred_color_mode} = $by_tool ? 'tool_or_feature' : 'feature'; } } # Called by the Platter wxNotebook when this page is activated. sub OnActivate { my ($self) = @_; $self->reload_print(1) if ($self->{reload_delayed}); } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/Plater/3DToolpaths.pm000066400000000000000000000113111324354444700230020ustar00rootroot00000000000000package Slic3r::GUI::Plater::3DToolpaths; 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); __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) = @_; if ($event->HasModifiers) { $event->Skip; } else { 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) = @_; $self->canvas->reset_objects; $self->_loaded(0); $self->load_print; } sub load_print { my ($self) = @_; 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 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) { # load skirt and brim $self->canvas->load_print_toolpaths($self->print); foreach my $object (@{$self->print->objects}) { $self->canvas->load_print_object_toolpaths($object); # Show the objects in very transparent color. #my @volume_ids = $self->canvas->load_object($object->model_object); #$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids; } $self->canvas->zoom_to_volumes; $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-version_1.39.1/lib/Slic3r/GUI/Plater/LambdaObjectDialog.pm000066400000000000000000000166221324354444700242770ustar00rootroot00000000000000# Generate an anonymous or "lambda" 3D object. This gets used with the Add Generic option in Settings. # package Slic3r::GUI::Plater::LambdaObjectDialog; use strict; use warnings; use utf8; 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, "Lambda Object", 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 => "box", dim => [1, 1, 1], cyl_r => 1, cyl_h => 1, sph_rho => 1.0, slab_h => 1.0, slab_z => 0.0, }; $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); my $button_sizer = Wx::BoxSizer->new(wxHORIZONTAL); my $button_ok = $self->CreateStdDialogButtonSizer(wxOK); my $button_cancel = $self->CreateStdDialogButtonSizer(wxCANCEL); $button_sizer->Add($button_ok); $button_sizer->Add($button_cancel); EVT_BUTTON($self, wxID_OK, sub { # validate user input return if !$self->CanClose; $self->EndModal(wxID_OK); $self->Destroy; }); EVT_BUTTON($self, wxID_CANCEL, sub { # validate user input return if !$self->CanClose; $self->EndModal(wxID_CANCEL); $self->Destroy; }); 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, ); my @options = ("box", "slab", "cylinder", "sphere"); $self->{type} = Wx::ComboBox->new($self, 1, "box", wxDefaultPosition, wxDefaultSize, \@options, wxCB_READONLY); $optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 0, label => 'L', type => 'f', default => '1', )); $optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 1, label => 'W', type => 'f', default => '1', )); $optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 2, label => 'H', type => 'f', default => '1', )); 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 => '1', )); $optgroup_cylinder->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => "cyl_h", label => 'Height', type => 'f', default => '1', )); 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 => 'Rho', type => 'f', default => '1', )); 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_z' || $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 => 'H', type => 'f', default => '1', )); $optgroup_slab->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => "slab_z", label => 'Initial Z', type => 'f', default => '0', )); 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($button_sizer,0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); $self->_update_ui; $self->SetSizer($self->{sizer}); $self->{sizer}->Fit($self); $self->{sizer}->SetSizeHints($self); return $self; } sub CanClose { return 1; } 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-version_1.39.1/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm000066400000000000000000000252151324354444700236500ustar00rootroot00000000000000# 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 Slic3r::Geometry qw(PI X); 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, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, $params{object}->name, wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); $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; # cut options $self->{cut_options} = { z => 0, keep_upper => 1, keep_lower => 1, rotate_lower => 1, # preview => 1, # Disabled live preview by default as it is not stable and/or the calculation takes too long for interactive usage. preview => 0, }; 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 => 'z', type => 'slider', label => 'Z', default => $self->{cut_options}{z}, min => 0, max => $self->{model_object}->bounding_box->size->z * $self->{model_object}->instances->[0]->scaling_factor, 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); $cut_button_sizer->Add($self->{btn_cut}, 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, 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 ($self->{new_model_objects}{lower}) { if ($self->{cut_options}{rotate_lower}) { $self->{new_model_objects}{lower}->rotate(PI, X); $self->{new_model_objects}{lower}->center_around_origin; # align to Z = 0 } } if ($self->{new_model_objects}{upper}) { $self->{new_model_objects}{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_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) = @_; return $self->{cut_options}{z} / $self->{model_object}->instances->[0]->scaling_factor; } # 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($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}{upper} = $upper_object; } if ($self->{cut_options}{keep_lower} && $lower_object->volumes_count > 0) { $self->{new_model_objects}{lower} = $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, values %{$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([ $z + $volume->mesh->bounding_box->z_min ])->[0]; push @expolygons, @$expp; } foreach my $expolygon (@expolygons) { $self->{model_object}->instances->[0]->transform_polygon($_) for @$expolygon; $expolygon->translate(map Slic3r::Geometry::scale($_), @{ $self->{model_object}->instances->[0]->offset }); } $self->{canvas}->reset_objects; $self->{canvas}->load_object($_, undef, undef, [0]) for @objects; $self->{canvas}->SetCuttingPlane( $self->{cut_options}{z}, [@expolygons], ); $self->{canvas}->Render; } } # update controls { my $z = $self->{cut_options}{z}; my $optgroup = $self->{optgroup}; $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}); # Disabled live preview by default as it is not stable and/or the calculation takes too long for interactive usage. # $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 values %{ $self->{new_model_objects} }; } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm000066400000000000000000000511431324354444700240450ustar00rootroot00000000000000# 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 :keycode wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxID_CANCEL wxMOD_CONTROL wxTheApp); use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED EVT_TREE_KEY_DOWN); use base 'Wx::Panel'; use constant ICON_OBJECT => 0; use constant ICON_SOLIDMESH => 1; use constant ICON_MODIFIERMESH => 2; sub new { my ($class, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); # C++ type Slic3r::ModelObject my $object = $self->{model_object} = $params{model_object}; # Save state for sliders. $self->{move_options} = { x => 0, y => 0, z => 0, }; $self->{last_coords} = { x => 0, y => 0, z => 0, }; # 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); { $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, "Load generic…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $self->{btn_delete} = Wx::Button->new($self, -1, "Delete part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $self->{btn_split} = Wx::Button->new($self, -1, "Split part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $self->{btn_move_up} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT); $self->{btn_move_down} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT); $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)); $self->{btn_split}->SetBitmap(Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG)); $self->{btn_move_up}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_up.png"), wxBITMAP_TYPE_PNG)); $self->{btn_move_down}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_down.png"), wxBITMAP_TYPE_PNG)); # buttons sizer my $buttons_sizer = Wx::GridSizer->new(2, 3); $buttons_sizer->Add($self->{btn_load_part}, 0, wxEXPAND | wxBOTTOM | wxRIGHT, 5); $buttons_sizer->Add($self->{btn_load_modifier}, 0, wxEXPAND | wxBOTTOM | wxRIGHT, 5); $buttons_sizer->Add($self->{btn_load_lambda_modifier}, 0, wxEXPAND | wxBOTTOM, 5); $buttons_sizer->Add($self->{btn_delete}, 0, wxEXPAND | wxRIGHT, 5); $buttons_sizer->Add($self->{btn_split}, 0, wxEXPAND | wxRIGHT, 5); { my $up_down_sizer = Wx::GridSizer->new(1, 2); $up_down_sizer->Add($self->{btn_move_up}, 0, wxEXPAND | wxRIGHT, 5); $up_down_sizer->Add($self->{btn_move_down}, 0, wxEXPAND, 5); $buttons_sizer->Add($up_down_sizer, 0, wxEXPAND, 5); } $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); $self->{btn_split}->SetFont($Slic3r::GUI::small_font); $self->{btn_move_up}->SetFont($Slic3r::GUI::small_font); $self->{btn_move_down}->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; $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. if ($self->{move_options}{$opt_id} != $optgroup_movers->get_value($opt_id)) { $self->{move_options}{$opt_id} = $optgroup_movers->get_value($opt_id); 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, min => -($self->{model_object}->bounding_box->size->x)*4, max => $self->{model_object}->bounding_box->size->x*4, full_width => 1, )); $optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'y', type => 'slider', label => 'Y', default => 0, min => -($self->{model_object}->bounding_box->size->y)*4, max => $self->{model_object}->bounding_box->size->y*4, full_width => 1, )); $optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'z', type => 'slider', label => 'Z', default => 0, min => -($self->{model_object}->bounding_box->size->z)*4, max => $self->{model_object}->bounding_box->size->z*4, full_width => 1, )); # left pane with tree my $left_sizer = Wx::BoxSizer->new(wxVERTICAL); $left_sizer->Add($tree, 3, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10); $left_sizer->Add($buttons_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10); $left_sizer->Add($settings_sizer, 5, 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(($volume_idx == -1) ? undef : $canvas->volumes->[$volume_idx]->volume_idx); }); $canvas->load_object($self->{model_object}, undef, 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_KEY_DOWN($self, $tree, \&on_tree_key_down); 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); EVT_BUTTON($self, $self->{btn_split}, \&on_btn_split); EVT_BUTTON($self, $self->{btn_move_up}, \&on_btn_move_up); EVT_BUTTON($self, $self->{btn_move_down}, \&on_btn_move_down); $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}) { $_->set_selected(0) for @{$self->{canvas}->volumes}; } # disable things as if nothing is selected $self->{'btn_' . $_}->Disable for (qw(delete move_up move_down split)); $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_options} = { x => 0, y => 0, z => 0, }; $self->{last_coords} = { x => 0, y => 0, z => 0, }; 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} ]->set_selected(1); } $self->{btn_delete}->Enable; $self->{btn_split}->Enable; $self->{btn_move_up}->Enable if $itemData->{volume_id} > 0; $self->{btn_move_down}->Enable if $itemData->{volume_id} + 1 < $self->{model_object}->volumes_count; # attach volume config to settings panel my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ]; if ($volume->modifier) { $self->{optgroup_movers}->enable; } else { $self->{optgroup_movers}->disable; } $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->{optgroup_movers}->disable; $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_keys(\@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; } foreach my $object (@{$model->objects}) { foreach my $volume (@{$object->volumes}) { my $new_volume = $self->{model_object}->add_volume($volume); $new_volume->set_modifier($is_modifier); $new_volume->set_name(basename($input_file)); # 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::cube($params->{"dim"}[0], $params->{"dim"}[1], $params->{"dim"}[2]); } elsif ($type eq "cylinder") { $mesh = Slic3r::TriangleMesh::cylinder($params->{"cyl_r"}, $params->{"cyl_h"}); } elsif ($type eq "sphere") { $mesh = Slic3r::TriangleMesh::sphere($params->{"sph_rho"}); } elsif ($type eq "slab") { $mesh = Slic3r::TriangleMesh::cube($self->{model_object}->bounding_box->size->x*1.5, $self->{model_object}->bounding_box->size->y*1.5, $params->{"slab_h"}); # box sets the base coordinate at 0,0, move to center of plate and move it up to initial_z $mesh->translate(-$self->{model_object}->bounding_box->size->x*1.5/2.0, -$self->{model_object}->bounding_box->size->y*1.5/2.0, $params->{"slab_z"}); } else { return; } $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} = 1; $self->_parts_changed; } sub on_tree_key_down { my ($self, $event) = @_; my $keycode = $event->GetKeyCode; # Wx >= 0.9911 if (defined(&Wx::TreeEvent::GetKeyEvent) && ($event->GetKeyEvent->GetModifiers & wxMOD_CONTROL)) { if ($keycode == WXK_UP) { $event->Skip; $self->on_btn_move_up; } elsif ($keycode == WXK_DOWN) { $event->Skip; $self->on_btn_move_down; } } } sub on_btn_move_up { my ($self) = @_; my $itemData = $self->get_selection; if ($itemData && $itemData->{type} eq 'volume') { my $volume_id = $itemData->{volume_id}; if ($self->{model_object}->move_volume_up($volume_id)) { $self->{canvas}->volumes->move_volume_up($volume_id); $self->{parts_changed} = 1; $self->reload_tree($volume_id - 1); } } } sub on_btn_move_down { my ($self) = @_; my $itemData = $self->get_selection; if ($itemData && $itemData->{type} eq 'volume') { my $volume_id = $itemData->{volume_id}; if ($self->{model_object}->move_volume_down($volume_id)) { $self->{canvas}->volumes->move_volume_down($volume_id); $self->{parts_changed} = 1; $self->reload_tree($volume_id + 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 on_btn_split { my ($self) = @_; my $itemData = $self->get_selection; if ($itemData && $itemData->{type} eq 'volume') { my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}]; $self->{parts_changed} = 1 if $volume->split > 1; } $self->_parts_changed; } sub _parts_changed { my ($self) = @_; $self->reload_tree; 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 = $self->GetParent->GetParent->GetParent->GetParent->GetParent->config->clone; eval { $config->apply($self->model_object->config); $config->validate; }; return ! Slic3r::GUI::catch_error($self); } sub PartsChanged { my ($self) = @_; return $self->{parts_changed}; } sub PartSettingsChanged { my ($self) = @_; return $self->{part_settings_changed}; } sub _update { my ($self) = @_; my ($m_x, $m_y, $m_z) = ($self->{move_options}{x}, $self->{move_options}{y}, $self->{move_options}{z}); my ($l_x, $l_y, $l_z) = ($self->{last_coords}{x}, $self->{last_coords}{y}, $self->{last_coords}{z}); my $itemData = $self->get_selection; if ($itemData && $itemData->{type} eq 'volume') { my $d = Slic3r::Pointf3->new($m_x - $l_x, $m_y - $l_y, $m_z - $l_z); my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}]; $volume->mesh->translate(@{$d}); $self->{last_coords}{x} = $m_x; $self->{last_coords}{y} = $m_y; $self->{last_coords}{z} = $m_z; } $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; } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm000066400000000000000000000173401324354444700247150ustar00rootroot00000000000000# 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); use base 'Wx::Dialog'; # Called with # %params{object} of a Perl type Slic3r::GUI::Plater::Object # %params{model_object} of a C++ type Slic3r::ModelObject sub new { my ($class, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, "Settings for " . $params{object}->name, wxDefaultPosition, [700,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); $self->{$_} = $params{$_} for keys %params; $self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); $self->{tabpanel}->AddPage($self->{parts} = Slic3r::GUI::Plater::ObjectPartsPanel->new($self->{tabpanel}, model_object => $params{model_object}), "Parts"); $self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}), "Layers"); 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) = @_; # $self->GetParent->GetParent is of type Slic3r::GUI::Plater::ObjectSettingsDialog return $self->GetParent->GetParent->{model_object}; } 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 default layer height for parts of this object.", wxDefaultPosition, [-1, 40]); $label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); $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; $grid->SetColLabelValue(0, "Min Z (mm)"); $grid->SetColLabelValue(1, "Max Z (mm)"); $grid->SetColLabelValue(2, "Layer height (mm)"); $grid->SetColSize($_, 135) 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, ($event->GetCol == 2) ? $self->_clamp_layer_height($value) : $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 _clamp_layer_height { my ($self, $value) = @_; # $self->GetParent->GetParent is of type Slic3r::GUI::Plater::ObjectSettingsDialog my $config = $self->GetParent->GetParent->{config}; if ($value =~ /^[0-9,.E]+$/) { # Looks like a number. Validate the layer height. my $nozzle_dmrs = $config->get('nozzle_diameter'); my $min_layer_heights = $config->get('min_layer_height'); my $max_layer_heights = $config->get('max_layer_height'); my $min_layer_height = 1000.; my $max_layer_height = 0.; my $max_nozzle_dmr = 0.; for (my $i = 0; $i < int(@{$nozzle_dmrs}); $i += 1) { $min_layer_height = $min_layer_heights->[$i] if ($min_layer_heights->[$i] < $min_layer_height); $max_layer_height = $max_layer_heights->[$i] if ($max_layer_heights->[$i] > $max_layer_height); $max_nozzle_dmr = $nozzle_dmrs ->[$i] if ($nozzle_dmrs ->[$i] > $max_nozzle_dmr ); } $min_layer_height = 0.005 if ($min_layer_height < 0.005); $max_layer_height = $max_nozzle_dmr * 0.75 if ($max_layer_height == 0.); $max_layer_height = $max_nozzle_dmr if ($max_layer_height > $max_nozzle_dmr); return ($value < $min_layer_height) ? $min_layer_height : ($value > $max_layer_height) ? $max_layer_height : $value; } else { # If an invalid numeric value has been entered, use the default layer height. return $config->get('layer_height'); } } 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-version_1.39.1/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm000066400000000000000000000151431324354444700251250ustar00rootroot00000000000000# Included in ObjectSettingsDialog -> ObjectPartsPanel. # Maintains, displays, adds and removes overrides of slicing parameters for an object and its modifier mesh. 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, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); # C++ class Slic3r::DynamicPrintConfig, initially empty. $self->{default_config} = Slic3r::Config->new; $self->{config} = Slic3r::Config->new; # On change callback. $self->{on_change} = $params{on_change}; $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); EVT_LEFT_DOWN($btn, sub { my $menu = Wx::Menu->new; # 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 my @categories = ('Layers and Perimeters', 'Infill', 'Support material', 'Speed', 'Extruders', 'Extrusion Width', 'Advanced'); #foreach my $cat (sort keys %categories) { foreach my $cat (@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}); $self->SetScrollbars(0, 1, 0, 1); $self->set_opt_keys($params{opt_keys}) if $params{opt_keys}; $self->update_optgroup; return $self; } sub set_default_config { my ($self, $config) = @_; $self->{default_config} = $config; } sub set_config { my ($self, $config) = @_; $self->{config} = $config; $self->update_optgroup; } sub set_opt_keys { my ($self, $opt_keys) = @_; # sort options by category+label $self->{option_labels} = { map { $_ => $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label} } @$opt_keys }; $self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ]; } sub set_fixed_options { my ($self, $opt_keys) = @_; $self->{fixed_options} = { map {$_ => 1} @$opt_keys }; $self->update_optgroup; } sub update_optgroup { my $self = shift; $self->{options_sizer}->Clear(1); return if !defined $self->{config}; 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 = 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 { $self->{on_change}->() if $self->{on_change} }, extra_column => sub { my ($line) = @_; my $opt_key = $line->get_options->[0]->opt_id; # we assume that we have one option per line # disallow deleting fixed options return undef if $self->{fixed_options}{$opt_key}; 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}->() if $self->{on_change}; wxTheApp->CallAfter(sub { $self->update_optgroup }); }); return $btn; }, ); foreach my $opt_key (sort @{$categories{$category}}) { $optgroup->append_single_option_line($opt_key); } $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; } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/Preferences.pm000066400000000000000000000123761324354444700216640ustar00rootroot00000000000000# 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 $app_config = wxTheApp->{app_config}; 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( # 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 => $app_config->get("version_check") // 1, # readonly => !wxTheApp->have_version_check, # )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( 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 => $app_config->get("remember_output_path"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'autocenter', type => 'bool', label => 'Auto-center parts', tooltip => 'If this is enabled, Slic3r will auto-center objects around the print bed center.', default => $app_config->get("autocenter"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( 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 => $app_config->get("background_processing"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'no_controller', type => 'bool', label => 'Disable USB/serial connection', tooltip => 'Disable communication with the printer over a serial / USB cable. This simplifies the user interface in case the printer is never attached to the computer.', default => $app_config->get("no_controller"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'no_defaults', type => 'bool', label => 'Suppress "- default -" presets', tooltip => 'Suppress "- default -" presets in the Print / Filament / Printer selections once there are any other valid presets available.', default => $app_config->get("no_defaults"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'show_incompatible_presets', type => 'bool', label => 'Show incompatible print and filament presets', tooltip => 'When checked, the print and filament presets are shown in the preset editor even ' . 'if they are marked as incompatible with the active printer', default => $app_config->get("show_incompatible_presets"), )); $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( opt_id => 'use_legacy_opengl', type => 'bool', label => 'Use legacy OpenGL 1.1 rendering', tooltip => 'If you have rendering issues caused by a buggy OpenGL 2.0 driver, you may try to check this checkbox. This will disable the layer height editing and anti aliasing, so it is likely better to upgrade your graphics driver.', default => $app_config->get("use_legacy_opengl"), )); 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) = @_; if (defined($self->{values}{no_controller}) || defined($self->{values}{no_defaults}) || defined($self->{values}{use_legacy_opengl})) { Slic3r::GUI::warning_catcher($self)->("You need to restart Slic3r to make the changes effective."); } my $app_config = wxTheApp->{app_config}; $app_config->set($_, $self->{values}{$_}) for keys %{$self->{values}}; $self->EndModal(wxID_OK); $self->Close; # needed on Linux # Nothify the UI to update itself from the ini file. wxTheApp->update_ui_from_settings; } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/ProgressStatusBar.pm000066400000000000000000000061071324354444700230530ustar00rootroot00000000000000# 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-version_1.39.1/lib/Slic3r/GUI/SystemInfo.pm000066400000000000000000000046151324354444700215200ustar00rootroot00000000000000package Slic3r::GUI::SystemInfo; use strict; use warnings; use utf8; use Wx qw(:font :html :misc :dialog :sizer :systemsettings :frame :id wxTheClipboard); use Wx::Event qw(EVT_HTML_LINK_CLICKED EVT_LEFT_DOWN EVT_BUTTON); use Wx::Html; use base 'Wx::Dialog'; sub new { my ($class, %params) = @_; my $self = $class->SUPER::new($params{parent}, -1, 'Slic3r Prusa Edition - System Information', wxDefaultPosition, [600, 340], wxDEFAULT_DIALOG_STYLE | wxMAXIMIZE_BOX | wxRESIZE_BORDER); $self->{text_info} = $params{text_info}; $self->SetBackgroundColour(Wx::wxWHITE); my $vsizer = Wx::BoxSizer->new(wxVERTICAL); $self->SetSizer($vsizer); # text my $text = '' . '' . ($params{slic3r_info} // '') . ($params{copyright_info} // '') . ($params{system_info} // '') . ($params{opengl_info} // '') . '' . ''; my $html = $self->{html} = Wx::HtmlWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO); my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); my $size = &Wx::wxMSW ? 8 : 10; $html->SetFonts($font->GetFaceName, $font->GetFaceName, [$size * 1.5, $size * 1.4, $size * 1.3, $size, $size, $size, $size]); $html->SetBorders(10); $html->SetPage($text); $vsizer->Add($html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 0); EVT_HTML_LINK_CLICKED($self, $html, \&link_clicked); my $buttons = $self->CreateStdDialogButtonSizer(wxOK); my $btn_copy_to_clipboard = Wx::Button->new($self, -1, "Copy to Clipboard", wxDefaultPosition, wxDefaultSize); $buttons->Insert(0, $btn_copy_to_clipboard, 0, wxLEFT, 5); EVT_BUTTON($self, $btn_copy_to_clipboard, \©_to_clipboard); $self->SetEscapeId(wxID_CLOSE); EVT_BUTTON($self, wxID_CLOSE, sub { $self->EndModal(wxID_CLOSE); $self->Close; }); # $vsizer->Add($buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3); $vsizer->Add($buttons, 0, wxEXPAND | wxALL, 5); return $self; } sub link_clicked { my ($self, $event) = @_; Wx::LaunchDefaultBrowser($event->GetLinkInfo->GetHref); $event->Skip(0); } sub copy_to_clipboard { my ($self, $event) = @_; my $data = $self->{text_info}; wxTheClipboard->Open; wxTheClipboard->SetData(Wx::TextDataObject->new($data)); wxTheClipboard->Close; } 1; Slic3r-version_1.39.1/lib/Slic3r/GUI/Tab.pm000066400000000000000000002333121324354444700201240ustar00rootroot00000000000000# The "Expert" tab at the right of the main tabbed window. # This file implements following packages: # Slic3r::GUI::Tab; # Slic3r::GUI::Tab::Print; # Slic3r::GUI::Tab::Filament; # Slic3r::GUI::Tab::Printer; # Slic3r::GUI::Tab::Page # - Option page: For example, the Slic3r::GUI::Tab::Print has option pages "Layers and perimeters", "Infill", "Skirt and brim" ... # Slic3r::GUI::SavePresetWindow # - Dialog to select a new preset name to store the configuration. # Slic3r::GUI::Tab::Preset; # - Single preset item: name, file is default or external. package Slic3r::GUI::Tab; use strict; use warnings; use utf8; use File::Basename qw(basename); use List::Util qw(first); use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :treectrl :window :button wxTheApp wxCB_READONLY); use Wx::Event qw(EVT_BUTTON EVT_COMBOBOX EVT_KEY_DOWN EVT_CHECKBOX EVT_TREE_SEL_CHANGED); use base qw(Wx::Panel Class::Accessor); sub new { my ($class, $parent, %params) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); # Vertical sizer to hold the choice menu and the rest of the page. $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); $self->{sizer}->SetSizeHints($self); $self->SetSizer($self->{sizer}); # preset chooser { # choice menu $self->{presets_choice} = Wx::BitmapComboBox->new($self, -1, "", wxDefaultPosition, [270, -1], [], wxCB_READONLY); $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->{show_incompatible_presets} = 0; $self->{bmp_show_incompatible_presets} = Wx::Bitmap->new(Slic3r::var("flag-red-icon.png"), wxBITMAP_TYPE_PNG); $self->{bmp_hide_incompatible_presets} = Wx::Bitmap->new(Slic3r::var("flag-green-icon.png"), wxBITMAP_TYPE_PNG); $self->{btn_hide_incompatible_presets} = Wx::BitmapButton->new($self, -1, $self->{bmp_hide_incompatible_presets}, 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; my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); $self->{sizer}->Add($hsizer, 0, wxBOTTOM, 3); $hsizer->Add($self->{presets_choice}, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); $hsizer->AddSpacer(4); $hsizer->Add($self->{btn_save_preset}, 0, wxALIGN_CENTER_VERTICAL); $hsizer->AddSpacer(4); $hsizer->Add($self->{btn_delete_preset}, 0, wxALIGN_CENTER_VERTICAL); $hsizer->AddSpacer(16); $hsizer->Add($self->{btn_hide_incompatible_presets}, 0, wxALIGN_CENTER_VERTICAL); } # Horizontal sizer to hold the tree and the selected page. $self->{hsizer} = Wx::BoxSizer->new(wxHORIZONTAL); $self->{sizer}->Add($self->{hsizer}, 1, wxEXPAND, 0); # left vertical sizer my $left_sizer = Wx::BoxSizer->new(wxVERTICAL); $self->{hsizer}->Add($left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); # tree $self->{treectrl} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [185, -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); # Map from an icon file name to its index in $self->{icons}. $self->{icon_index} = {}; # Index of the last icon inserted into $self->{icons}. $self->{icon_count} = -1; $self->{treectrl}->AssignImageList($self->{icons}); $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->{hsizer}->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_COMBOBOX($parent, $self->{presets_choice}, sub { $self->select_preset($self->{presets_choice}->GetStringSelection); }); EVT_BUTTON($self, $self->{btn_save_preset}, sub { $self->save_preset }); EVT_BUTTON($self, $self->{btn_delete_preset}, sub { $self->delete_preset }); EVT_BUTTON($self, $self->{btn_hide_incompatible_presets}, sub { $self->_toggle_show_hide_incompatible }); # Initialize the DynamicPrintConfig by default keys/values. # Possible %params keys: no_controller $self->build(%params); $self->rebuild_page_tree; $self->_update; return $self; } # Save the current preset into file. # This removes the "dirty" flag of the preset, possibly creates a new preset under a new name, # and activates the new preset. # Wizard calls save_preset with a name "My Settings", otherwise no name is provided and this method # opens a Slic3r::GUI::SavePresetWindow dialog. sub save_preset { my ($self, $name) = @_; # 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; if (!defined $name) { my $preset = $self->{presets}->get_selected_preset; my $default_name = $preset->default ? 'Untitled' : $preset->name; $default_name =~ s/\.[iI][nN][iI]$//; my $dlg = Slic3r::GUI::SavePresetWindow->new($self, title => lc($self->title), default => $default_name, values => [ map $_->name, grep !$_->default && !$_->external, @{$self->{presets}} ], ); return unless $dlg->ShowModal == wxID_OK; $name = $dlg->get_name; } # Save the preset into Slic3r::data_dir/presets/section_name/preset_name.ini eval { $self->{presets}->save_current_preset($name); }; Slic3r::GUI::catch_error($self) and return; # Mark the print & filament enabled if they are compatible with the currently selected preset. wxTheApp->{preset_bundle}->update_compatible_with_printer(0); # Add the new item into the UI component, remove dirty flags and activate the saved item. $self->update_tab_ui; # Update the selection boxes at the platter. $self->_on_presets_changed; } # Called for a currently selected preset. sub delete_preset { my ($self) = @_; my $current_preset = $self->{presets}->get_selected_preset; # Don't let the user delete the '- default -' configuration. my $msg = 'Are you sure you want to ' . ($current_preset->external ? 'remove' : 'delete') . ' the selected preset?'; my $title = ($current_preset->external ? 'Remove' : 'Delete') . ' Preset'; return if $current_preset->default || wxID_YES != Wx::MessageDialog->new($self, $msg, $title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION)->ShowModal; # Delete the file and select some other reasonable preset. # The 'external' presets will only be removed from the preset list, their files will not be deleted. eval { $self->{presets}->delete_current_preset; }; Slic3r::GUI::catch_error($self) and return; # Load the newly selected preset into the UI, update selection combo boxes with their dirty flags. $self->load_current_preset; } sub _toggle_show_hide_incompatible { my ($self) = @_; $self->{show_incompatible_presets} = ! $self->{show_incompatible_presets}; $self->_update_show_hide_incompatible_button; $self->update_tab_ui; } sub _update_show_hide_incompatible_button { my ($self) = @_; $self->{btn_hide_incompatible_presets}->SetBitmap($self->{show_incompatible_presets} ? $self->{bmp_show_incompatible_presets} : $self->{bmp_hide_incompatible_presets}); $self->{btn_hide_incompatible_presets}->SetToolTipString($self->{show_incompatible_presets} ? "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer."); } # Register the on_value_change callback. sub on_value_change { my ($self, $cb) = @_; $self->{on_value_change} = $cb; } # Register the on_presets_changed callback. sub on_presets_changed { my ($self, $cb) = @_; $self->{on_presets_changed} = $cb; } # This method is called whenever an option field is changed by the user. # Propagate event to the parent through the 'on_value_change' callback # and call _update. # The on_value_change callback triggers Platter::on_config_change() to configure the 3D preview # (colors, wipe tower positon etc) and to restart the background slicing process. sub _on_value_change { my ($self, $key, $value) = @_; $self->{on_value_change}->($key, $value) if $self->{on_value_change}; $self->_update; } # Override this to capture changes of configuration caused either by loading or switching a preset, # or by a user changing an option field. # This callback is useful for cross-validating configuration values of a single preset. sub _update {} # Call a callback to update the selection of presets on the platter: # To update the content of the selection boxes, # to update the filament colors of the selection boxes, # to update the "dirty" flags of the selection boxes, # to uddate number of "filament" selection boxes when the number of extruders change. sub _on_presets_changed { my ($self, $reload_dependent_tabs) = @_; $self->{on_presets_changed}->($self->{presets}, $reload_dependent_tabs) if $self->{on_presets_changed}; } # For the printer profile, generate the extruder pages after a preset is loaded. sub on_preset_loaded {} # If the current preset is dirty, the user is asked whether the changes may be discarded. # if the current preset was not dirty, or the user agreed to discard the changes, 1 is returned. sub may_discard_current_dirty_preset { my ($self, $presets, $new_printer_name) = @_; $presets //= $self->{presets}; # Display a dialog showing the dirty options in a human readable form. my $old_preset = $presets->get_current_preset; my $type_name = $presets->name; my $tab = ' '; my $name = $old_preset->default ? ('Default ' . $type_name . ' preset') : ($type_name . " preset\n$tab" . $old_preset->name); # Collect descriptions of the dirty options. my @option_names = (); foreach my $opt_key (@{$presets->current_dirty_options}) { my $opt = $Slic3r::Config::Options->{$opt_key}; my $name = $opt->{full_label} // $opt->{label}; $name = $opt->{category} . " > $name" if $opt->{category}; push @option_names, $name; } # Show a confirmation dialog with the list of dirty options. my $changes = join "\n", map "$tab$_", @option_names; my $message = (defined $new_printer_name) ? "$name\n\nis not compatible with printer\n$tab$new_printer_name\n\nand it has the following unsaved changes:" : "$name\n\nhas the following unsaved changes:"; my $confirm = Wx::MessageDialog->new($self, $message . "\n$changes\n\nDiscard changes and continue anyway?", 'Unsaved Changes', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); return $confirm->ShowModal == wxID_YES; } # Called by the UI combo box when the user switches profiles. # Select a preset by a name. If ! defined(name), then the default preset is selected. # If the current profile is modified, user is asked to save the changes. sub select_preset { my ($self, $name, $force) = @_; $force //= 0; my $presets = $self->{presets}; # If no name is provided, select the "-- default --" preset. $name //= $presets->default_preset->name; my $current_dirty = $presets->current_is_dirty; my $canceled = 0; my $printer_tab = $presets->name eq 'printer'; my @reload_dependent_tabs = (); if (! $force && $current_dirty && ! $self->may_discard_current_dirty_preset) { $canceled = 1; } elsif ($printer_tab) { # Before switching the printer to a new one, verify, whether the currently active print and filament # are compatible with the new printer. # If they are not compatible and the the current print or filament are dirty, let user decide # whether to discard the changes or keep the current printer selection. my $new_printer_preset = $presets->find_preset($name, 1); my $print_presets = wxTheApp->{preset_bundle}->print; my $print_preset_dirty = $print_presets->current_is_dirty; my $print_preset_compatible = $print_presets->get_edited_preset->is_compatible_with_printer($new_printer_preset); $canceled = ! $force && $print_preset_dirty && ! $print_preset_compatible && ! $self->may_discard_current_dirty_preset($print_presets, $name); my $filament_presets = wxTheApp->{preset_bundle}->filament; my $filament_preset_dirty = $filament_presets->current_is_dirty; my $filament_preset_compatible = $filament_presets->get_edited_preset->is_compatible_with_printer($new_printer_preset); if (! $canceled && ! $force) { $canceled = $filament_preset_dirty && ! $filament_preset_compatible && ! $self->may_discard_current_dirty_preset($filament_presets, $name); } if (! $canceled) { if (! $print_preset_compatible) { # The preset will be switched to a different, compatible preset, or the '-- default --'. push @reload_dependent_tabs, 'print'; $print_presets->discard_current_changes if $print_preset_dirty; } if (! $filament_preset_compatible) { # The preset will be switched to a different, compatible preset, or the '-- default --'. push @reload_dependent_tabs, 'filament'; $filament_presets->discard_current_changes if $filament_preset_dirty; } } } if ($canceled) { $self->update_tab_ui; # Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, # if this action was initiated from the platter. $self->_on_presets_changed; } else { $presets->discard_current_changes if $current_dirty; $presets->select_preset_by_name($name); # Mark the print & filament enabled if they are compatible with the currently selected preset. # The following method should not discard changes of current print or filament presets on change of a printer profile, # if they are compatible with the current printer. wxTheApp->{preset_bundle}->update_compatible_with_printer(1) if $current_dirty || $printer_tab; # Initialize the UI from the current preset. $self->load_current_preset(\@reload_dependent_tabs); } } # Initialize the UI from the current preset. sub load_current_preset { my ($self, $dependent_tab_names) = @_; my $preset = $self->{presets}->get_current_preset; eval { local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self); my $method = $preset->default ? 'Disable' : 'Enable'; $self->{btn_delete_preset}->$method; $self->_update; # For the printer profile, generate the extruder pages. $self->on_preset_loaded; # Reload preset pages with the new configuration values. $self->_reload_config; }; # use CallAfter because some field triggers schedule on_change calls using CallAfter, # and we don't want them to be called after this update_dirty() as they would mark the # preset dirty again # (not sure this is true anymore now that update_dirty is idempotent) wxTheApp->CallAfter(sub { $self->update_tab_ui; $self->_on_presets_changed($dependent_tab_names); }); } sub add_options_page { my ($self, $title, $icon, %params) = @_; # Index of $icon in an icon list $self->{icons}. my $icon_idx = 0; if ($icon) { $icon_idx = $self->{icon_index}->{$icon}; if (! defined $icon_idx) { # Add a new icon to the icon list. my $bitmap = Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG); $self->{icons}->Add($bitmap); $icon_idx = $self->{icon_count} + 1; $self->{icon_count} = $icon_idx; $self->{icon_index}->{$icon} = $icon_idx; } } # Initialize the page. my $page = Slic3r::GUI::Tab::Page->new($self, $title, $icon_idx); $page->Hide; $self->{hsizer}->Add($page, 1, wxEXPAND | wxLEFT, 5); push @{$self->{pages}}, $page; return $page; } # Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. sub _reload_config { my ($self) = @_; $self->Freeze; $_->reload_config for @{$self->{pages}}; $self->Thaw; } # Regerenerate content of the page tree. sub rebuild_page_tree { my ($self) = @_; $self->Freeze; # 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)); } $self->Thaw; } # Update the combo box label of the selected preset based on its "dirty" state, # comparing the selected preset config with $self->{config}. sub update_dirty { my ($self) = @_; $self->{presets}->update_dirty_ui($self->{presets_choice}); $self->_on_presets_changed; } # Load a provied DynamicConfig into the tab, modifying the active preset. # This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. sub load_config { my ($self, $config) = @_; my $modified = 0; foreach my $opt_key (@{$self->{config}->diff($config)}) { $self->{config}->set($opt_key, $config->get($opt_key)); $modified = 1; } if ($modified) { $self->update_dirty; # Initialize UI components with the config values. $self->_reload_config; $self->_update; } } # To be called by custom widgets, load a value into a config, # update the preset selection boxes (the dirty flags) sub _load_key_value { my ($self, $opt_key, $value) = @_; $self->{config}->set($opt_key, $value); # Mark the print & filament enabled if they are compatible with the currently selected preset. if ($opt_key eq 'compatible_printers') { # $opt_key eq 'compatible_printers_condition') { wxTheApp->{preset_bundle}->update_compatible_with_printer(0); } $self->{presets}->update_dirty_ui($self->{presets_choice}); $self->_on_presets_changed; $self->_update; } # Find a field with an index over all pages of this tab. # This method is used often and everywhere, therefore it shall be quick. 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; } # Set a key/value pair on this page. Return true if the value has been modified. # Currently used for distributing extruders_count over preset pages of Slic3r::GUI::Tab::Printer # after a preset is loaded. sub set_value { my ($self, $opt_key, $value) = @_; my $changed = 0; foreach my $page (@{$self->{pages}}) { $changed = 1 if $page->set_value($opt_key, $value); } return $changed; } # Return a callback to create a Tab widget to mark the preferences as compatible / incompatible to the current printer. 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); $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 { my $method = $checkbox->GetValue ? 'Disable' : 'Enable'; $btn->$method; # All printers have been made compatible with this preset. $self->_load_key_value('compatible_printers', []) if $checkbox->GetValue; $self->get_field('compatible_printers_condition')->toggle($checkbox->GetValue); }); EVT_BUTTON($self, $btn, sub { # Collect names of non-default non-external printer profiles. my @presets = map $_->name, grep !$_->default && !$_->external, @{wxTheApp->{preset_bundle}->printer}; my $dlg = Wx::MultiChoiceDialog->new($self, "Select the printers this profile is compatible with.", "Compatible printers", \@presets); # Collect and set indices of printers marked as compatible. my @selections = (); foreach my $preset_name (@{ $self->{config}->get('compatible_printers') }) { my $idx = first { $presets[$_] eq $preset_name } 0..$#presets; push @selections, $idx if defined $idx; } $dlg->SetSelections(@selections); # Show the dialog. if ($dlg->ShowModal == wxID_OK) { my $value = [ @presets[$dlg->GetSelections] ]; if (!@$value) { $checkbox->SetValue(1); $self->get_field('compatible_printers_condition')->toggle(1); $btn->Disable; } # All printers have been made compatible with this preset. $self->_load_key_value('compatible_printers', $value); } }); return $sizer; }; } sub _reload_compatible_printers_widget { my ($self) = @_; my $has_any = int(@{$self->{config}->get('compatible_printers')}) > 0; my $method = $has_any ? 'Enable' : 'Disable'; $self->{compatible_printers_checkbox}->SetValue(! $has_any); $self->{compatible_printers_btn}->$method; $self->get_field('compatible_printers_condition')->toggle(! $has_any); } sub update_ui_from_settings { my ($self) = @_; # Show the 'show / hide presets' button only for the print and filament tabs, and only if enabled # in application preferences. my $show = wxTheApp->{app_config}->get("show_incompatible_presets") && $self->{presets}->name ne 'printer'; my $method = $show ? 'Show' : 'Hide'; $self->{btn_hide_incompatible_presets}->$method; # If the 'show / hide presets' button is hidden, hide the incompatible presets. if ($show) { $self->_update_show_hide_incompatible_button; } else { if ($self->{show_incompatible_presets}) { $self->{show_incompatible_presets} = 0; $self->update_tab_ui; } } } sub update_tab_ui { my ($self) = @_; $self->{presets}->update_tab_ui($self->{presets_choice}, $self->{show_incompatible_presets}) } package Slic3r::GUI::Tab::Print; use base 'Slic3r::GUI::Tab'; use List::Util qw(first); use Wx qw(:icon :dialog :id wxTheApp); sub name { 'print' } sub title { 'Print Settings' } sub build { my $self = shift; $self->{presets} = wxTheApp->{preset_bundle}->print; $self->{config} = $self->{presets}->get_edited_preset->config; { 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'); } { my $optgroup = $page->new_optgroup('Vertical shells'); $optgroup->append_single_option_line('perimeters'); $optgroup->append_single_option_line('spiral_vase'); my $line = Slic3r::GUI::OptionsGroup::Line->new( label => '', full_width => 1, widget => sub { my ($parent) = @_; return $self->{recommended_thin_wall_thickness_description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent); }, ); $optgroup->append_line($line); } { 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('ensure_vertical_shell_thickness'); $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'); $optgroup->append_single_option_line('external_fill_pattern'); } { 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('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('bridge_angle'); $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'); } } { 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_enforce_layers'); } { my $optgroup = $page->new_optgroup('Raft'); $optgroup->append_single_option_line('raft_layers'); # $optgroup->append_single_option_line('raft_contact_distance'); } { 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_with_sheath'); $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_interface_contact_loops'); $optgroup->append_single_option_line('support_material_buildplate_only'); $optgroup->append_single_option_line('support_material_xy_spacing'); $optgroup->append_single_option_line('dont_support_bridges'); $optgroup->append_single_option_line('support_material_synchronize_layers'); } } { my $page = $self->add_options_page('Speed', 'time.png'); { my $optgroup = $page->new_optgroup('Speed for print moves'); $optgroup->append_single_option_line('perimeter_speed'); $optgroup->append_single_option_line('small_perimeter_speed'); $optgroup->append_single_option_line('external_perimeter_speed'); $optgroup->append_single_option_line('infill_speed'); $optgroup->append_single_option_line('solid_infill_speed'); $optgroup->append_single_option_line('top_solid_infill_speed'); $optgroup->append_single_option_line('support_material_speed'); $optgroup->append_single_option_line('support_material_interface_speed'); $optgroup->append_single_option_line('bridge_speed'); $optgroup->append_single_option_line('gap_fill_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'); $optgroup->append_single_option_line('max_volumetric_extrusion_rate_slope_positive'); $optgroup->append_single_option_line('max_volumetric_extrusion_rate_slope_negative'); } } { 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('Wipe tower'); $optgroup->append_single_option_line('wipe_tower'); $optgroup->append_single_option_line('wipe_tower_x'); $optgroup->append_single_option_line('wipe_tower_y'); $optgroup->append_single_option_line('wipe_tower_width'); $optgroup->append_single_option_line('wipe_tower_per_color_wipe'); } { my $optgroup = $page->new_optgroup('Advanced'); $optgroup->append_single_option_line('interface_shells'); } } { my $page = $self->add_options_page('Advanced', 'wrench.png'); { my $optgroup = $page->new_optgroup('Extrusion width', label_width => 180, ); $optgroup->append_single_option_line('extrusion_width'); $optgroup->append_single_option_line('first_layer_extrusion_width'); $optgroup->append_single_option_line('perimeter_extrusion_width'); $optgroup->append_single_option_line('external_perimeter_extrusion_width'); $optgroup->append_single_option_line('infill_extrusion_width'); $optgroup->append_single_option_line('solid_infill_extrusion_width'); $optgroup->append_single_option_line('top_infill_extrusion_width'); $optgroup->append_single_option_line('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('clip_multipart_objects'); $optgroup->append_single_option_line('elefant_foot_compensation'); $optgroup->append_single_option_line('xy_size_compensation'); # $optgroup->append_single_option_line('threads'); $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('Dependencies', 'wrench.png'); { my $optgroup = $page->new_optgroup('Profile dependencies'); { my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Compatible printers', widget => $self->_compatible_printers_widget, ); $optgroup->append_line($line); my $option = $optgroup->get_option('compatible_printers_condition'); $option->full_width(1); $optgroup->append_single_option_line($option); } } } } # Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. sub _reload_config { my ($self) = @_; $self->_reload_compatible_printers_widget; $self->SUPER::_reload_config; } # Slic3r::GUI::Tab::Print::_update is called after a configuration preset is loaded or switched, or when a single option is modifed by the user. sub _update { my ($self) = @_; $self->Freeze; my $config = $self->{config}; if ($config->spiral_vase && !($config->perimeters == 1 && $config->top_solid_layers == 0 && $config->fill_density == 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" . "- no ensure_vertical_shell_thickness\n" . "\nShall I adjust those settings in order to enable Spiral Vase?", 'Spiral Vase', wxICON_WARNING | wxYES | wxNO); my $new_conf = Slic3r::Config->new; if ($dialog->ShowModal() == wxID_YES) { $new_conf->set("perimeters", 1); $new_conf->set("top_solid_layers", 0); $new_conf->set("fill_density", 0); $new_conf->set("support_material", 0); $new_conf->set("ensure_vertical_shell_thickness", 0); } else { $new_conf->set("spiral_vase", 0); } $self->load_config($new_conf); } if ($config->wipe_tower && ($config->first_layer_height != 0.2 || $config->layer_height < 0.15 || $config->layer_height > 0.35)) { my $dialog = Wx::MessageDialog->new($self, "The Wipe Tower currently supports only:\n" . "- first layer height 0.2mm\n" . "- layer height from 0.15mm to 0.35mm\n" . "\nShall I adjust those settings in order to enable the Wipe Tower?", 'Wipe Tower', wxICON_WARNING | wxYES | wxNO); my $new_conf = Slic3r::Config->new; if ($dialog->ShowModal() == wxID_YES) { $new_conf->set("first_layer_height", 0.2); $new_conf->set("layer_height", 0.15) if $config->layer_height < 0.15; $new_conf->set("layer_height", 0.35) if $config->layer_height > 0.35; } else { $new_conf->set("wipe_tower", 0); } $self->load_config($new_conf); } if ($config->wipe_tower && $config->support_material && $config->support_material_contact_distance > 0. && ($config->support_material_extruder != 0 || $config->support_material_interface_extruder != 0)) { my $dialog = Wx::MessageDialog->new($self, "The Wipe Tower currently supports the non-soluble supports only\n" . "if they are printed with the current extruder without triggering a tool change.\n" . "(both support_material_extruder and support_material_interface_extruder need to be set to 0).\n" . "\nShall I adjust those settings in order to enable the Wipe Tower?", 'Wipe Tower', wxICON_WARNING | wxYES | wxNO); my $new_conf = Slic3r::Config->new; if ($dialog->ShowModal() == wxID_YES) { $new_conf->set("support_material_extruder", 0); $new_conf->set("support_material_interface_extruder", 0); } else { $new_conf->set("wipe_tower", 0); } $self->load_config($new_conf); } if ($config->wipe_tower && $config->support_material && $config->support_material_contact_distance == 0 && ! $config->support_material_synchronize_layers) { my $dialog = Wx::MessageDialog->new($self, "For the Wipe Tower to work with the soluble supports, the support layers\n" . "need to be synchronized with the object layers.\n" . "\nShall I synchronize support layers in order to enable the Wipe Tower?", 'Wipe Tower', wxICON_WARNING | wxYES | wxNO); my $new_conf = Slic3r::Config->new; if ($dialog->ShowModal() == wxID_YES) { $new_conf->set("support_material_synchronize_layers", 1); } else { $new_conf->set("wipe_tower", 0); } $self->load_config($new_conf); } if ($config->support_material) { # Ask only once. if (! $self->{support_material_overhangs_queried}) { $self->{support_material_overhangs_queried} = 1; if ($config->overhangs != 1) { my $dialog = Wx::MessageDialog->new($self, "Supports work better, if the following feature is enabled:\n" . "- Detect bridging perimeters\n" . "\nShall I adjust those settings for supports?", 'Support Generator', wxICON_WARNING | wxYES | wxNO | wxCANCEL); my $answer = $dialog->ShowModal(); my $new_conf = Slic3r::Config->new; if ($answer == wxID_YES) { # Enable "detect bridging perimeters". $new_conf->set("overhangs", 1); } elsif ($answer == wxID_NO) { # Do nothing, leave supports on and "detect bridging perimeters" off. } elsif ($answer == wxID_CANCEL) { # Disable supports. $new_conf->set("support_material", 0); $self->{support_material_overhangs_queried} = 0; } $self->load_config($new_conf); } } } else { $self->{support_material_overhangs_queried} = 0; } if ($config->fill_density == 100 && !first { $_ eq $config->fill_pattern } @{$Slic3r::Config::Options->{external_fill_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'); $new_conf->set("fill_density", 100); } else { $new_conf->set("fill_density", 40); } $self->load_config($new_conf); } my $have_perimeters = $config->perimeters > 0; $self->get_field($_)->toggle($have_perimeters) for qw(extra_perimeters ensure_vertical_shell_thickness thin_walls overhangs seam_position external_perimeters_first external_perimeter_extrusion_width perimeter_speed small_perimeter_speed external_perimeter_speed); my $have_infill = $config->fill_density > 0; # infill_extruder uses the same logic as in Print::extruders() $self->get_field($_)->toggle($have_infill) for qw(fill_pattern infill_every_layers infill_only_where_needed solid_infill_every_layers solid_infill_below_area infill_extruder); my $have_solid_infill = ($config->top_solid_layers > 0) || ($config->bottom_solid_layers > 0); # solid_infill_extruder uses the same logic as in Print::extruders() $self->get_field($_)->toggle($have_solid_infill) for qw(external_fill_pattern infill_first solid_infill_extruder solid_infill_extrusion_width solid_infill_speed); $self->get_field($_)->toggle($have_infill || $have_solid_infill) for qw(fill_angle bridge_angle infill_extrusion_width infill_speed bridge_speed); $self->get_field('gap_fill_speed')->toggle($have_perimeters && $have_infill); 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_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; # perimeter_extruder uses the same logic as in Print::extruders() $self->get_field('perimeter_extruder')->toggle($have_perimeters || $have_brim); my $have_raft = $config->raft_layers > 0; my $have_support_material = $config->support_material || $have_raft; my $have_support_interface = $config->support_material_interface_layers > 0; my $have_support_soluble = $have_support_material && $config->support_material_contact_distance == 0; $self->get_field($_)->toggle($have_support_material) for qw(support_material_threshold support_material_pattern support_material_with_sheath support_material_spacing support_material_angle support_material_interface_layers dont_support_bridges support_material_extrusion_width support_material_contact_distance support_material_xy_spacing); $self->get_field($_)->toggle($have_support_material && $have_support_interface) for qw(support_material_interface_spacing support_material_interface_extruder support_material_interface_speed support_material_interface_contact_loops); $self->get_field('support_material_synchronize_layers')->toggle($have_support_soluble); $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); my $have_wipe_tower = $config->wipe_tower; $self->get_field($_)->toggle($have_wipe_tower) for qw(wipe_tower_x wipe_tower_y wipe_tower_width wipe_tower_per_color_wipe); $self->{recommended_thin_wall_thickness_description_line}->SetText( Slic3r::GUI::PresetHints::recommended_thin_wall_thickness(wxTheApp->{preset_bundle})); $self->Thaw; } # Update on activation to recalculate the estimates if the nozzle diameter changed # and the extrusion width values were left to zero (automatic, nozzle dependent). sub OnActivate { my ($self) = @_; $self->{recommended_thin_wall_thickness_description_line}->SetText( Slic3r::GUI::PresetHints::recommended_thin_wall_thickness(wxTheApp->{preset_bundle})); } package Slic3r::GUI::Tab::Filament; use base 'Slic3r::GUI::Tab'; use Wx qw(wxTheApp); sub name { 'filament' } sub title { 'Filament Settings' } sub build { my $self = shift; $self->{presets} = wxTheApp->{preset_bundle}->filament; $self->{config} = $self->{presets}->get_edited_preset->config; { 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); $optgroup->append_single_option_line('filament_density', 0); $optgroup->append_single_option_line('filament_cost', 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', 0)); $line->append_option($optgroup->get_option('bed_temperature', 0)); $optgroup->append_line($line); } } } { my $page = $self->add_options_page('Cooling', 'hourglass.png'); { my $optgroup = $page->new_optgroup('Enable'); $optgroup->append_single_option_line('fan_always_on', 0); $optgroup->append_single_option_line('cooling', 0); my $line = Slic3r::GUI::OptionsGroup::Line->new( label => '', full_width => 1, widget => sub { my ($parent) = @_; return $self->{cooling_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', 0)); $line->append_option($optgroup->get_option('max_fan_speed', 0)); $optgroup->append_line($line); } $optgroup->append_single_option_line('bridge_fan_speed', 0); $optgroup->append_single_option_line('disable_fan_first_layers', 0); } { my $optgroup = $page->new_optgroup('Cooling thresholds', label_width => 250, ); $optgroup->append_single_option_line('fan_below_layer_time', 0); $optgroup->append_single_option_line('slowdown_below_layer_time', 0); $optgroup->append_single_option_line('min_print_speed', 0); } } { my $page = $self->add_options_page('Advanced', 'wrench.png'); { my $optgroup = $page->new_optgroup('Filament properties'); $optgroup->append_single_option_line('filament_type', 0); $optgroup->append_single_option_line('filament_soluble', 0); $optgroup = $page->new_optgroup('Print speed override'); $optgroup->append_single_option_line('filament_max_volumetric_speed', 0); my $line = Slic3r::GUI::OptionsGroup::Line->new( label => '', full_width => 1, widget => sub { my ($parent) = @_; return $self->{volumetric_speed_description_line} = Slic3r::GUI::OptionsGroup::StaticText->new($parent); }, ); $optgroup->append_line($line); } } { my $page = $self->add_options_page('Custom G-code', 'cog.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('Dependencies', 'wrench.png'); { my $optgroup = $page->new_optgroup('Profile dependencies'); { my $line = Slic3r::GUI::OptionsGroup::Line->new( label => 'Compatible printers', widget => $self->_compatible_printers_widget, ); $optgroup->append_line($line); my $option = $optgroup->get_option('compatible_printers_condition'); $option->full_width(1); $optgroup->append_single_option_line($option); } } } } # Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. sub _reload_config { my ($self) = @_; $self->_reload_compatible_printers_widget; $self->SUPER::_reload_config; } # Slic3r::GUI::Tab::Filament::_update is called after a configuration preset is loaded or switched, or when a single option is modifed by the user. sub _update { my ($self) = @_; $self->{cooling_description_line}->SetText( Slic3r::GUI::PresetHints::cooling_description($self->{presets}->get_edited_preset)); $self->{volumetric_speed_description_line}->SetText( Slic3r::GUI::PresetHints::maximum_volumetric_flow_description(wxTheApp->{preset_bundle})); my $cooling = $self->{config}->cooling->[0]; my $fan_always_on = $cooling || $self->{config}->fan_always_on->[0]; $self->get_field($_, 0)->toggle($cooling) for qw(max_fan_speed fan_below_layer_time slowdown_below_layer_time min_print_speed); $self->get_field($_, 0)->toggle($fan_always_on) for qw(min_fan_speed disable_fan_first_layers); } sub OnActivate { my ($self) = @_; $self->{volumetric_speed_description_line}->SetText( Slic3r::GUI::PresetHints::maximum_volumetric_flow_description(wxTheApp->{preset_bundle})); } package Slic3r::GUI::Tab::Printer; use base 'Slic3r::GUI::Tab'; 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 build { my ($self, %params) = @_; $self->{presets} = wxTheApp->{preset_bundle}->printer; $self->{config} = $self->{presets}->get_edited_preset->config; $self->{extruders_count} = scalar @{$self->{config}->nozzle_diameter}; my $bed_shape_widget = sub { my ($parent) = @_; my $btn = Wx::Button->new($parent, -1, "Set…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); $btn->SetFont($Slic3r::GUI::small_font); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("printer_empty.png"), wxBITMAP_TYPE_PNG)); my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); $sizer->Add($btn); EVT_BUTTON($self, $btn, sub { my $dlg = Slic3r::GUI::BedShapeDialog->new($self, $self->{config}->bed_shape); $self->_load_key_value('bed_shape', $dlg->GetValue) if $dlg->ShowModal == wxID_OK; }); return $sizer; }; { 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', widget => $bed_shape_widget, ); $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('single_extruder_multi_material'); } $optgroup->on_change(sub { my ($opt_key, $value) = @_; wxTheApp->CallAfter(sub { if ($opt_key eq 'extruders_count') { $self->_extruders_count_changed($optgroup->get_value('extruders_count')); $self->update_dirty; } else { $self->update_dirty; $self->_on_value_change($opt_key, $value); } }); }); } if (!$params{no_controller}) { 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; }); my $serial_test = sub { my ($parent) = @_; my $btn = $self->{serial_test_btn} = Wx::Button->new($parent, -1, "Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); $btn->SetFont($Slic3r::GUI::small_font); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("wrench.png"), wxBITMAP_TYPE_PNG)); 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_info($self, "Connection to printer works correctly.", "Success!"); } else { Slic3r::GUI::show_error($self, "Connection failed."); } }); return $btn; }; $line->append_option($serial_port); $line->append_option($optgroup->get_option('serial_speed')); $line->append_widget($serial_test); $optgroup->append_line($line); } { my $optgroup = $page->new_optgroup('OctoPrint upload'); # append two buttons to the Host line my $octoprint_host_browse = sub { my ($parent) = @_; my $btn = Wx::Button->new($parent, -1, "Browse…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $btn->SetFont($Slic3r::GUI::small_font); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("zoom.png"), wxBITMAP_TYPE_PNG)); if (!eval "use Net::Bonjour; 1") { $btn->Disable; } EVT_BUTTON($self, $btn, sub { # look for devices my $entries; { my $res = Net::Bonjour->new('http'); $res->discover; $entries = [ $res->entries ]; } if (@{$entries}) { my $dlg = Slic3r::GUI::BonjourBrowser->new($self, $entries); $self->_load_key_value('octoprint_host', $dlg->GetValue . ":" . $dlg->GetPort) if $dlg->ShowModal == wxID_OK; } else { Wx::MessageDialog->new($self, 'No Bonjour device found', 'Device Browser', wxOK | wxICON_INFORMATION)->ShowModal; } }); return $btn; }; my $octoprint_host_test = sub { my ($parent) = @_; my $btn = $self->{octoprint_host_test_btn} = Wx::Button->new($parent, -1, "Test", wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); $btn->SetFont($Slic3r::GUI::small_font); $btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("wrench.png"), wxBITMAP_TYPE_PNG)); EVT_BUTTON($self, $btn, sub { my $ua = LWP::UserAgent->new; $ua->timeout(10); my $res = $ua->get( "http://" . $self->{config}->octoprint_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)."); } }); return $btn; }; my $host_line = $optgroup->create_single_option_line('octoprint_host'); $host_line->append_widget($octoprint_host_browse); $host_line->append_widget($octoprint_host_test); $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('variable_layer_height'); } } { my $page = $self->add_options_page('Custom G-code', 'cog.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); } } { 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->{extruder_pages} = []; $self->_build_extruder_pages; $self->_update_serial_ports if (!$params{no_controller}); } sub _update_serial_ports { my ($self) = @_; $self->get_field('serial_port')->set_values([ Slic3r::GUI::scan_serial_ports ]); } sub _extruders_count_changed { my ($self, $extruders_count) = @_; $self->{extruders_count} = $extruders_count; wxTheApp->{preset_bundle}->printer->get_edited_preset->set_num_extruders($extruders_count); wxTheApp->{preset_bundle}->update_multi_material_filament_presets; $self->_build_extruder_pages; $self->_on_value_change('extruders_count', $extruders_count); } sub _build_extruder_pages { my ($self) = @_; my $default_config = Slic3r::Config::Full->new; foreach my $extruder_idx (@{$self->{extruder_pages}} .. $self->{extruders_count}-1) { # 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('Layer height 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 deretract_speed retract_restart_extra retract_before_travel retract_layer_change wipe retract_before_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); } { my $optgroup = $page->new_optgroup('Preview'); $optgroup->append_single_option_line('extruder_colour', $extruder_idx); } } # 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}; } # rebuild page list my @pages_without_extruders = (grep $_->{title} !~ /^Extruder \d+/, @{$self->{pages}}); my $page_notes = pop @pages_without_extruders; @{$self->{pages}} = ( @pages_without_extruders, @{$self->{extruder_pages}}[ 0 .. $self->{extruders_count}-1 ], $page_notes ); $self->rebuild_page_tree; } # Slic3r::GUI::Tab::Printer::_update is called after a configuration preset is loaded or switched, or when a single option is modifed by the user. sub _update { my ($self) = @_; $self->Freeze; 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('octoprint_host') && eval "use LWP::UserAgent; 1") { $self->{octoprint_host_test_btn}->Enable; } else { $self->{octoprint_host_test_btn}->Disable; } $self->get_field('octoprint_apikey')->toggle($config->get('octoprint_host')); my $have_multiple_extruders = $self->{extruders_count} > 1; $self->get_field('toolchange_gcode')->toggle($have_multiple_extruders); $self->get_field('single_extruder_multi_material')->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); # 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 my $retraction = ($have_retract_length || $config->use_firmware_retraction); $self->get_field($_, $i)->toggle($retraction) for qw(retract_lift 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_speed deretract_speed retract_before_wipe retract_restart_extra wipe); my $wipe = $config->get_at('wipe', $i); $self->get_field('retract_before_wipe', $i)->toggle($wipe); if ($config->use_firmware_retraction && $wipe) { 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); } $self->get_field('retract_length_toolchange', $i)->toggle($have_multiple_extruders); 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); } $self->Thaw; } # this gets executed after preset is loaded and before GUI fields are updated sub on_preset_loaded { my ($self) = @_; # update the extruders count field my $extruders_count = scalar @{ $self->{config}->nozzle_diameter }; $self->set_value('extruders_count', $extruders_count); # update the GUI field according to the number of nozzle diameters supplied $self->_extruders_count_changed($extruders_count); } # Single Tab page containing a {vsizer} of {optgroups} package Slic3r::GUI::Tab::Page; use Wx qw(wxTheApp :misc :panel :sizer); use base 'Wx::ScrolledWindow'; sub new { my ($class, $parent, $title, $iconID) = @_; my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); $self->{optgroups} = []; $self->{title} = $title; $self->{iconID} = $iconID; $self->SetScrollbars(1, 1, 1, 1); $self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL); $self->SetSizer($self->{vsizer}); 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->update_dirty; $self->GetParent->_on_value_change($opt_key, $value); }); }, ); 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, $opt_key, $value) = @_; my $changed = 0; foreach my $optgroup (@{$self->{optgroups}}) { $changed = 1 if $optgroup->set_value($opt_key, $value); } return $changed; } # Dialog to select a new file name for a modified preset to be saved. # Called from Tab::save_preset(). 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, $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 " . lc($params{title}) . " 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} = Slic3r::normalize_utf8_nfc($self->{combo}->GetValue))) { if ($self->{chosen_name} !~ /^[^<>:\/\\|?*\"]+$/) { 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) = @_; return $self->{chosen_name}; } 1; Slic3r-version_1.39.1/lib/Slic3r/Geometry.pm000066400000000000000000000171471324354444700205730ustar00rootroot00000000000000package Slic3r::Geometry; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); # Exported by this module. The last section starting with convex_hull is exported by Geometry.xsp our @EXPORT_OK = qw( PI epsilon angle3points collinear dot line_intersection normalize point_in_segment polyline_lines polygon_is_convex polygon_segment_having_point scale unscale scaled_epsilon size_2D X Y Z convex_hull chained_path_from deg2rad rad2deg rad2deg_dir ); 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; sub epsilon () { 1E-4 } sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR } sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR } # used by geometry.t, polygon_segment_having_point 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; } # used by geometry.t sub polyline_lines { my ($polyline) = @_; my @points = @$polyline; return map Slic3r::Line->new(@points[$_, $_+1]), 0 .. $#points-1; } # given a $polygon, return the (first) segment having $point # used by geometry.t sub polygon_segment_having_point { my ($polygon, $point) = @_; foreach my $line (@{ $polygon->lines }) { return $line if point_in_segment($point, $line); } return undef; } # 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 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 # used by 3DScene.pm sub dot { my ($u, $v) = @_; return $u->[X] * $v->[X] + $u->[Y] * $v->[Y]; } 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]; } # 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 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 sub collinear. 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; } # 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; } 1; Slic3r-version_1.39.1/lib/Slic3r/Geometry/000077500000000000000000000000001324354444700202235ustar00rootroot00000000000000Slic3r-version_1.39.1/lib/Slic3r/Geometry/Clipper.pm000066400000000000000000000004461324354444700221630ustar00rootroot00000000000000package Slic3r::Geometry::Clipper; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw( offset offset2 offset_ex offset2_ex diff_ex diff union_ex intersection_ex JT_ROUND JT_MITER JT_SQUARE intersection intersection_pl diff_pl union); 1; Slic3r-version_1.39.1/lib/Slic3r/Layer.pm000066400000000000000000000013071324354444700200430ustar00rootroot00000000000000# 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-version_1.39.1/lib/Slic3r/Line.pm000066400000000000000000000005571324354444700176640ustar00rootroot00000000000000package 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-version_1.39.1/lib/Slic3r/Model.pm000066400000000000000000000100251324354444700200240ustar00rootroot00000000000000# extends C++ class Slic3r::Model package Slic3r::Model; use List::Util qw(first max any); 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 List::Util qw(first sum); 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; 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_scaling_factor($args{scaling_factor}) if defined $args{scaling_factor}; $new_instance->set_offset($args{offset}) if defined $args{offset}; return $new_instance; } } sub mesh_stats { my $self = shift; # TODO: sum values from all volumes return $self->volumes->[0]->mesh->stats; } 1; Slic3r-version_1.39.1/lib/Slic3r/Point.pm000066400000000000000000000010501324354444700200530ustar00rootroot00000000000000package 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-version_1.39.1/lib/Slic3r/Polygon.pm000066400000000000000000000003161324354444700204150ustar00rootroot00000000000000package Slic3r::Polygon; use strict; use warnings; # a polygon is a closed polyline. use parent 'Slic3r::Polyline'; sub grow { my $self = shift; return $self->split_at_first_point->grow(@_); } 1;Slic3r-version_1.39.1/lib/Slic3r/Polyline.pm000066400000000000000000000006431324354444700205640ustar00rootroot00000000000000package 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-version_1.39.1/lib/Slic3r/Print.pm000066400000000000000000000231321324354444700200630ustar00rootroot00000000000000# 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 unscale); use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex intersection offset union JT_ROUND JT_SQUARE); use Slic3r::Print::State ':steps'; our $status_cb; sub set_status_cb { my ($class, $cb) = @_; $status_cb = $cb; } sub status_cb { return $status_cb // sub {}; } sub size { my $self = shift; return $self->bounding_box->size; } # Slicing process, running at a background thread. sub process { my ($self) = @_; Slic3r::trace(3, "Staring the slicing process."); $_->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 $self->make_wipe_tower; # 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"); } Slic3r::trace(3, "Slicing process finished.") } # G-code export process, running at a background thread. # The export_gcode may die for various reasons (fails to process output_filename_format, # write error into the G-code, cannot execute post-processing scripts). # It is up to the caller to show an error message. sub export_gcode { my $self = shift; my %params = @_; # prerequisites $self->process; # output everything to a G-code file # The following call may die if the output_filename_format template substitution fails. my $output_file = $self->output_filepath($params{output_file} // ''); $self->status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : "")); # The following line may die for multiple reasons. Slic3r::GCode->new->do_export($self, $output_file); # 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; # -x doesn't return true on Windows except for .exe files if (($^O eq 'MSWin32') ? !(-e $script) : !(-x $script)) { die "The configured post-processing script is not executable: check permissions. ($script)\n"; } if ($^O eq 'MSWin32' && $script =~ /\.[pP][lL]/) { system($^X, $script, $output_file); } else { system($script, $output_file); } } } } # Export SVG slices for the offline SLA printing. # The export_svg is expected to be executed inside an eval block. sub export_svg { my $self = shift; my %params = @_; $_->slice for @{$self->objects}; my $fh = $params{output_fh}; if (!$fh) { # The following line may die if the output_filename_format template substitution fails. my $output_file = $self->output_filepath($params{output_file}); $output_file =~ s/\.[gG][cC][oO][dD][eE]$/.svg/; 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]); EOF my $print_polygon = sub { my ($polygon, $type) = @_; printf $fh qq{ \n}, $type, (join ' ', map { join ',', map unscale $_, @$_ } @$polygon), ($type eq 'contour' ? 'white' : 'black'); }; my @layers = sort { $a->print_z <=> $b->print_z } map { @{$_->layers}, @{$_->support_layers} } @{$self->objects}; my $layer_id = -1; my @previous_layer_slices = (); for my $layer (@layers) { $layer_id++; if ($layer->slice_z == -1) { printf $fh qq{ \n}, $layer_id; } else { printf $fh qq{ \n}, $layer_id, unscale($layer->slice_z); } my @current_layer_slices = (); # sort slices so that the outermost ones come first my @slices = sort { $a->contour->contains_point($b->contour->first_point) ? 0 : 1 } @{$layer->slices}; foreach my $copy (@{$layer->object->_shifted_copies}) { foreach my $slice (@slices) { my $expolygon = $slice->clone; $expolygon->translate(@$copy); $expolygon->translate(-$print_bb->x_min, -$print_bb->y_min); $print_polygon->($expolygon->contour, 'contour'); $print_polygon->($_, 'hole') for @{$expolygon->holes}; push @current_layer_slices, $expolygon; } } # generate support material if ($self->has_support_material && $layer->id > 0) { my (@supported_slices, @unsupported_slices) = (); foreach my $expolygon (@current_layer_slices) { my $intersection = intersection_ex( [ map @$_, @previous_layer_slices ], [ @$expolygon ], ); @$intersection ? push @supported_slices, $expolygon : push @unsupported_slices, $expolygon; } my @supported_points = map @$_, @$_, @supported_slices; foreach my $expolygon (@unsupported_slices) { # look for the nearest point to this island among all # supported points my $contour = $expolygon->contour; my $support_point = $contour->first_point->nearest_point(\@supported_points) or next; my $anchor_point = $support_point->nearest_point([ @$contour ]); printf $fh qq{ \n}, map @$_, $support_point, $anchor_point; } } print $fh qq{ \n}; @previous_layer_slices = @current_layer_slices; } print $fh "\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); $self->skirt->clear; if ($self->has_skirt) { $self->status_cb->(88, "Generating skirt"); $self->_make_skirt(); } $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; return if $self->step_done(STEP_BRIM); $self->set_step_started(STEP_BRIM); # since this method must be idempotent, we clear brim paths *before* # checking whether we need to generate them $self->brim->clear; if ($self->config->brim_width > 0) { $self->status_cb->(88, "Generating brim"); $self->_make_brim; } $self->set_step_done(STEP_BRIM); } sub make_wipe_tower { my $self = shift; # prerequisites $_->make_perimeters for @{$self->objects}; $_->infill for @{$self->objects}; $_->generate_support_material for @{$self->objects}; $self->make_skirt; $self->make_brim; return if $self->step_done(STEP_WIPE_TOWER); $self->set_step_started(STEP_WIPE_TOWER); $self->_clear_wipe_tower; if ($self->has_wipe_tower) { # $self->status_cb->(95, "Generating wipe tower"); $self->_make_wipe_tower; } $self->set_step_done(STEP_WIPE_TOWER); } 1; Slic3r-version_1.39.1/lib/Slic3r/Print/000077500000000000000000000000001324354444700175245ustar00rootroot00000000000000Slic3r-version_1.39.1/lib/Slic3r/Print/Object.pm000066400000000000000000000066001324354444700212720ustar00rootroot00000000000000package Slic3r::Print::Object; # extends c++ class Slic3r::PrintObject (Print.xsp) use strict; use warnings; use List::Util qw(min max sum first); use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(scale epsilon); use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex offset offset2 offset_ex offset2_ex JT_MITER); use Slic3r::Print::State ':steps'; use Slic3r::Surface ':types'; 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; my $warning = $self->_fix_slicing_errors; warn $warning if (defined($warning) && $warning ne ''); # simplify slices if required $self->_simplify_slices(scale($self->print->config->resolution)) if ($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_step_done(STEP_SLICE); } # 1) Merges typed region slices into stInternal type. # 2) Increases an "extra perimeters" counter at region slices where needed. # 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal). sub make_perimeters { my ($self) = @_; # prerequisites $self->slice; if (! $self->step_done(STEP_PERIMETERS)) { $self->print->status_cb->(20, "Generating perimeters"); $self->_make_perimeters; } } sub prepare_infill { my ($self) = @_; # prerequisites $self->make_perimeters; return if $self->step_done(STEP_PREPARE_INFILL); $self->set_step_started(STEP_PREPARE_INFILL); $self->print->status_cb->(30, "Preparing infill"); $self->_prepare_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) && scalar(@{$self->layers}) > 1) { $self->print->status_cb->(85, "Generating support material"); # New supports, C++ implementation. $self->_generate_support_material; } $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); } 1; Slic3r-version_1.39.1/lib/Slic3r/Print/Simple.pm000066400000000000000000000065001324354444700213140ustar00rootroot00000000000000# 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); has '_print' => ( is => 'ro', default => sub { Slic3r::Print->new }, handles => [qw(apply_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', default => sub { Slic3r::Pointf->new(100,100) }, ); has 'dont_arrange' => ( is => 'rw', default => sub { 0 }, ); has 'output_file' => ( is => 'rw', ); 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); } 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); } 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); } $_->translate(0,0,-$_->bounding_box->z_min) for @{$model->objects}; $model->center_instances_around_point($self->print_center) if (! $self->dont_arrange); 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) = @_; $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-version_1.39.1/lib/Slic3r/Print/State.pm000066400000000000000000000005651324354444700211500ustar00rootroot00000000000000# 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_SLICE STEP_PERIMETERS STEP_PREPARE_INFILL STEP_INFILL STEP_SUPPORTMATERIAL STEP_SKIRT STEP_BRIM STEP_WIPE_TOWER); our %EXPORT_TAGS = (steps => \@EXPORT_OK); 1; Slic3r-version_1.39.1/lib/Slic3r/SVG.pm000066400000000000000000000101601324354444700174230ustar00rootroot00000000000000package 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-version_1.39.1/lib/Slic3r/Surface.pm000066400000000000000000000005421324354444700203570ustar00rootroot00000000000000package 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-version_1.39.1/lib/Slic3r/Test.pm000066400000000000000000002760461324354444700177240ustar00rootroot00000000000000package Slic3r::Test; use strict; use warnings; use Cwd 'abs_path'; 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] ]; } 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 $input_file = "${model_name}.stl"; my $mesh = mesh($model_name, %params); # $mesh->write_ascii("out/$input_file"); my $model = Slic3r::Model->new; my $object = $model->add_object(input_file => $input_file); $model->set_material($model_name); $object->add_volume(mesh => $mesh, 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); } } # Call apply_config one more time, so that the layer height profiles are updated over all PrintObjects. $print->apply_config($config); $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'); # Write the resulting G-code into a temporary file. my $gcode_temp_path = abs_path($0) . '.gcode.temp'; # Remove the existing temp file. unlink $gcode_temp_path; $print->process; $print->export_gcode(output_file => $gcode_temp_path, quiet => 1); # Read the temoprary G-code file. my $gcode; { local $/; open my $fh, '<', $gcode_temp_path or die "Test.pm: can't open $gcode_temp_path: $!"; $gcode = <$fh>; } # Remove the temp file. unlink $gcode_temp_path; 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-version_1.39.1/lib/Slic3r/Test/000077500000000000000000000000001324354444700173475ustar00rootroot00000000000000Slic3r-version_1.39.1/lib/Slic3r/Test/SectionCut.pm000066400000000000000000000163721324354444700217760ustar00rootroot00000000000000# 2D cut in the XZ plane through the toolpaths. # For debugging purposes. package Slic3r::Test::SectionCut; use Moo; use List::Util qw(first 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 => 'rw'); has '_height' => (is => 'rw'); has '_svg' => (is => 'rw'); has '_svg_style' => (is => 'rw', default => sub { {} }); sub BUILD { my $self = shift; # calculate the Y coordinate of the section line my $bb = $self->print->bounding_box; my $y = ($bb->y_min + $bb->y_max) * $self->y_percent; # store our section line $self->line(Slic3r::Line->new([ $bb->x_min, $y ], [ $bb->x_max, $y ])); } sub export_svg { my $self = shift; my ($filename) = @_; # get bounding box of print and its height # (Print should return a BoundingBox3 object instead) my $bb = $self->print->bounding_box; my $print_size = $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) : () }); 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) = @_; my $bb = $self->print->bounding_box; my $g = $self->_svg->group(style => { %{$self->_svg_style} }); foreach my $object (@{$self->print->objects}) { foreach my $copy (@{$object->_shifted_copies}) { foreach my $layer (@{$object->layers}, @{$object->support_layers}) { # get all ExtrusionPath objects my @paths = map $_->clone, map { ($_->isa('Slic3r::ExtrusionLoop') || $_->isa('Slic3r::ExtrusionPath::Collection')) ? @$_ : $_ } grep defined $_, $filter->($layer); # move paths to location of copy $_->polyline->translate(@$copy) for @paths; if (0) { # export plan with section line and exit require "Slic3r/SVG.pm"; Slic3r::SVG::output( "line.svg", no_arrows => 1, lines => [ $self->line ], red_polylines => [ map $_->polyline, @paths ], ); exit; } foreach my $path (@paths) { foreach my $line (@{$path->lines}) { my @intersections = @{intersection_pl( [ $self->line->as_polyline ], $line->grow(Slic3r::Geometry::scale $path->width/2), )}; die "Intersection has more than two points!\n" if defined first { @$_ > 2 } @intersections; # turn intersections to lines my @lines = map Slic3r::Line->new(@$_), @intersections; # align intersections to canvas $_->translate(-$bb->x_min, 0) for @lines; # we want lines oriented from left to right in order to draw # rectangles correctly foreach my $line (@lines) { $line->reverse if $line->a->x > $line->b->x; } if ($path->is_bridge) { foreach my $line (@lines) { my $radius = $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 { foreach my $line (@lines) { my $height = $path->height; $height = $layer->height if $height == -1; $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-version_1.39.1/resources/000077500000000000000000000000001324354444700165355ustar00rootroot00000000000000Slic3r-version_1.39.1/resources/icons/000077500000000000000000000000001324354444700176505ustar00rootroot00000000000000Slic3r-version_1.39.1/resources/icons/Slic3r-console.ico000066400000000000000000000422061324354444700231470ustar00rootroot0000000000000000 ¨%F  ¨î% ˆ –6 h@(0`  "'+/35799999753/,'" 6IXclsx}€‚„…‡ˆˆˆˆˆˆˆ‡…„‚€}yslcXJ6¢ÿCu…ŒŒŒŒ‘3r°V¿Ôhçínõ÷nõ÷nõ÷nõ÷nõ÷nõ÷nóöcÜåL¨È%Q£ŒŒŒŒŒŒŒŒŒŒŒ†vF ¢ÿ #4CO'bQ²«kíésÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsþþdÝà;‚§yvsnibYPD5#¢ÿ ¢ÿvùYsý×sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿnôÂRµG ¢ÿ¢ÿŽÿ}ÿ«sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿósÿh¢ÿ¢ÿ¢ÿ¢ÿ€ÿ«sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿˆ¢ÿ¢ÿ¢ÿ¢ÿŽÿ7{ÿäsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿŽ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‘ÿ,{ÿåtÿÿvÿÿzÿÿ}ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿÿÿ{ÿÿ wÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿs¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿÿ,„ÿçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿ{ÿÿuÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿøuÿ:¢ÿ¢ÿ¢ÿ¢ÿœÿ3(Žÿì1ÿÿ0ÿÿ0ÿÿ0ÿÿ0ÿÿ0ÿÿ/Œÿÿ*‰ÿÿ$†ÿÿƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÐ~ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ÿšÿ9,ÿî2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ.‹ÿÿ'‡ÿÿƒÿÿÿÿÿÿÿÿÿÿ€ÿÿ wÿÿsÿÿsÿÿsÿÿsÿÿtÿh¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ÿšÿ—ÿM+ÿñ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ+Šÿÿ!„ÿÿÿÿÿÿÿÿÿÿzÿÿsÿÿsÿÿsÿÿsÿÝ€ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ  ÿ šÿ˜ÿ–ÿ6 ”ÿ~)ÿö2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ+Šÿÿƒÿÿÿÿÿÿÿÿ{ÿÿsÿÿsÿÿsÿÿuÿT¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿœÿ šÿ—ÿ-•ÿP–ÿ¦“ÿß)Žÿú2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ'‡ÿÿÿÿÿÿÿÿ{ÿÿsÿÿsÿÿtÿ¦¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ™ÿ’ÿ’ÿ$’ÿBtÀ¤ÿg²íŠÿë)Œÿû2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ-‹ÿÿƒÿÿÿÿÿÿ xÿÿsÿÿsÿã¢ÿ¢ÿ¢ÿ¢ÿ¢ÿšÿÿ †ÿ ˆÿ@ávýÿÿd´ïŠÿé)ÿú2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿ „ÿÿÿÿ€ÿÿuÿÿsÿþ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ…ÿ‚ÿÿ@€ýl '>áÿÿÿ"""ÿv½íœÿç)‘ÿú2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿÿ „ÿÿÿÿ|ÿÿsÿÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿ~ÿzÿ2zÿh O‘­ÿ$$$ÿ'''ÿ***ÿ---ÿ000ÿŒÂëµÿç+”ÿû2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿ‚ÿÿÿÿvÿÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿÿzÿ"xÿSoæ„&*0ô'''ÿ'''ÿ(((ÿ+++ÿ///ÿ111ÿ555ÿ¡ÆîÄÿí+•ÿû2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ,Šÿÿÿÿ|ÿÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ|ÿwÿ6wÿtBsª'''ÿ'''ÿ'''ÿ***ÿ---ÿ000ÿ444ÿEEEÿNNNÿ(²ÏóÇÿî2—ÿû<“ÿÿ6ÿÿ3Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ%†ÿÿ€ÿÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿxÿwÿJwþy *2Ý'''ÿ'''ÿ(((ÿ+++ÿ...ÿ222ÿDDDÿMMMÿRRRÿUUUÿ)²Ñï ¿ÿå<šÿúGšÿÿD˜ÿÿ<”ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ/Œÿÿ€ÿÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ€ÿwÿ%vÿ\bÌ„''(ú'''ÿ'''ÿ(((ÿ+++ÿ///ÿAAAÿKKKÿOOOÿSSSÿWWWÿ\\\ÿ,¬Ôä µÿÛ=šÿúGšÿÿGšÿÿF™ÿÿ=”ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ!„ÿÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ|ÿ vÿ-vÿkH‡›'''ÿ'''ÿ'''ÿ)))ÿ,,,ÿ888ÿHHHÿLLLÿPPPÿTTTÿYYYÿ\\\ÿaaaÿ.§ÕÛ ¯ÿ×<™ÿúGšÿÿGšÿÿGšÿÿC—ÿÿ6ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ+Šÿø¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿzÿvÿ4uÿt8Z²'''ÿ'''ÿ'''ÿ)))ÿ...ÿCCCÿIIIÿLLLÿQQQÿUUUÿZZZÿ]]]ÿcccÿfffÿ,«ÙÝ µÿß>›ÿûGšÿÿGšÿÿGšÿÿE™ÿÿ7‘ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Žÿ×¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿyÿuÿ:uÿw0EÂ'''ÿ'''ÿ'''ÿ)))ÿ777ÿEEEÿIIIÿMMMÿQQQÿUUUÿZZZÿ^^^ÿcccÿfffÿkkkÿ+µÜæ ¾ÿê>œÿüGšÿÿGšÿÿGšÿÿD˜ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ,Žÿ•¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿxÿuÿ<uÿw+:Ì'''ÿ'''ÿ'''ÿ***ÿ???ÿEEEÿIIIÿLLLÿQQQÿUUUÿZZZÿ]]]ÿcccÿfffÿkkkÿnnnÿ*¾ßï Âÿð>œÿüGšÿÿGšÿÿGšÿÿ@–ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿú‘ÿB ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿvÿuÿ<uÿw+;Ë'''ÿ'''ÿ'''ÿ///ÿBBBÿEEEÿHHHÿLLLÿPPPÿTTTÿYYYÿ\\\ÿaaaÿeeeÿiiiÿmmmÿoooÿ)Ááñ¾ÿí>šÿüGšÿÿGšÿÿF™ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ.Žÿ½™ÿÿ¡ÿ¢ÿ¢ÿ¢ÿvÿuÿ9uÿv/DÀ'''ÿ'''ÿ'''ÿ222ÿAAAÿDDDÿGGGÿKKKÿOOOÿSSSÿWWWÿ\\\ÿ___ÿeeeÿkkkÿsssÿxxxÿ{{{ÿ3¼äì¯ÿèAšÿüGšÿÿE™ÿÿ2Žÿÿ2Žÿÿ2ŽÿúÿQ”ÿšÿ ÿ¢ÿ¢ÿ¢ÿsÿ uÿ3tÿs6Z®'''ÿ'''ÿ'''ÿ666ÿAAAÿCCCÿFFFÿJJJÿMMMÿRRRÿUUUÿ]]]ÿfffÿpppÿuuuÿvvvÿxxxÿzzzÿzz{ÿ2ªäé—ÿéA˜ÿýGšÿÿ2Žÿÿ2Žÿÿ%Œÿ©ˆÿ)ÿ—ÿ žÿ¢ÿ¢ÿ¢ÿsÿuÿ,tÿfI‰•'''ÿ'''ÿ'''ÿ777ÿ@@@ÿBBBÿEEEÿHHHÿLLLÿPPPÿ]]]ÿgggÿlllÿnnnÿqqqÿtttÿuuuÿvvvÿvvvÿuvwÿ/”ãìˆÿî?–ÿý2Žÿÿ'Œÿã€ÿE…ÿ'ÿ”ÿ ›ÿ¢ÿ¢ÿsÿtÿ"tÿXdÑ'''ù'''ÿ'''ÿ888ÿ>>>ÿAAAÿDDDÿFFFÿLLLÿ\\\ÿcccÿeeeÿhhhÿlllÿmmmÿoooÿqqqÿsssÿsssÿsssÿprsÿ-Œæì‡ÿê$‹ÿì €ÿuÿ=„ÿ"‹ÿ’ÿ›ÿ¢ÿ¢ÿsÿsÿtÿGtþu!'0Ø'''ÿ'''ÿ666ÿ===ÿ@@@ÿBBBÿGGGÿXXXÿ]]]ÿ```ÿcccÿeeeÿhhhÿkkkÿmmmÿmmmÿnnnÿnnnÿnnnÿmmmÿlmnþ-ŽäÝ€ÿ„{ÿMÿ0…ÿŒÿ’ÿŸÿ¢ÿsÿ sÿ2tÿr;k¢'''ÿ'''ÿ222ÿ===ÿ>>>ÿAAAÿRRRÿXXXÿ\\\ÿ]]]ÿ```ÿcccÿeeeÿfffÿhhhÿjjjÿkkkÿkkkÿkkkÿjjjÿhhhÿfhjý:ØŸ}ÿ7ÿ!†ÿÿ “ÿ ÿ¢ÿ¢ÿsÿsÿ tÿUiá{%&'ó'''ÿ...ÿ===ÿ===ÿEEEÿUUUÿVVVÿYYYÿ\\\ÿ]]]ÿ___ÿaaaÿdddÿeeeÿeeeÿfffÿfffÿfffÿeeeÿeeeÿdddÿ`cdýG˜Ñ€ƒÿˆÿ •ÿ›ÿ¢ÿ¢ÿ¢ÿ ¢ÿsÿsÿsÿ8sÿu6Y¯'''ÿ)))ÿ<<<ÿ===ÿJJJÿSSSÿUUUÿVVVÿXXXÿ[[[ÿ\\\ÿ^^^ÿ___ÿaaaÿbbbÿcccÿcccÿcccÿbbbÿaaaÿ___ÿ^^^ÿ\^_üN›ÊrŒÿ—ÿ ÿ¢ÿ¢ÿ¢ÿsÿsÿ"sÿTkèz#%'é'''ÿ555ÿ===ÿNNNÿRRRÿSSSÿUUUÿVVVÿXXXÿYYYÿ[[[ÿ\\\ÿ]]]ÿ^^^ÿ^^^ÿ___ÿ^^^ÿ^^^ÿ]]]ÿ\\\ÿ[[[ÿYYYÿW[\ûKšÉmžÿ¢ÿ¢ÿsÿsÿsÿ4sÿlI'''þ+++ÿ===ÿOOOÿQQQÿQQQÿSSSÿTTTÿUUUÿWWWÿXXXÿYYYÿZZZÿ[[[ÿ\\\ÿ\\\ÿ\\\ÿ[[[ÿZZZÿYYYÿXXXÿWWWÿUUUÿZ[]ûV¢Îj¢ÿ¢ÿsÿsÿsÿBsÿt2S¯'''ÿ333ÿJJJÿQQQÿQQQÿQQQÿRRRÿSSSÿTTTÿUUUÿVVVÿWWWÿXXXÿXXXÿXXXÿXXXÿXXXÿWWWÿVVVÿUUUÿTTTÿVVVÿ[[[ÿ\_aúX Ïjsÿ sÿ¢ÿsÿsÿ sÿ"sÿMsÿt,@À(((ÿAAAÿQQQÿQQQÿQQQÿQQQÿQQQÿRRRÿSSSÿTTTÿTTTÿUUUÿUUUÿUUUÿUUUÿUUUÿTTTÿTTTÿSSSÿUUUÿZZZÿZZZÿZZZÿ@DFöx¾]sÿsÿsÿsÿ sÿ)sÿVrüu,A¼,,,ÿKKKÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿRRRÿRRRÿSSSÿSSSÿSSSÿSSSÿSSSÿRRRÿRRRÿTTTÿZZZÿZZZÿZZZÿEEEÿ'''ÿ'7C܃ÿ(ÿsÿsÿsÿ+sÿTsÿt8a§///úLLLÿRRRÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿRRRÿVVVÿZZZÿZZZÿZZZÿCCCÿ'''ÿ#:TÓ{ëk‚ÿ2†ÿsÿsÿsÿsÿ(sÿJsÿt P¦‰%*1ÚGGGÿTTTÿRRRÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿRRRÿUUUÿYYYÿZZZÿZZZÿVVVÿ;;;ÿ''(ú>k¬pövtÿHvÿxÿsÿsÿsÿ sÿsÿ?sÿgpùu?wœ47<áMMMÿVVVÿUUUÿTTTÿTTTÿTTTÿUUUÿWWWÿYYYÿZZZÿZZZÿVVVÿCCCÿ--.ú*:É U¯†sÿqsÿIsÿ&sÿsÿsÿsÿ sÿsÿ0sÿNsÿorüuOžŒ,=S¿@BFæMMMùSSSÿVVVÿWWWÿUUUÿRRRÿHHHÿ999ó)/6Ø4X« [¿‚sÿtsÿZsÿ8sÿsÿ sÿsÿsÿsÿsÿsÿ2sÿJsÿcsÿthãz R§Š?vœ5Zª1N³2P±6_§D— W·„oówsÿtsÿZsÿ>sÿ&sÿsÿsÿsÿsÿsÿ sÿsÿ*sÿ;sÿMsÿ]sÿhsÿosÿssÿssÿnsÿesÿYsÿIsÿ6sÿ$sÿsÿ sÿsÿsÿsÿsÿ sÿsÿsÿ$sÿ,sÿ1sÿ3sÿ2sÿ0sÿ*sÿ"sÿsÿsÿsÿsÿÿÿø?x?x?€ÿ~ÿÿx?ÿyÿ±€ÿÃÀÿçàÿü€ÿüÿüü?þ?þüüüüøüøüüüüüüü?ü?üþ?þþÿÏÿÏÿ€oÿ€?ÿÀ?ÿà?ÿà?ÿð?ÿüÿþÿÿÿ€ÿÿÿàÿ( @ ¢ÿ <[iry}€ƒ„…………„ƒ€}ysi[> ¢ÿ¢ÿ >[g&y/h•S¸ÃkììsÿÿsÿÿsÿÿsÿÿlïïU¼Ê2n¢'Š}zvpg[@¢ÿ ¢ÿ9XyÚqáRqö³sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿmòÅTº_9% ¢ÿ¢ÿ¢ÿ„ÿuÿøsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿüsÿxsÿ ¢ÿ¢ÿ¢ÿ~ÿuÿ÷sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿ‰sÿ ¢ÿ¢ÿ€ÿqyÿôzÿÿ|ÿÿÿÿÿÿ€ÿÿÿÿ}ÿÿ{ÿÿ xÿÿvÿÿtÿÿsÿÿsÿÿsÿÿsÿlsÿ.–ÿ{0ÿö.‹ÿÿ,Šÿÿ)‰ÿÿ&‡ÿÿ"…ÿÿƒÿÿ‚ÿÿÿÿ€ÿÿ{ÿÿvÿÿsÿÿsÿÿsÿïuÿ4Šÿ¢ÿ¢ÿ¢ÿMºÿ 2šÿ‰3‘ÿ÷2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿ)‰ÿÿ#…ÿÿƒÿÿÿÿÿÿ xÿÿtÿÿsÿÿtÿzÿ¢ÿ¢ÿ¢ÿ›ÿ –ÿ–ÿ4"—ÿÆ0ÿû2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ-‹ÿÿ%†ÿÿ‚ÿÿÿÿ zÿÿtÿÿsÿôwÿ.¢ÿsÿzÿ}ÿ#‡ÿ?•ÿ‰•ÿÙ‘ÿó0ÿý2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ+Šÿÿ!„ÿÿÿÿ yÿÿsÿÿuÿd¢ÿ¢ÿsÿsÿvÿ-yÿZ~÷….QãÿWò‘ýñ0ÿü2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿ#…ÿÿÿÿvÿÿtÿš¢ÿ¢ÿsÿvÿ"vÿ[vú§ T£Ì ÿÿÿr¦ï¨ýï0“ÿü2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿÿ"…ÿÿ}ÿÿvÿ¹¢ÿ¢ÿ¢ÿsÿ uÿ5uÿ‚cË´!+òÿÿ$$$ÿ000ÿ0™²ò+¼ýô7˜ÿü:’ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ/Œÿÿƒÿÿ yÿâÿ ¢ÿ¢ÿsÿuÿFuÿ B|Âÿÿÿ'''ÿ999ÿJJKÿ4£¸ô+¸þï?šÿû>•ÿÿ7‘ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ(ˆÿÿ€ÿ¼¢ÿ ¢ÿ¢ÿsÿuÿRuÿ­+DÖÿÿ!!!ÿ111ÿDDDÿLLLÿTUUÿ<œ»é1°þåGÿûD˜ÿÿ<”ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ…ÿŸ¢ÿ ¢ÿ¢ÿsÿuÿZuÿ­",èÿÿ'''ÿ<<<ÿEEEÿMMMÿUUUÿ\__þ@˜ºã-´þèEÿüGšÿÿ@–ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ‰ÿk¢ÿ ¢ÿ¢ÿsÿtÿ^uÿ­ õÿÿ///ÿ===ÿDDDÿLLLÿTTTÿ\\\ÿdeeþ?¥Àí-¾þôEÿýGšÿÿ=”ÿÿ2Žÿÿ2Žÿÿ3ÿñÿ:¢ÿ ¢ÿ¢ÿsÿtÿatÿ­÷ÿÿ222ÿ<<<ÿBBBÿJJJÿRRRÿZZZÿaaaÿeijþ>¬Áô+ºþôGœÿýF™ÿÿ2Žÿÿ2Žÿÿ1’ÿ¦ “ÿ¡ÿ¢ÿ¢ÿsÿtÿ`tÿ¬"ìÿÿ555ÿ:::ÿ@@@ÿGGGÿPPPÿ]]]ÿiiiÿpppÿqvvþO¥Åï1§þðG›ÿý2Žÿÿ2ÿíÿS•ÿŸÿ¢ÿ¢ÿsÿsÿYtÿ¬".Úÿÿ333ÿ888ÿ===ÿEEEÿUUUÿ```ÿfffÿkkkÿmmmÿlprýHŽÁï3–ÿõ4‘ÿø$ŒÿŸ…ÿ*ÿ›ÿ£ÿsÿsÿMtÿ¬3ZÄÿÿ111ÿ555ÿ===ÿMMMÿWWWÿ\\\ÿ```ÿdddÿeeeÿfffÿdgkýD‚»î …ÿ¢}ÿH…ÿ Žÿ Ÿÿ¢ÿsÿ sÿ;sÿ R¨´úÿ000ÿ222ÿCCCÿNNNÿSSSÿVVVÿZZZÿ\\\ÿ^^^ÿ___ÿ^^^ÿ\^aûF€®Ô“ÿK…ÿÿ ÿ¢ÿ ¢ÿsÿsÿ'sÿmmì¬'=Ñÿ---ÿ111ÿGGGÿJJJÿMMMÿQQQÿSSSÿUUUÿWWWÿXXXÿWWWÿUUUÿSUWûG€¤Ç¥ÿ2ÿ¢ÿ¢ÿsÿsÿsÿGsÿ U²²ñ'''ÿ111ÿFFFÿGGGÿJJJÿLLLÿNNNÿPPPÿQQQÿQQQÿQQQÿPPPÿNNNÿLNPûH€¡Ã"¤ÿ5sÿsÿ¢ÿsÿsÿ(sÿiqû«?{¼ÿ...ÿFFFÿFFFÿFFFÿHHHÿJJJÿJJJÿKKKÿKKKÿKKKÿJJJÿJJJÿIIIÿMQSûO€¢ÀŠû<sÿsÿsÿsÿsÿ7sÿqø«7gÁ###ÿBBBÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿGGGÿFFFÿGGGÿMMMÿOOOÿ>@AýU‹žÿ(sÿ sÿsÿsÿBsÿ†qù«E‡º,-.óEEEÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿHHHÿNNNÿOOOÿ>>>ÿ2Jç n߆vÿ4sÿ sÿsÿsÿBsÿ~rü«bЮ$9SÎ@@@ùIIIÿGGGÿGGGÿGGGÿJJJÿMMMÿOOOÿJJJÿ234ô??ùDsÌ cÑ–sû=sÿsÿsÿsÿLsþ‰mížX«´.NuÑ:RpÝ;Snà/LnÖKˆÁcÔ§rýsÿRsÿsÿsÿsÿsÿsÿ0sÿOsÿnsÿsÿ‡sÿˆsÿƒsÿxsÿ_sÿ7sÿsÿsÿsÿsÿsÿsÿ(sÿ0sÿ1sÿ,sÿ!sÿsÿsÿ   àøààààààààààààààððüüÿ(  ¢ÿ¢ÿ ¢ÿ¢ÿ^+]R¶ÃbÚßbÙà]Î×B‘´‡ymD¢ÿ ‚ÿxtÿÝsÿÿsÿÿsÿÿsÿÿsÿøsÿ sÿ¢ÿ€ÿ| xÿû|ÿÿ}ÿÿ|ÿÿ{ÿÿ wÿÿtÿÿsÿÁsÿyÿsÿ $’ÿz0ÿù-‹ÿÿ*‰ÿÿ$†ÿÿÿÿ{ÿÿtÿÿtÿŽsÿsÿ zÿ&…ÿO”ÿÜ1ÿý2Žÿÿ2Žÿÿ/Œÿÿ&‡ÿÿ~ÿÿtÿé¢ÿsÿsÿtÿC Z ¬"1ûŒâö0‘ÿü2Žÿÿ2Žÿÿ2Žÿÿ*‰ÿÿ|ÿþ¢ÿ¢ÿsÿsÿ%vÿ\#,íÿ%/2ý,³âö;˜ÿû6ÿÿ2Žÿÿ2Žÿÿ'ˆÿÿ¢ÿ ¢ÿsÿ sÿ+wÿjÿ ÿ999ÿELNü4¨áêBœÿú<“ÿÿ2Žÿÿ0ÿú¢ÿ¢ÿsÿ sÿ.vÿmÿ...ÿGGGÿ\\\ÿdhjý3ºäôCÿü2Žÿÿ0ÿÒ ÿ ¢ÿsÿ sÿ3tÿpÿ222ÿMMMÿlllÿ‚‚‚ÿsvxþAŸâô-Žÿø#ÿx•ÿsÿsÿ5sÿtò000ÿOOOÿmmmÿuuuÿmmmÿY\\þ+ƒÏÒ{ÿ={ÿsÿ sÿsÿ sÿ/sÿj'@¸)))ÿJJJÿZZZÿ___ÿZZZÿNNNÿFGHý8ŒÃ´yÿ:xÿsÿ sÿsÿ!sÿRiåy%-ÔAAAÿJJJÿKKKÿJJJÿFFFÿGGGÿMMMÿg­tÿ.uÿsÿsÿsÿ0sÿ_içx%7M»AABõGGGÿGGGÿJJJÿIIIþ7APÙfÑssÿ/sÿsÿsÿsÿsÿ0sÿSsÿqsÿssÿssÿssÿssÿssÿisÿDsÿsÿ ÿ@` À€€€€€€€€€€€Slic3r-version_1.39.1/resources/icons/Slic3r.icns000066400000000000000000001226021324354444700216700ustar00rootroot00000000000000icns¥‚is32“‚1ACB=,„=gFG;Z‚M Te€FJb‰ƒÿ"'A€FJj¢Éÿÿ€ ó,6EFJl³ò€ÿ€D,15Dl³øÿ ÿ2(16]«÷‚ÿ ÿH6]ªöƒÿÿô S«ö„ÿÿÿ4H¡ô…ÿ€ÿä¢ò…ÿ€ŠÿŠÿˆÿ‚ WÉö€ÿüËU€s‘})‚1ACB=,„=gFG7B‚8Te€FHXq^¢'A€FI]€Ž¢¢€ š,6EFI^Š’ˆž¢€L3,15B^޼¯Œ¢¢((14M„ƽ‘¡¢¢64M€À½Ÿ¢¢¢›B„Á¹žŽ…¢¢¢¡&4q¼µŽŠ{¢¢€ ¡‰h°µœŽ‰u¢€¢¢œ¶µœ‹…uu¢€¢ µ±–‡‚zss¢¬¢„wvt€sw‚ 8fq€sr](€ 6C9‚1ACB=,„=g‚F/‚Te‚FE'A‚FE1€,6E€F=€€,14?E>55 (€1/,3F3,11-2F72.#,,>422!!3€2+€€3€2*€€ '322,!€ *'# €‚„€‡s8mkP²åðÓ‰¶ÿÿÿÿÿÿðGÂÿÿÿÿÿÿÿÑqÿÿÿÿÿÿÿÖßÿÿÿÿÿÿúp#ÿÿÿÿÿÿû÷ä'9ÿÿÿÿÿüøÿÿ)ÿÿÿÿøòÿÿÿÏ æÿÿýôÿÿÿÿõ ƒÿüöÿÿÿÿÿõ%Õ÷ÿÿÿÿÿÿË¡ÿÿÿÿÿÿÿu|ÿÿÿÿÿÿÿÜ \ÿÿÿÿÿÿÿè,Ÿ÷ÿÿÿýÌK( H\jŠ™“{ojaQ*il32xŠ &:??>;6%!8E„F:"%A‡FE+gÿÿ‰ oÎH…FGKSHJØÿˆ+8”Ä…FHN[p„ÿ†M'?EE„F HPb~œ°ÿÿ…ÿ%0>…F IQf†¨¼ÿÿÿÿ„ÿE"18„FIRh‹¬Æ‚ÿ…ÿ#&11B‚FIRhŒ¯×ƒÿ„ÿè )113C€FIRi°äƒÿ…ÿR)€12>FIRi°ä…ÿƒÿÿ5'‚17Igްã†ÿƒÿÿ,"14?Y¨àˆÿ‚ÿÿ+0114?Y‚¨Þ‰ÿ‚ÿÿ6€(14?Y‚¨ÛŠÿ‚ ÿÿL3?Zƒ¨Ù‹ÿ‚ ÿÿä0Zƒ¨ÚŒÿ‚€ÿ'*Hz¨Ùÿ‚€ÿ`+HvŸÔŽÿ‚ÿ.;pžÒŽÿƒÿÇm•Ìÿ„ÿÔËÿ„–ÿ„•ÿƒÿ•ÿƒÿÿ€‘ÿ…“ÿˆ‘ÿŠýþþ‰ÿúê.ˆ |×þ„ÿó±*€ƒ€8еÇËÀ£h„ƒ€€Š &:??>;6%!8E„F:"%A‡FE+I¢¢‰ oÎH†FIO=8Œ¢ˆ$8”Ä…FGKTam\¢†8'?EE„F GLXj}†¢¢…¢!0>…F GMZo„ŽŸ¢¢¢„¢3"18„F GN\r‡Œ– ¢¢…¢ &11B‚F HN\s‰Œ†‘›¢¢„¢”)113C€F GN\tŠ£¤}„Žš¢…¢<)€12>FHN\tЍÀ¾—„Ž™¢¢ƒ¢¢*'‚16EZtвÄź¢“œ¢¢ƒ¢¢%"1 3:Jd}¹ËÅ¿«›‘™Ÿ€¢‚¢¢$0113:Ke|µÎÅ¿°œ“•¢‚¢¢*€(13:Ke}©ËÅ¿²“Ž¢‚¢¢82:Kf}¤ÅÅ¿±œ“ŽŽ‰¢‚¢¢’*Kf}¬Èž­œ“€Žƒ¢‚€¢"$7Y|±ÍÁ¹ªœ“€Žˆ}¢‚¢¢¡C%7Uo¤Ë¾·ª›“€ŽŒw¡€¢‚¢¢ )Ml–þ·ª›“€Žƒ~u€¢ƒ¢¢ ›xE_м¾·©›’€ŽŒƒuu€¢„ ¢¡›˜ˆ»¾·©›’€Ž‰‚ysw¢¢„€¢Ÿ™–´¾·©›“ŽŽŒ…zss¢¢„€¢¡œ¸¾·©›’ŽŠ…€xssu¢¢ƒ¢¢»¾¶©›Š†‚€}u€s|¢¢ƒ¢¢€®µ°¡‘†‚€€{vsv¢¢…€¢ ­®¥”„|zzywuƒsužˆ¢¤­£“‚xt‡suŠ¢¡Š~xt†srnˆ;ct„soR€ƒ€@S[]XK0„ƒ€€Š &:??>;6%!8E„F:"%A‡FE+‰ oÎHˆF,ˆ8”ĉFE†'?EE‰F<…0>ŠF=„"18ˆFG8‚…&11B†FG*ƒ„)113C„FG ‚…)€12>‚FG 5="‚ƒ'‚1 4=DFG!4H;1ƒ"…143HH42.‚‚0ƒ122HH?€2‚€(‚121HHC3€2,‚€0€121HHB32%‚ 1120HE:‚21‚€!1'=7„2'‚€€33„2.‚ 33„20ƒ‚33„2/„ƒ33„2* „ƒ!33ƒ2.! ‚„‚+33‚2+! ‚ƒ.3322.)#€ƒƒ€!!„…€€ …ˆŠŒˆ‰€ƒƒ†ƒ€€l8mk?мÛåÜÀDbÜÿÿÿÿÿÿÿÿÿßc#ÈÿÿÿÿÿÿÿÿÿÿÿÿÿÂ$=íÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâ4óÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùNâÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúRÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûW)ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿüd –ÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ“. çÿÿÿÿÿÿÿÿÿÿÿÿÿÿë¶I#-ÿÿÿÿÿÿÿÿÿÿÿÿÿÿðøÿ™+Zÿÿÿÿÿÿÿÿÿÿÿÿÿî÷ÿÿøJ nÿÿÿÿÿÿÿÿÿÿÿÿó÷ÿÿÿÿ· pÿÿÿÿÿÿÿÿÿÿÿòøÿÿÿÿÿý4 `ÿÿÿÿÿÿÿÿÿÿéôÿÿÿÿÿÿÿ ;ÿÿÿÿÿÿÿÿÿåìÿÿÿÿÿÿÿÿ¾  ðÿÿÿÿÿÿÿïîÿÿÿÿÿÿÿÿÿä ¨ÿÿÿÿÿÿõõÿÿÿÿÿÿÿÿÿÿô Aþÿÿÿÿóöÿÿÿÿÿÿÿÿÿÿÿô ¸ÿÿÿòòÿÿÿÿÿÿÿÿÿÿÿÿâFðÿõôÿÿÿÿÿÿÿÿÿÿÿÿÿ¹*|èóÿÿÿÿÿÿÿÿÿÿÿÿÿÿv ;¿ÿÿÿÿÿÿÿÿÿÿÿÿÿÿý& ‰ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯{ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿù,tÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzrþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ§âÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¡$±ÿÿÿÿÿÿÿÿÿÿÿÿók 2BŠàþÿÿÿÿÿÿÿ÷Èwb\TJ>.>q‡ŠŠ›ºÏÚÝÕÆ­ŒŒŒ‹‹‰†k,)5>FLQUWXYXWTPKE<2' ih32 ˜’  /7;;:73,!ž%6AE„FC7$™0CŠF7—„FHMWhš°Ëù†ÿˆÿÿà %12?‚FHMWh™°Ìú‡ÿˆÿÿg%ƒ1 :EFFHNXh›±Ìûˆÿ‡€ÿ4$„1 4?GMXh€›°ÊûŠÿ†€ÿ2€!„1 23;Keš±Éù‹ÿ†€ÿ+€0‚1 24:EXs©ÇúŒÿ…ÿ'-1 249EXr¨Ãøÿ…ÿ*%€1 249DXs©Âöÿ„ÿ1 /124:EYt‘©Â÷ÿ„ÿ5‚ %24:FYs‘©Àö‘ÿ„ÿR 1:EYt‘©¿ô’ÿ„ÿÖ€ (DZu’©Àõ“ÿ„‚ÿ +$1Ls’©¿õ”ÿ„‚ÿ ?$0Ge‡©¾ó”ÿ…‚ÿ ¼!$2Gg‡¡¹ó•ÿ…ƒÿA/Ge‡ ¶ò–ÿ†‚ÿÃ)4X~žµï—ÿ†ƒÿ¥Wz—®î—ÿˆƒÿ©—¬í˜ÿ‡„ÿüÈé˜ÿ‡£ÿ‡¢ÿ‰¡ÿ†ÿ ÿ†€ÿÿÿ›ÿˆ€ÿšÿŠ€ÿ›ÿŒœÿŽšÿ‘–ÿòô“$Ev¶â÷ÿî³+‚‰ =«í‰ÿã•&‚† ‚ f¬ÕåðõôíâÍžQˆ†ƒ%,+!‚„ˆ‚€‚€„’  /7;;:73,!ž%6AE„FC7$™0CŠF7—„FGKQ\k|Š—“|~ƒ‹—€¢ˆ¢¢%12?‚FGKQ\k|Š˜­²‹}‚‰— ¢¢ˆ¢¢H%ƒ1:EFFGKQ\l|Šš²Â©…ƒ‰—Ÿ¢¢‡€¢*€$„14?FKQ\k|Š›¹ÂÆÁ³˜…‹’™ €¢†€¢(€!„1238EXk|ŠžÆÅÅļ¯ž•œ¡€¢†€¢$€0‚1237>J[n›ÓÉÆÄÀ±¥š’”šž¢…¢"-1236>J[n}–×ÌÆÄ¿¶¥œ•‘ ¢…¢#%€1236=J[n}“ÓÍÅÄ¿¸¨œ•‘Ÿƒ¢„¢(/1237>J\o~‘ÉËÅÄ¿¸ªœ•‘ŽŽ–ƒ¢„¢*‚%237>K\o~Ž¿ÈÆÄ¿¸ªœ•‘Žƒ¢„¢;/7>K\n~Ž»ÅÅÄ¿·©œ•‘€Ž…ƒ¢„¢Š€$=K]o~‘ÅÆÅÿ´¦›•‘މƒƒ¢„¢£$ )<[o~ÏËÆÃ¼±¥›•‘‚Žƒƒ¢„‚¢0!(7Ja}Ò̾¸¯¥›•‘‚މy¡¢…¢¡x!)7K_p„ÍË¿¼¸®¤›•‘‚Ž‚€u¡¢…¢¡ž,'7J_o¿Ç¿½¸¯¤›•‘‚Ž„zu‚¢†€¢ œw!:Tk}°Á¿½·¯¤›•‘ƒŽ†€tw‚¢†€¢ œšd7N`p¨¼¿¼·®¤›”‘‚Ž…xsw¢ˆ¢¢ œ™—h`o¨»¿½·¯¤›”‘‚ŽŒ„}ssz¢‡€¢¡œš—•£¼¿½·®¤›”‘‚މ‚~tss€¢‡‚¢Ÿ›™–™µ¾¼·®¤›”‘ŽŒ…€ussuž€¢‡ƒ¢ œš™³¿¼·®¤›”‘€Ž‹†~t€sy€¢‰‚¢Ÿ·¿¼·®¤›”‘ŽŽ‰„‚|t€st†€¢†¢‚¢ §¸¿¼¶®£›”‰…ƒw‚sz€¢†€¢¢¢£¸¾¼¶¬¡–އƒ„yt‚su–¢¢ˆ€¢ ¬±±­¥™ˆ„‚‚€|w„st~¢¢Š€¢ ¢¬¯¬¥™Ž…€~€}|zxu‡s{¢¢Œ€¢ ¬¯¬¥š‚zvts|šŽ€¢©«¤˜‚zvts{‘ ¢£¢¢…€zvt‹stp~“.LimsutŠstnT‚‰ Pmu‡suiE‚† ‚ /Qajnppmh^J%ˆ†ƒ ‚„ˆ‚€‚€„’  /7;;:73,!ž%6AE„FC7$™0CŠF7—ŠFG7†ˆ%12?ˆFG7 0…ˆ%ƒ1:E…FG7 ;B/ „‡€€$„14>E‚FG: 9HA3*…†€€!†139AEGH: 9HH>22!„†€€0ˆ126/ 8€H7220ƒ…-ˆ12* 6€HD2&ƒ…%‡12* 5€HG82/ƒ„/…12+ 5€HG=ƒ2ƒ„‚%„12, 4€HG>„2!ƒ„‚.‚12, 4€HG<„20ƒ„ƒ/€12, 3IHH@5…2)ƒ„‚ƒ /12- 0HE@6†21ƒ„‚„0- &<74ˆ2* ‚…‚ƒ !€3ˆ20‚…ƒ € €3ˆ21!ƒ†ƒ €3‰2#ƒ†„€3ˆ21" ƒˆ†€3ˆ2/ „‡‡€3ˆ2)ƒ‡†433‡2-!€„‡…&€3†2-#€„‰„*€3„21*!‚…†‚ +43321.)"‚††€(210/,*&!ƒ †ˆ€…‡Š€ € ŠŒ€•Ž€€”‘€”“‚‰“† „‡Š†„‰„ˆ‚€‚€„h8mk *_‘·ÍØÕǪKgÄóÿÿÿÿÿÿÿÿÿýåŸ>ráÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü°0=Òÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷€ jñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ·' ‚ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~„þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŸ bùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ§ *óÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ­  ¾ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿª \ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ²Ìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ»" OüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿËA& ¥ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóQ6  ëÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøãåD) 4ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøêúÿè_0kÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøèùÿÿÿÂ2 Œÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùåøÿÿÿÿþs ¡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúëøÿÿÿÿÿÿÝ* ©ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûîùÿÿÿÿÿÿÿþp ¥ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúìùÿÿÿÿÿÿÿÿÿË “ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷âöÿÿÿÿÿÿÿÿÿÿõ9 wÿÿÿÿÿÿÿÿÿÿÿÿÿÿö×ñÿÿÿÿÿÿÿÿÿÿÿÿo HÿÿÿÿÿÿÿÿÿÿÿÿÿøÔîÿÿÿÿÿÿÿÿÿÿÿÿÿ¬ õÿÿÿÿÿÿÿÿÿÿÿúÞïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÐ »ÿÿÿÿÿÿÿÿÿÿüéóÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿç  hþÿÿÿÿÿÿÿÿüî÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿð  Þÿÿÿÿÿÿÿüìöÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿð  }ÿÿÿÿÿÿüèôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿå-ÖÿÿÿÿüèóÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÍ"bûÿÿüëôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¥8 üýìôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿe -R¸àòÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿó,  :`ÒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÆ '™ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþd ‚üÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÚyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ^súÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ»oùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿä&oþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñCi÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõReôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿëKBÐÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ½% îÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿê‹) *:HU]}Äôÿÿÿÿÿÿÿÿÿÿÿÿyxuqlg_VJ=,Uv‚‡‰‹‹‹Ž­Ìãð÷úùõíßÅ¥ŒŒŒŒŒŒŒŒ‹‹Šˆƒx^" %?P^hpuz}„‰Ž‘‘މ‡…††…„ƒ‚€~zvqj`TB*  &+059<@BCDDEDDB@=:61-'! it32?·¾ç #,39=?A€C B?=93,$ß !0–ÿíZB¡FGHJKMOPSVY^^C@FIJ;µÿ² -DDRæÿùf@ŸFGHIJLMORUX\`djfPPSL£þÿ³E€):G?s‚ÿçSCœF€GHIKLNPTX[_dimrxq\X£ÿÿ³ f%2@G?p‚ÿ£>œF€GHIKMORUY^bgmrx|€…oª€ÿ†ÿÿ§ ±03DGDLÓ€ÿ¸LC›F€GHJKMPSW[`ekqw}‚†Š‰·þÿÿ‡ÿÿ¦Ì€ +27GFFBY¡­€CB›F€GIJLNQTX]bhou|ƒ‰Ž”“½þÿ‡€ÿ¦ A#20E›FGGHIJLNQUY^dkqy€ˆ•››Ãþ€ÿ…ÿ¥s €/20?G¢FGGHIJLORVZ`elt|„Œ“›¡¡Æþÿ…ÿ¤   %210AG¡FGGHIKMORV[ahowˆ™ §¨Éþ‚ÿ„‚ÿ¡ÿë)€.210AG FGGHIKMOSW\biqy‚Š“œ£«¬Äý‚ÿ„ƒÿ ÿÿv €#2110@GŸFGGHIKMOSX]cjr{ƒ–ž§­®Æû‚ÿ…‚ÿ ÿÿº,2110>GžFGGHJKMPTX^eks}†Ž˜¡©°²Çûƒÿ„ƒÿŸ€ÿö7€10:GFGGHIKMPTY^dlt}‡™¢ª±³Åù„ÿƒ„ÿŸÿÿ— (2€105FGGHIKMPTY_emv~ˆ’𣬲³Èú‡ÿ‚ƒÿžÿë,/2‚1CG™F€GHJKNQUY_fnv€Š“œ¤­´µÉúŠÿ„ÿž€ÿŒ 2‚10=G™FGGHJKNQTZ_fnv€‹”œ¥­´¶ÅùŠÿ„ÿžÿô2%2‚106G—F€GHIKMPTY`fnw€Š•¦®µ¶Å÷”ÿÿ ‚*2ƒ10@G•F€GHIKNQUZ`gox‚Œ– §¯¶¸Æ÷”ÿžÿõ7‚.2ƒ106G•FGGHJKMPUZ_foxŒ•Ÿ¨¯¶¸Äö•ÿÿ·‚0…10>G“FGGHJKNPUZ`goy‚Œ– ¨°·¸È÷•ÿ‚ÿe ‚ 2†12BG‘FGGHIKMQUZ`hoyƒ—¡©±·¸Êø•ÿžÿð+‚"2†105‘FGGHIKMQUZ_gpx‚— ©°·¸Ç÷–ÿ‚ÿ·ƒ%2‡108GGFGGHJKNQUZ`goyƒ— ©±·¸Ìù–ÿž‚ÿo ƒ'2ˆ109FG‹FGGHJKNQVZ`hpy„—¡ª±¸¸Ëú—ÿƒÿú:ƒ(2‰108FG‰FGGHJLNPTZ_gox‚— ª±¸¹Çø™ÿ›ƒÿ؃(2Š107EG‡FGGHJKNQTZ`gpyƒ˜¡ª²¸¹Ëøšÿ›ƒÿ¥ „(2‹104@H…FGGHJLNQUZ`hpy„˜¢ª²¸¹Éùœÿ™„ÿh„(2Œ101BHOW`juŽš¥®¶¹Åò§ÿ”…ÿS†'2Œ1€2357:=CHOW`kv—¡©°²½ò§ÿ”†ÿE†"2Œ1233579>BHOW`kuŒ—¡©°²¹ì©ÿ“†ÿüB‡0‹122357:>BHPWalv‚—¡ª°²½ìªÿ“†ÿüB‡+2‰122367:>CHPXakw‚˜¡ª±³»ñ¬ÿ’†ÿýC‡"2ˆ123357:=BHOWakv‚Œ˜¡©°³¸ê­ÿ‘‡ÿPˆ.2…1€2358:>BHPXalw‚Ž˜¢ª±´»ê®ÿ‘‡ÿOˆ'2…123357:>BHOXalw‚Ž™¢ª±´¹í¯ÿ’†ÿaˆ…123457:=BHOXakv‚˜¢©°´¸ç°ÿ‘‡ÿ| ‰)2‚123367:>CIPXblwƒ˜¢ª±´ºç±ÿ‘‡ÿŽ ‰‚123457:>CHPXalw‚Ž™¢ª±´·ê²ÿ‘‡ÿªŠ)21122357:>CIPXbkw‚˜¢ª°´·ä³ÿ‘ˆÿÌŠ/€2467;>CIQXbmwƒ˜¢«±´¹ä´ÿ‘ˆÿä%Š#€367:>CIPYbmx‚™¢«±´·çµÿ‘ˆÿýB‹-558:>CIPXblw‚Ž™¢ª±´¶â¶ÿ‘ˆÿvŠ 39:>CIQXbmw„™£«±µ¸â¸ÿ‰ÿ¿‰';?CIPYbmxƒ™¢«±µ·ä¸ÿ‘‰ÿá †!#2DIPYblxƒŽ™£ª±µ¶Þ¹ÿ‘‰ÿýP†!$)-@RYcnx„™£«±µ¶ÞºÿŠÿ …!%(.4GR^jw„™¢©®­ÑÁÿ”ÿ]+5?HQ]jvƒ™¢©­­ÏþÁÿ•Œÿ× +KZl|‹˜£ª¯­Îþ¶ÿŠÿ”Žÿ]3>LYguŒ˜¢©ªÌý·ÿ‰ÿ•ŽÿÞ:;KZhuƒŽ—Ÿ¥¤Çý¸ÿˆÿ—Žÿ °BZivƒ˜ ¦¤ÅýÅÿ—Žÿ þˆ^v‚˜Ÿ¤¤ÁûÅÿ˜ÿ ôŽ|˜ ¥¤ÃüºÿˆÿšŽÿïš” ¥¤Âü»ÿ‡ÿšÿæ¡¢¥½ùÇÿš‘ÿé©»ú¼ÿ‡ÿ™”ÿíö½ÿ‡ÿ˜×ÿ†ÿ˜âÿ™Öÿ†ÿšßÿšÖÿ†ÿšßÿœÔÿ…ÿžÜÿ–€ÿ‚ŽÿÀÿ…ÿ—ÿ‚ÿ†ÿÉÿ—ƒÿ€ÿÿÿÁÿ„ÿ—„ÿ€ÿÿ€ÿÿÁÿ„ÿ—†ÿƒÿÿ…Âÿƒÿ˜‡ÿ‹Âÿƒÿ„ÿ†ÿÿÂÿ‚ÿ™ÿ‚„ÿ†ÿÃÿ‚ÿ™ÿƒ†ÿ„Ãÿ‚ÿ¢…ÿÿÃÿÿ¥„ÿ€ÿÃÿ€ÿ¨†ÿÃÿÿ§Íÿ€ÿªËÿÿÿ­Éÿÿ°Çÿÿ²Åÿ¨ÿ‹¿ÿñ¤º½ÿÅ.½ÆËÏÓÜâ€äô¯ÿæs € ¯  (DR]ffe”ã«ÿì„€•ÿ‹ |ì§ÿò!ƒ‚‚€‘ÿˆ  €#~Ñ£ÿâ‰%‡ƒ€€€Žÿ‡ €€€K¯ìÿ÷²V„€€ ˜ ‹~Èñ—ÿ÷Ç~.œ – €‘_ÅëÿèÇ’W%¢€ — “7Ww–«ÀÍÑÖÕÔ˾®”wU3¤ – €ƒ”¥ƒ€ ™ €‹œ‹€ œ€ƒ€…„„„…‚€£€†ƒ‚ƒ†€® €€ ‚€€‚ ›¾ç #,39=?A€C B?=93,$ß !0–ÿíZB¢FGIIJLMOQRUT616890v¢² -DDRæÿùf@ŸF€GIJKLNOQTVY]X=<>;l¥£³3€):G?s‚ÿçSCžFGGHIJKMOQTVY\_bg]DAm¦£³ E%2@G?p‚ÿ£>F€GHIJLNPRUX[_cfikoYp¦£¢†¢¢§ s03DGDLÓ€ÿ¸LCœF€GHIKLNQSVZ]afilorsƒ£¤¢‡¢¢¦„€ +27GFFBY¡­€CBFGGIJKMOQUX\`dinqtxyˆ£¢‡€¢¦ 1#20EFGHIJKMPRUY]bgkqty}~Œ£€¢…¢¥N€/20?G¤FGHIJLNPSWZ_cinsx|‚Ž£¢…¢¤ i%210AG£FGHIJLNQSX\`ekpv{€„†‘¢‚¢„‚¢¡¢—#€.210AG¢FGHIKLNQTX]aflrw}‚†‰¢‚¢„ƒ¢ ¢¢Q€#2110@G¡FGHJKLNQUX]bhmty~„ˆ‹ ‚¢…‚¢ ¢¢y,2110>G FGHJKLORUZ^ciou{€…Šœ‚¢„ƒ¢Ÿ¢¢¤Ÿ+€10:GŸFGHIKLORUY_djpv{†‹Ž™•™ £€¢ƒ„¢Ÿ¢¢d(2€105ŸFGHIKLORVZ_ejpw|‡‹Ž‘’‘–›œŸ¢‚ƒ¢ž€¢¥—%/2‚1CGœFGHJKMPRV[_elrw}‚‡‘ŽŒŠŽ“—–——Ÿ‚¢„¢ž€¢]2‚10=G›FGHIKMOSV[`ekrx}ƒˆ‘†‡Š’“”™›¢„¢ž€¢¥œ)%2‚106GšF GHIKMORV[`flry~ƒ‰’Œƒƒ…ˆŠŒ‘—™—ž£ˆ¢¢h‚*2ƒ10@G˜F!GHJKMPRW[`flsy€„‰Ž“Œƒƒ…‡‰Œ”š–”˜¡‡¢ž€¢¤+‚.2ƒ106G—F#GHIKLPSV[aflsy…‰Ž’‚ƒƒ…ˆŽ––““•£†¢¢w‚0…10>G•F#GIIKMOSV[`flsz…ŠŽ’Ž}€ƒ„‡•’“•š†¢‚¢G‚ 2†12BG“F&GHJKMPSW\afmsz€…Š‘“ˆ||}€€‚„†ŽŽ’”– £ƒ¢ž¢™$‚"2†105“F'GHJKMPRV[afltz€…ŠŽ–š‰|{|}~€ƒ†ŒŒŽ‘”—œ£ƒ¢‚¢wƒ%2‡108GGF'GHIKMPRW[agmsz€…‹Ž˜§¡Ž€{{|~ƒ…ŠŒ‘”™œƒ¢ž‚¢Mƒ'2ˆ109FGF)GHJKMPSW\agntz†Š˜«¬¦ž‡}{|}ƒ…‰Œ’–šœ¡‚¢‚¢¤Ÿ-ƒ(2‰108FG‹F*GHJKLPSV[agmtz€†‹—¬¯«¶·š„||}‚…‡‹Ž’•™› ƒ¢›ƒ¢Šƒ(2Š107EG‰F+HHIKMOSW[agmt{€†‹˜­°®¹Æ¿¯˜|}€ƒ…‡Š‘”—šžƒ¢›ƒ¢m„(2‹104@H†F-GGHIKMPSW\agntz†‹˜¯²¯»ÇÆÃ¸¬’}€ƒ…‡Š’”˜š„¢™„¢H„(2Œ101BKV`js{‡‹–ÄÔÍȀƀÅÃÂÀ·²®ª¤Ÿ˜’Ž’”–™›œŸ‡¢•…¢A†,21€234579<@DIOU\dnv†‹Œ™ÊÙÓÌÆÅÄÃÂÀ½²¯«¦¡˜“‘’”–™šœŸˆ¢”…¢<†'2Œ122334579AEJPW_fmty~ƒ…„¯ÓÍÉ€ÆÅÅÄÃÁ¿½º´¬¥¢ž›˜–”“‘€ˆŽ…€¤‹¢‘ˆ¢£¤:…€!$&7GKQX_fntzƒ…„±ÙÒÍÇ€ÆÅÄÃÁÀ½¸±ª¥¢ž›˜–”’‘€ˆŽŒФ‹¢Š¢^„€ $'+/DRW_emtyƒ„‚´ÞØÒÈ€ÆÅÄÿº´®ª¦¢Ÿ›˜–”’‘€‰Ž…€‚£‹¢‘‰¢¨‚ƒ0!#'+06=P`fmty~ƒ…ƒ²áÜÖÉÅÆÆÅÅÿ»¶±®«¦£ž›˜–”’‘€‰ŽŒ€~|¡£Š¢‘‰¢££6€0!#',06>EL^ntzƒ…„²ãàÚÊÅÆÅÿ»¸µ±­ª¦¢ž›˜•“’‘‹Ž…€‚zz¡£Š¢‘Š¢[2!$'+06=EMT\ky€ƒ…‚²åáÜÊÃÃÁ¿½¼º¸µ²®ª¦¢Ÿ›˜–”’‘‹Ž‹€‚ux £Š¢‘Š¢8¥Š!$'+06=EMU\djs‚†ƒ®ãâÝÆ¾¿¿¾½¼º¸µ²®ª¦¢ž›˜•”’‘ŒŽ „€‚swŸ£‰¢“‰¢7 §Q!$',06>EMU]dkpty¬ààÜÆ½¿¿¾½¼º¸µ±­ª¥¡ž›˜–”’‘ŒŽ ‡€‚{qwŸ£‰¢’‰¢5 Ÿ¤‡!$'+16=EMT]djpuwt£ÝÝÛȽ¿¿¾½¼º¸µ²­ª¦¡ž›˜–”’‘€‹Ž‹‚vqx £‰¢’‰¢ ŸŸ¡C!$'+07=EMU]dkpuxuž×ÙØÆ¾€¿½¼º¸µ²®ª¦¢ž›˜–”’‘€Žƒ€‚}srz¡£‰¢“ˆ¢ŸŸž¢…#%(,16>EMU]dkpuwušÑÓÓž€¿¾¼º¸µ±­©¦¡ž›˜–”’‘€ŒŽ…€€‚wrq|Š¢”ˆ¢Ÿž£8 #+28>ELT]dkptwu–ÊÎΞ€¿½¼º¸µ²­ª¦¡ž›˜•”’‘€ŒŽ‡€€‚ssq£‰¢•†¢¡Ÿžœ¡‚  *7CMV^ekpuxv”ÄÇÈþ€¿½¼º¸µ²®©¦¢ž›˜–”’‘Žˆ€‚wrsqƒ‰¢•†¢ žžœ››¡X '0:GS_iquyw‘¾€Á€¿¾½¼º¸µ±®©¥¡ž›˜•”’‘މ‚‚}rsspŠŠ¢”†¢¡žœ›ššž9 '09BJRYaioq¸¼¼¿¾½¼º¸µ±­ª¦¡ž›˜•”’‘ŽŠƒ‚vrssp‰¢•‡¢Ÿœ›š™…%%09CKS[`ehf„¶¸¸¾€¿¾½¼º¸µ±®©¥¢ž›˜•”’‘މƒ‚{r€sr–ˆ¢—…¢.¡žžœ›š™—ži+9CLS[aeigƒµ·µ½À¿¿¾½¼º¸´±®©¥¡ž›˜•“’‘€ŒŽˆƒ‚t€sruœ£ˆ¢—…¢¡žžœ›š™—˜˜T=KSZaehg²·´¼À€¿½¼º·´±­ª¥¡ž›˜•”’‘€Ž‡„‚wr€sqz‰¢˜…¢¡žžœ›š™˜—˜’YOZafig‚µ·´¼À€¿½¼º¸´±­ª¦¡žš˜•”’‘€‹Ž„€ƒ‚{rsq‡ˆ¢š„¢*žœ›š™˜—–˜Žb^figµ¸¶¼À¿¿¾½¼º·´±®©¥¡žš—•“’‘€‹Ž‹ƒ€ƒ‚~ƒsq”‡¢š…¢)Ÿœ›š™˜—–•˜Œggh}³¹¶»À¿¿¾½¼º·´±®ª¥¡ž›—•“’‘€ŠŽˆ…‚€u‚sru£‡¢š…¢ œš™™˜—–•™‘l{³¹·¼€¿¾½¼º·´±­©¥¡ž›˜•”’‘‹ŽŒ…€…€vr‚sq‡¢™‡¢)¡žœœ›š™˜—–••›–¦µµ¼À¿¿¾½¼º·´±­©¥¡ž›˜•“’‘ŠŽŽˆ€†wrƒsp‡¢˜‰¢Ÿœ›šš™——–••§­¸€¿¾½¼º·´±­ª¥¡žš˜–”’‘‰ŽŒ…€†‚yr…sš†¢˜ˆ¢&££¢žœ››š™˜——••–±½¾¿¾½¼º·´±®ª¥¡›˜•”’‘€ˆŽ†€‡yr„sq~£‡¢™ˆ¢££ œ›š™™——–•’«¿€¾½¼º·´±­©¥¡žš˜•”’€‡Ž‡‚€ˆxr…sp†¢šˆ¢#££¢ž›šš™˜—–“¦ÀÀ¿¾½¼º·´²­©¥¡žš˜•“’‘€†Žˆƒ€‡‚xr…sru£…¢šŠ¢!££žœ››™™˜–©ÀÀ¿¾½¼º·´±­©¥¡š—•“’‘€„ŽŒ‡‚€ˆ‚v‡sq‰†¢šŒ¢¡€œ›™–ªÁÀ¿¾½¼¹·´±­©¥ žš˜•“’‘„ŽŽ‹…€‰‚~tr†srvž£…¢œŒ¢ žžœ—§ÀÀ¿¾½»º·´±­©¥¡žš˜•“’‘‚މ„€Š‚|tˆsp†…¢žŒ¢ ŸŸ›©ÀÀ¿¾½¼º·´±­©¤¡›—•”’‘ŽŠ…‚€€Š‚€wrˆsrtœ¤…¢–€¢‚Œ¢£¡«ÁÀ¿¾½»¹·´±­¨¤¡žš˜•“’‘€މ…‚€€‹‚‚|tr‰spˆ…¢—¢‚¢†¢ ¨¿À¿¾½»¹·´°­©¤¡š˜•”’‘‘Ž‹‰…‚€€‚xrŠsrv£„¢—ƒ¢€¢¢¢«¿À¿¾½»º·´°­¨¥¡žš˜•”“‘Œ‰†ƒ€€Ž‚€ytr‹sq‘„¢—„¢€¢¢€¢¢«ÀÀ¿¾½¼º·´±­©¥¢žš—”‘ˆ…ƒ‚€‚€ztrŒsp„¢—†¢ƒ¢¢…ª¿À¿¿½¼º·´°«§¢˜“‹‡…ƒƒ‚‚‚‚zursrt›ƒ¢˜‡¢‹£¶»ºº¸¶³°­©£Ÿš•‘Ž‹ˆ‡…„„‚‚Ž‚‚}xtrspŽƒ¢„¢†¢¢¤€¯°²³±¯«§£ž™–’Ž‹‰‡…„ƒ‚‚Œ‚‚zurrsp„‚¢™¢‚„¢†¢£¯°¯®¬«ª©¨¦£Ÿ›–’Œ‰‡…„„‚‚ˆ‚€~zvsr’sq{¡‚¢™¢ƒ†¢„£¯±¯®­«¨¥¡˜•“‹‰ˆ†…„ƒ‚‚€ƒ‚ €~|ywtsrr“srv›‚¢¢…¢¢£¯±¯¯­«©¦¢˜“ŽŠ…}}||}„~~|zywvts€r–srr•¢¥„¢€¢¢®±¯¯­«©¦¢™”І~|ywut‡s‚ršsrs‘€¢¨†¢£®±¯¯­«¨¥¢˜“Š…~{yxvutt§srs’¢§‡¢¤®°¯®­«¨¥¡ž™“ŽŠ…‚~{yxvutt§srq€¢ª…¢¡§°¯®­«¨¥¡œ˜”Š…‚~|yxvutt§sqs¢¢­…¢¡¦®­«¨¤ ›–’މ…‚~{yxvutt§sqv—¢°…¢¡¤ª¨¥Ÿ™•Œ‰…~{yxvutt§spwš§²…¢¡¢¡›•Ї„~{zwvutt¦suy‰§¨¢‹…¢¡•‡†…„‚|{ywvutt¦sylOº ª¨©¨§¦¥¥¤“|€{zxw€ut¥svz[½~‚„†Œ‘“‰wwutvvuu€t£suzk7€ ¯,5–ÿíZB«FHD!€ ²€-DDRæÿùf@¬FH@³€):G?s‚ÿçSC­FI:³ €%2@G?p‚ÿ£>¯FJ2 †§ 03DGDLÓ€ÿ¸LC¯FK*‡¦€ +27GFFBY¡­€CB¯FK,‡€¦ #20E¯FK+€…¥€/20?GµFGK-…¤ €%210AG´FGL/‚„‚¡€.210AG³FGK3‚„ƒ €#2110@G±FGGL1‚…‚ ‚,2110>G°FGGL2ƒ„ƒŸ€€10:G®F€GL5„ƒ„Ÿ (2€105®F€GL3†‚ƒž/2‚1CG«F€GL4‰„ž€ 2‚10=GªF€GK9‰„ž%2‚106G©F€GL6“ ‚*2ƒ10@G§F€GL7“ž‚.2ƒ106G¦F€GK:” ‚0…10>G¤F€GL8”‚‚ 2†12BG¡FGL9”ž‚"2†105¢F€GK>•‚ ƒ%2‡108GGFGK9”ž‚ƒ'2ˆ109FG›FGK:’„ƒ(2‰108FG™FG K? #D’›ƒ…(2Š107EG—FG K; !KD5’›ƒ „(2‹104@H•FG K= %LJD35’™„„(2Œ101 JIHID€31 ‘˜ƒ…&21008CGGŒFGJA !LIHHID3324)—„…$2‘102:BGG‰FGJCJI€HIB23225‘–„ …!2“1017?EGG…FGJ@IJH I@232231 –„…–10039?E€GF€GJB KJ‚HI<23€24+•…†/2—10049>CEGHJEIJ„H8ƒ26•…†,2™1€0 147953€23š23-Œ‘Š !046)72‚3›2 Œ“ŠŽ€,(6†3š23%Œ’‹Ž‚&7†3š23,Œ’ŒŠ‚'6†3š231Ž“Œ †‚'7†3›23"€ Œ”‚#6†3›23%€Ž• %6†3›24'€‰•Žƒ %7†3›24)Š”†   7†3›23*ƒ‰•ƒ€#6†3›24)ƒ‚ˆ—ƒ#7…3œ23'‚—„7†3š232%ƒŽ˜ƒ!6†3š230!ƒƒˆš” 6†3š24,ƒ…‡š•7†3™233(ƒš“6†3™23/"„„‡™–7…3™233'……‡˜–6†3—233-!… ††˜—64…3–234/$… ‘™•74„3—240%† ‡†š”54„3•2331'‡ ‘š”54…3“233.%‡ˆ†š“64„3’2€3+#ˆ’œ‘54„32€30)!‰‰…ž54„3Ž2€30+#Š ”–€‚Ž64„3Œ2€30)"‹‹…—‚†54„3ˆ231-)"Œ ”—ƒ€44„3…2€320,'# „—„€€64‹320.)# ŽŽ„—†ƒ…55‚4 3210.,*'%"€‘ƒ˜‡‹ #&&$#""!ƒ“ ƒ„†  €€— ’‚™‚„†‚  €”‚™ƒ†„…  ‡  €–‚¢…‡€ €  ˜¥„€††ƒ„ƒ€¨††¹§ˆ†¹€ª††¹­†„¹°†‚¹²†º¨‹Áº¾¾¹€ ±€¬ƒ„€•‹ƒ‚ª…‚‚€‘ˆ €€‚´ƒ€€€Ž‡ €€„¤‘„€€ ˜ Œ – €’—£€ – –Œ¦ — €ƒÀƒ€ ™ €‹œ‹€ œ€ƒ€…„„„…‚€£€†ƒ‚ƒ†€® €€ ‚€€‚ ›t8mk@6OgyŠ•˜š“oU=! Oƒ²×ðÿÿÿÿÿÿÿÿÿÿÿÿÿÿõä»–X-8ŠÈóÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÖ™P%uÀüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿË…2Q¬ôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿú´\^ÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÛbLÄÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÃM9°ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¹0ûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñt=Ãÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ²*}úÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿîd   ªÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ *Ëÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ²! @æÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ-PóÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿeSøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿNøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ–>ôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,éÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŸ×ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ®ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŸ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ§Gÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ«  Øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿµ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ²6öÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ´Éÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¼ [ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ·  Øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿº  \ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÆ   ÊÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÅ"  JÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÊ.  Àÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ×A')"  7þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿØQ870(   ‘ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿærKH?6.%  Ûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿö«r\QF=4+# >þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöÒ³ˆj\OD:0(! ŠÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùßÕÉÂfXK@5,$ ÈÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüåßÜéÿÍv^QE90' !ôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúèäãïÿÿÿËhVJ>3*" RÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûéææóÿÿÿÿÿºYNA6-$ Œÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüêççòÿÿÿÿÿÿÿ•KD8/& Ðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûéæçòÿÿÿÿÿÿÿÿ÷u?:0'  ðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûçäåòÿÿÿÿÿÿÿÿÿÿÝI91(! 6ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýçâãïÿÿÿÿÿÿÿÿÿÿÿÿ˜02)! VÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûæàâîÿÿÿÿÿÿÿÿÿÿÿÿÿôY*)! wÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüçàáîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÛ8&" Œÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþëâáìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿˆ" ¢ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýìåäíþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿç4 ²ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýíçæðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ¼ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþðéèïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿë0 Ãÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþñêêðþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ Âÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþðëëòÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿä$ Âÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñêëðþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿn  ·ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþïèéïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÁ ±ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿëãæïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ5 žÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿêßâëýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ ˆÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýæÙÜçüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿº  nÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáÓ×ãüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñ, RÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâÏÒÝûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿQ 2üÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþàËÎÙúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‘ êÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÞÉË×úÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÖ ÁÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿãËËÔùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿð)  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþæÎÍÔøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿL @ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿçÒÐ×ùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿe  îÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿíØÖÚøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŠ  ³ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñÞÛÞùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¡  rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòãáâùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿº  5øÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõçåæúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÇ  ¼ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöêèêûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÒ  vÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöëëëúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ×  *íÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöëëìûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÛ  „ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöéêìúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÜ   1öÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõçèêùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÖ   ¨ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõåæéùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÐ  6ñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôãäæùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà  ¤ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõââäöÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ³  Aõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõãâä÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿœ  „ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöãâã÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ} '(Àÿÿÿÿÿÿÿÿÿÿÿÿÿ÷åãäõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ_ (+Wóÿÿÿÿÿÿÿÿÿÿÿ÷æåæöÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ< &13“ÿÿÿÿÿÿÿÿÿÿøçæç÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿî %/9GÐÿÿÿÿÿÿÿÿùéççõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈ #,8C_ãÿÿÿÿÿÿùèèè÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ  *4CJvðÿÿÿÿùèçè÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ@ &0>LR’ûÿÿøååæôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿé #+8ES_ŸøöÛàäôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯  '2=JZi¡ÁÏÛóÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿd  #,6CQ`uœ½èÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ$   &/:FR`{Õÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ´    (1" !1@LU]ejotwz}ƒ„…†‡ˆˆ‰‰ŠŠ‹‹‹Š‰‡………†ˆ‹ŒŒŒŠˆ†……†ˆŠ‹ŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹‹‹‹‹‹ŠŠŠ‰‰ˆ‡‡…„ƒ€~{wtpkf^VMA3# #*2:@GMRX\adhknpsuwyz{}~€‚ƒ„„„„„„„………††††‡‡‡†††††††…………„„ƒƒ‚€~}|zywusqnkhea]XSNHA;3+$  "'+048=@DHKNQTVY[]_acdfghjkllmnooooopppppppooooonmllkjhgfdca`][ZWURNKHEA=951,'#  !$&),.13579;<>?ACDEFGHIIJJKKKKKKKKKJJJIHGFEDCB@>=;97531.,*'$"   !!!!!!!!!!!!!  Slic3r-version_1.39.1/resources/icons/Slic3r.ico000066400000000000000000003073661324354444700215220ustar00rootroot00000000000000€€ (f@@ (BŽ00 ¨%¶J  ¨^p ˆ  hŽŠ(€                     !#%&()*,-./011222233333222110/..,+)('%$!      "&*.159<?BEGJLOQRTVWYZ\\]^_```aaaaaaaa```_^]\\[YWVUSQOLJGEB?<962.*'#   ")/5;AGLQUY]acgilnorsuvxyyz{|}}~~€€€€~~}}||zyyxwusrpnligda]YUQMGB<60)"  !/:DNU\bhlqswy|~ƒƒ„…†‡ˆˆˆ‰‰‰‰ŠŠŠŠŠŠŠŠŠŠŠŠ‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰ˆˆˆ‡‡…„ƒƒ~|ywtqlhc]VNE;0$  ¢ÿ¢ÿ¢ÿ 0FXenuz}„…‡ˆ‰ŠŠŠ‹‹‹‹ŒŒŒŒŒŒŒŒŒ ”Až,a©7z²A¼H ÃJ£ÅMªÈL¨ÇJ£ÄD—¿;‚¶0k¬$P£*—ŽŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹ŠŠŠ‰ˆ‡…„‚~zunf[J0 ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ @\oz…‡‰Š‹‹ŒŒŒŒŒŒŒŒŒŒŒŒŒŒ ’&T¤=ˆ¹V¾ÓdÝæmñõrüüsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿoö÷gåì\ÌÛJ£Å1l­/™ŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹ŠŠˆ…{r`A ! ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ 7Pcow}€ƒ†‡‰‰Š‹‹‹‹Œ‹ŒŒŒ!•<„¶WÂÕkíñsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿo÷øbÚãI¡Ã#N£ŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒŒ‹‹‹‹‹‹Š‰‰‡†„}woeS9 ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ  .=IT]diotxz}ƒ„…‡‡,a¥[ÊØlðósÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsþþeßçGÁ"”‹‹‹‹‹‹‹ŠŠŠŠŠ‰‰‰ˆ‡‡†„ƒ}zwtpkd]UJ?0  ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ  &.6<DJPUZ_cgj r?‹ bØÖsþýsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿmñòRµÉ"K˜‚‚€€~~}|zyxvtromifb^[UPJD=6/& ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ  " ' , 1 5 :0EP¯ƒmðÛsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿrüú^ÏÉ4q‹ddba_][YVSQMKGD@<740+&! ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ)A-3O 5Q =^Ae<]hâgqûÕsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿiç½7xS420.,*(%#   ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‡Ôã •ë ˜ïƒïsý‘sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿrýëiès     ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ~ÿ^tÿ×sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsþ»oö7¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ–ÿ2zÿ·sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿøsÿ€Šÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿQyÿÞsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿ¥vÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿŠÿiwÿñsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÉuÿ(¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ†ÿ„uÿùsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿätÿA¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ ¢ÿ'tÿûsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿ÷tÿT¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿƒÿ†tÿýsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿøtÿW¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿƒÿ„tÿýsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿútÿS¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‚ÿ‹sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿöuÿ:¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿÿ¡sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿuÿÿwÿÿ zÿÿ|ÿÿ~ÿÿÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ~ÿÿ|ÿÿzÿÿ xÿÿuÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÜvÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ€ÿ­sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿtÿÿ xÿÿ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿÿ xÿÿuÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÆxÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‚ÿ sÿÿsÿÿsÿÿtÿÿ xÿÿ|ÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿ}ÿÿ yÿÿuÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿ³¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ~ÿ¨wÿÿ|ÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}ÿÿ wÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿtÿk¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿ·0ÿÿ0ÿÿ0ÿÿ0ÿÿ0ÿÿ0ÿÿ0Œÿÿ/Œÿÿ.‹ÿÿ-‹ÿÿ+Šÿÿ(ˆÿÿ&‡ÿÿ#…ÿÿ „ÿÿƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿzÿÿuÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿôuÿ.¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ#ÿ¼2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ/Œÿÿ*‰ÿÿ'‡ÿÿ#…ÿÿƒÿÿ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿ zÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿŇÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ‹ÿÍ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿ,Šÿÿ(ˆÿÿ"…ÿÿ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿ yÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿtÿb¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ…ÿ ŽÿÏ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ.‹ÿÿ(ˆÿÿ"…ÿÿ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ xÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿå}ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¡ÿŸÿ $ÿÊ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿÿ+Šÿÿ$†ÿÿ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿtÿŽ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¡ÿŸÿ žÿ œÿ "ÿÓ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿÿ*‰ÿÿ"…ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿóxÿ&¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿžÿžÿ žÿ›ÿÿ ‹ÿÝ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿ(ˆÿÿ „ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿtÿ«¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ ÿÿÿ ›ÿšÿ™ÿ’ÿ+"Œÿâ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ-‹ÿÿ$†ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿÿuÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿûwÿ4¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ ¡ÿÿÿ œÿ ›ÿ™ÿ™ÿ˜ÿ" –ÿ<'Žÿè2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ-‹ÿÿ#…ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿuÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿtÿŸ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿŸÿÿœÿ ›ÿšÿ™ÿ™ÿ!—ÿ)—ÿ2‘ÿS'ÿë2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ/Œÿÿ&‡ÿÿ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿvÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿí~ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¡ÿ žÿ œÿ ›ÿšÿ™ÿ™ÿ ˜ÿ(—ÿ2–ÿ=–ÿH”ÿj"Œÿí2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ/Œÿÿ%†ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ wÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿuÿn¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ ¢ÿ ¢ÿŸÿÿœÿ ›ÿšÿ™ÿ˜ÿ&˜ÿ/–ÿ:•ÿF•ÿU”ÿj“ÿ—&Žÿó2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ,Šÿÿ „ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ wÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿtÿÈ—ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ ¢ÿ¡ÿÿœÿ ›ÿ›ÿšÿ™ÿ"—ÿ+—ÿ4–ÿB•ÿR•ÿe–ÿˆ—ÿ°“ÿÒ&‹ÿù2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ)‰ÿÿ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿûzÿ.¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿŸÿ˜ÿ ™ÿ ™ÿ˜ÿ˜ÿ—ÿ'–ÿ2•ÿ=•ÿL”ÿ\”ý€uÄÄ–ÿÈ–ÿÙÿå&Šÿû2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿ$†ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿvÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿuÿu¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ™ÿ™ÿ —ÿ•ÿ–ÿ–ÿ#–ÿ.”ÿ:”ÿH”ÿW•ÿpCoÝÿY˜ç”ÿß“ÿäÿé)ÿú2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ*‰ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿtÿ¿¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ™ÿ˜ÿ ”ÿ“ÿ“ÿ”ÿ“ÿ+“ÿ6’ÿB“ÿO“ÿdN‚Îÿÿÿ\žì‘ÿåŽÿçÿê)‹ÿü2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ.‹ÿÿ!„ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿð€ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿšÿšÿ’ÿ ’ÿ‘ÿÿÿ'‘ÿ2ÿ?‘ÿM‘ÿZ^´ÿÿÿÿÿc°îŽÿèŒÿé‰ÿì'‹ÿü2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ&‡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ zÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿwÿN¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿšÿ•ÿ‘ÿÿÿÿ#ÿ/ÿ<ŽÿIÿVp¿ÿÿÿÿÿÿÿe¶î‹ÿèŠÿèˆÿì)Šÿü2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ)‰ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿÿsÿÿsÿÿsÿÿsÿÿsÿÿuÿ†¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿœÿ™ÿ“ÿ ÿŒÿ‰ÿ ‰ÿ*Šÿ7ŠÿE‹ÿT‡ïq ôÿÿÿÿÿÿÿÿf»î‹ÿè‹ÿç ‹ÿì-Œÿþ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ*‰ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿtÿÿsÿÿsÿÿsÿÿsÿÿtÿ¹¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ˜ÿ–ÿ ‘ÿŠÿ‰ÿ†ÿ&‡ÿ2ˆÿBˆÿPŠÿa&BÒÿÿÿÿÿÿÿÿÿÿoÊìŒÿçÿç ‹ÿí-Œÿý2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ-‹ÿÿ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿÿsÿÿsÿÿsÿÿsÿÿsÿÝÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ–ÿ•ÿŽÿ ‹ÿ‡ÿ…ÿ#…ÿ/„ÿ;…ÿL…ÿ[T™–ÿÿÿÿÿÿÿÿÿÿÿ ÿnÃì‘ÿæ”ÿå ”ÿê-ÿü2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ/Œÿÿƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿvÿÿsÿÿsÿÿsÿÿsÿö~ÿ+¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ–ÿ–ÿÿ‰ÿ †ÿƒÿƒÿ)‚ÿ8‚ÿGƒÿXzép ôÿÿÿÿÿÿÿ ÿÿÿÿ!!!ÿ###ÿÓê˜ÿäÿäÿê-ÿý2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ/Œÿÿ‚ÿÿÿÿÿÿÿÿÿÿÿÿ}ÿÿsÿÿsÿÿsÿÿsÿÿyÿF¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ–ÿŽÿ‹ÿ ˆÿƒÿ‚ÿ#ÿ1ÿ@€ÿQÿc#BÆÿÿÿÿÿ ÿÿÿÿ ÿ!!!ÿ###ÿ$$$ÿ&&&ÿ')+ÿŒÚé£ÿã©ÿä Ÿÿë-Œÿþ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ-‹ÿÿÿÿÿÿÿÿÿÿÿÿÿÿwÿÿsÿÿsÿÿsÿÿwÿa¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿ–ÿŽÿ‡ÿ ƒÿ€ÿ€ÿ*ÿ9ÿIÿ[^¸‚ÿÿÿÿÿÿÿÿÿÿ!!!ÿ"""ÿ$$$ÿ%%%ÿ'''ÿ(((ÿ)+-ÿ—Üè¯ÿãµÿã ¥ÿí-Œÿþ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ+Šÿÿÿÿÿÿÿÿÿÿÿÿ~ÿÿsÿÿsÿÿsÿÿvÿ|¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿŽÿˆÿ ƒÿÿÿ"~ÿ0~ÿA~ÿS~ÿe-Ñÿÿÿÿÿÿÿÿÿ ÿ!!!ÿ###ÿ%%%ÿ&&&ÿ'''ÿ)))ÿ+++ÿ,.0þ ¤áçºÿä¿ÿå ªÿî0Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ(ˆÿÿÿÿÿÿÿÿÿÿÿÿvÿÿsÿÿsÿÿuÿ‘¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿŽÿ‡ÿ €ÿÿ~ÿ(|ÿ8}ÿI}ÿ]Q‘ÿÿÿÿÿÿÿÿÿÿ!!!ÿ"""ÿ$$$ÿ%%%ÿ'''ÿ(((ÿ***ÿ,,,ÿ...ÿ-69ý°çèÃÿæÇÿçªÿñ0Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ!„ÿÿÿÿÿÿÿÿÿÿ|ÿÿsÿÿsÿÿuÿž¢ÿ ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿÿˆÿ ƒÿ~ÿ~ÿ"|ÿ/|ÿ?|ÿRuíi")åÿÿÿÿÿÿÿÿÿ ÿ!!!ÿ###ÿ%%%ÿ&&&ÿ'''ÿ)))ÿ+++ÿ---ÿ///ÿ000ÿ1:=ýºéêËÿèÍÿé±ÿñ0Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿƒÿÿÿÿÿÿÿÿÿÿuÿÿsÿÿuÿ©¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿ‹ÿ„ÿ €ÿ~ÿ}ÿ'{ÿ5{ÿG{ÿYI‚Žÿÿÿÿÿÿÿÿÿÿ ÿ"""ÿ$$$ÿ%%%ÿ'''ÿ(((ÿ***ÿ,,,ÿ...ÿ000ÿ111ÿ555ÿ@JLþ¿éìÐÿêÑÿë±ÿò0Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ-‹ÿÿÿÿÿÿÿÿÿÿ{ÿÿsÿÿuÿ¬¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿ‡ÿÿ ÿ|ÿ{ÿ-{ÿ<zÿOxüa$0Óÿÿÿÿÿÿÿÿÿÿ!!!ÿ###ÿ$$$ÿ&&&ÿ'''ÿ)))ÿ+++ÿ---ÿ///ÿ000ÿ666ÿEEEÿJJJÿFVYýËòíÓÿìÓÿì­ÿô0ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ$†ÿÿÿÿÿÿÿÿ€ÿÿtÿÿuÿ«¢ÿ¢ÿ ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿ‹ÿˆÿ €ÿ}ÿ{ÿ#{ÿ2yÿBzÿU Xªxþÿÿÿÿÿÿÿÿÿ ÿ"""ÿ###ÿ%%%ÿ'''ÿ(((ÿ***ÿ,,,ÿ...ÿ000ÿ999ÿFFFÿJJJÿKKKÿMMMÿI`eüÎôíÔÿìÓÿì©ÿôB—ÿÿ?•ÿÿ<“ÿÿ8‘ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿÿ‚ÿÿÿÿÿÿÿÿ wÿÿuÿ©¢ÿ¢ÿ ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿ†ÿ„ÿ €ÿ|ÿzÿ'zÿ6yÿIyÿ\0L«ÿÿÿÿÿÿÿÿÿÿ ÿ"""ÿ$$$ÿ&&&ÿ'''ÿ(((ÿ***ÿ,,,ÿ///ÿ<<<ÿGGGÿIIIÿJJJÿLLLÿNNNÿPPPÿKdiüÍòíÓÿëÑÿê©ÿóGšÿÿGšÿÿGšÿÿE™ÿÿB—ÿÿ=”ÿÿ8‘ÿÿ3Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ(ˆÿÿÿÿÿÿÿÿ|ÿÿuÿ¦¢ÿ¢ÿ ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿ‹ÿÿ ~ÿ{ÿzÿ-yÿ<yÿOvóa!åÿÿÿÿÿÿÿÿÿÿ!!!ÿ###ÿ$$$ÿ&&&ÿ'''ÿ)))ÿ+++ÿ---ÿ999ÿFFFÿHHHÿJJJÿKKKÿMMMÿOOOÿQQQÿSSSÿLglû ÎøëÑÿèÎÿæ&¯ÿóGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿD˜ÿÿ<”ÿÿ5ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ‚ÿÿÿÿÿÿÿÿuÿ˜¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿ…ÿ€ÿ}ÿzÿ"zÿ1yÿAyÿU Y­uþÿÿÿÿÿÿÿÿÿ ÿ!!!ÿ###ÿ%%%ÿ'''ÿ(((ÿ***ÿ,,,ÿ999ÿEEEÿFFFÿIIIÿJJJÿLLLÿMMMÿPPPÿRRRÿSSSÿUUUÿLjqú Ë÷çÌÿäÊÿá%¦ÿòGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿF™ÿÿA–ÿÿ9’ÿÿ3Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ(ˆÿÿÿÿÿÿ€ÿÿvÿ‹¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿƒÿ ÿ|ÿyÿ%yÿ5xÿFxÿZ2R¤ÿÿÿÿÿÿÿÿÿÿ ÿ"""ÿ$$$ÿ%%%ÿ'''ÿ(((ÿ***ÿ555ÿDDDÿEEEÿGGGÿIIIÿKKKÿMMMÿNNNÿQQQÿSSSÿTTTÿUUUÿXXXÿNmuú ÇùãÇÿÞÅÿÜ#£ÿñGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿC—ÿÿ:’ÿÿ3Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿÿ‚ÿÿÿÿ€ÿÿxÿp¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿ†ÿÿ ~ÿ{ÿzÿ)yÿ:xÿKyÿ] )Óÿÿÿÿÿÿÿÿÿÿ ÿ"""ÿ$$$ÿ&&&ÿ'''ÿ)))ÿ222ÿBBBÿDDDÿEEEÿHHHÿJJJÿKKKÿMMMÿOOOÿQQQÿSSSÿUUUÿVVVÿYYYÿ[[[ÿNwøÄýÛÂÿÙÀÿ×' ÿïGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿC—ÿÿ7‘ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ$†ÿÿÿÿ€ÿÿ{ÿX¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿ†ÿÿ |ÿ{ÿxÿ,xÿ=xÿPpæcîÿÿÿÿÿÿÿÿÿÿ!!!ÿ"""ÿ$$$ÿ&&&ÿ'''ÿ---ÿ???ÿBBBÿEEEÿFFFÿHHHÿJJJÿLLLÿMMMÿPPPÿRRRÿSSSÿUUUÿWWWÿZZZÿ\\\ÿ\\\ÿK|Œó ÁüÕ¾ÿÔ¼ÿÒ(›ÿòGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿF™ÿÿ>•ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ,Šÿÿÿÿ€ÿûÿ9¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿƒÿ~ÿ {ÿzÿ!yÿ0xÿ@wÿTQ›yÿÿÿÿÿÿÿÿÿÿÿ!!!ÿ###ÿ%%%ÿ&&&ÿ(((ÿ<<<ÿBBBÿCCCÿEEEÿFFFÿIIIÿJJJÿLLLÿNNNÿPPPÿSSSÿTTTÿUUUÿXXXÿZZZÿ\\\ÿ]]]ÿ___ÿQw„ô »úÔ»ÿÏ·ÿÐ*ÿïGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿD˜ÿÿ8’ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ‚ÿÿÿîŠÿ&¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿ€ÿ}ÿ{ÿyÿ$xÿ2xÿCxÿX;g”ÿÿÿÿÿÿÿÿÿÿ ÿ!!!ÿ###ÿ%%%ÿ'''ÿ444ÿ@@@ÿBBBÿCCCÿEEEÿGGGÿIIIÿJJJÿMMMÿNNNÿQQQÿSSSÿUUUÿVVVÿXXXÿ[[[ÿ\\\ÿ^^^ÿ```ÿbbbÿQy‡ò ºþиÿ͸ÿÎ-ŸÿîGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ<“ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ$†ÿÿ‚ÿÌ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿÿ|ÿzÿyÿ&xÿ4xÿGwÿ[(<·ÿÿÿÿÿÿÿÿÿÿ ÿ!!!ÿ###ÿ%%%ÿ***ÿ===ÿ@@@ÿBBBÿCCCÿEEEÿGGGÿIIIÿKKKÿMMMÿOOOÿQQQÿSSSÿUUUÿVVVÿYYYÿ[[[ÿ\\\ÿ^^^ÿ```ÿcccÿeeeÿN‚—îºþ̸ÿιÿÏ/ÿóGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ>•ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ+Šÿÿ„ÿª¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿ~ÿ {ÿzÿyÿ(wÿ7wÿJxÿ\!*Ïÿÿÿÿÿÿÿÿÿÿ ÿ!!!ÿ###ÿ%%%ÿ666ÿ>>>ÿ@@@ÿBBBÿDDDÿEEEÿGGGÿJJJÿKKKÿMMMÿOOOÿQQQÿSSSÿUUUÿWWWÿYYYÿ\\\ÿ\\\ÿ^^^ÿaaaÿcccÿeeeÿfffÿM†œìºþκÿиÿÔ.›ÿöGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ@–ÿÿ3Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿÿ†ÿp¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ†ÿ~ÿ {ÿyÿyÿ*wÿ:wÿMqî_æÿÿÿÿÿÿÿÿÿÿ ÿ"""ÿ###ÿ---ÿ===ÿ>>>ÿ@@@ÿBBBÿDDDÿEEEÿGGGÿJJJÿKKKÿMMMÿOOOÿQQQÿSSSÿUUUÿWWWÿYYYÿ\\\ÿ\\\ÿ___ÿaaaÿcccÿeeeÿfffÿhhhÿKލë½ÿÒ¾ÿÕ¼ÿÙ-œÿøGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ@–ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿûŒÿ?¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿ†ÿ|ÿ zÿyÿxÿ,wÿ<wÿOfÕdðÿÿÿÿÿÿÿÿÿÿ ÿ"""ÿ$$$ÿ888ÿ===ÿ>>>ÿ@@@ÿBBBÿDDDÿEEEÿGGGÿJJJÿKKKÿMMMÿOOOÿRRRÿSSSÿUUUÿWWWÿYYYÿ\\\ÿ\\\ÿ___ÿaaaÿcccÿeeeÿfffÿhhhÿjjjÿJ¨ìÀþÖÂÿÚÀÿß6ÿùGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ>•ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2ÿÚ  ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿ†ÿ{ÿ xÿyÿ xÿ.wÿ>wÿQ W°qûÿÿÿÿÿÿÿÿÿÿ ÿ"""ÿ+++ÿ;;;ÿ===ÿ>>>ÿ@@@ÿBBBÿDDDÿEEEÿGGGÿJJJÿKKKÿMMMÿOOOÿQQQÿSSSÿUUUÿWWWÿYYYÿ\\\ÿ\\\ÿ___ÿaaaÿcccÿeeeÿfffÿhhhÿjjjÿlllÿJ§ïÅÿÜÇÿàÁÿå4ŸÿøGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ;“ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ(Œÿ¤¡ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿƒÿ{ÿ zÿyÿ!wÿ-wÿ?wÿRN˜zþÿÿÿÿÿÿÿÿÿÿ ÿ!!!ÿ444ÿ;;;ÿ===ÿ>>>ÿ@@@ÿBBBÿDDDÿEEEÿGGGÿJJJÿKKKÿMMMÿOOOÿQQQÿSSSÿUUUÿWWWÿYYYÿ\\\ÿ\\\ÿ^^^ÿaaaÿcccÿeeeÿfffÿhhhÿjjjÿlllÿmmmÿFœ´ïÊÿâÌÿåÇÿé5ŸÿúGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿF™ÿÿ6ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿý‰ÿY ÿ¡ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿƒÿ{ÿ xÿwÿ!wÿ/wÿ@vÿSFƒƒÿÿÿÿÿÿÿÿÿÿÿ ÿ'''ÿ999ÿ;;;ÿ===ÿ>>>ÿ@@@ÿBBBÿCCCÿEEEÿGGGÿIIIÿKKKÿMMMÿOOOÿQQQÿSSSÿUUUÿVVVÿYYYÿ[[[ÿ\\\ÿ^^^ÿ```ÿcccÿeeeÿeeeÿgggÿiiiÿkkkÿmmmÿnnnÿ?¤¾ðÎÿçÐÿèÉÿì7žÿûGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿB—ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ-ŒÿÝ›ÿŸÿ ÿ¡ÿ¢ÿ ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ’ÿƒÿ{ÿ wÿwÿ!wÿ/wÿ@vÿT=pŒÿÿÿÿÿÿÿÿÿÿÿ ÿ///ÿ999ÿ;;;ÿ===ÿ>>>ÿ@@@ÿBBBÿCCCÿEEEÿGGGÿIIIÿJJJÿMMMÿNNNÿQQQÿSSSÿUUUÿVVVÿXXXÿ[[[ÿ\\\ÿ^^^ÿ```ÿbbbÿdddÿeeeÿgggÿiiiÿkkkÿlllÿmmmÿnnnÿ:ªÄòÒÿêÓÿëÆÿî6šÿüGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ;“ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ$Šÿ ÿžÿ ÿ ÿ¢ÿ ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿƒÿ{ÿ wÿwÿ!wÿ/wÿAvÿT9d“ÿÿÿÿÿÿÿÿÿÿÿ ÿ555ÿ999ÿ;;;ÿ<<<ÿ===ÿ@@@ÿBBBÿCCCÿEEEÿFFFÿIIIÿJJJÿLLLÿNNNÿPPPÿSSSÿTTTÿUUUÿXXXÿZZZÿ\\\ÿ]]]ÿ___ÿaaaÿdddÿeeeÿfffÿhhhÿjjjÿkkkÿmmmÿnnnÿnnnÿ;©ÀôÔÿìÔÿìÅÿï7›ÿüGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿF™ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ/Œÿ÷ŒÿAœÿÿžÿŸÿ ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿƒÿ{ÿ wÿwÿ!wÿ/wÿAvÿS7a”ÿÿÿÿÿÿÿÿÿÿÿ%%%ÿ888ÿ999ÿ:::ÿ<<<ÿ===ÿ???ÿAAAÿBBBÿEEEÿFFFÿHHHÿJJJÿLLLÿMMMÿPPPÿRRRÿSSSÿUUUÿWWWÿZZZÿ\\\ÿ\\\ÿ^^^ÿaaaÿcccÿeeeÿeeeÿgggÿhhhÿjjjÿlllÿmmmÿnnnÿnnnÿ>«ÂõÔÿìÓÿìÃÿî9žÿûGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ<”ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ(‹ÿ¾™ÿ šÿœÿÿŸÿ ÿ ¡ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿƒÿ{ÿ wÿwÿ!wÿ/vÿ?vÿS?rŠÿÿÿÿÿÿÿÿÿÿÿ+++ÿ777ÿ999ÿ:::ÿ<<<ÿ===ÿ???ÿAAAÿBBBÿDDDÿEEEÿHHHÿJJJÿKKKÿMMMÿOOOÿQQQÿSSSÿUUUÿVVVÿYYYÿ[[[ÿ\\\ÿ]]]ÿ```ÿbbbÿdddÿeeeÿfffÿgggÿiiiÿjjjÿlllÿmmmÿmmmÿnnnÿ4±ËóÒÿëÑÿë Ãÿì<šÿüGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿD˜ÿÿ3Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ‰ÿl—ÿ ™ÿšÿœÿžÿŸÿ ¡ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿƒÿ{ÿ xÿvÿ vÿ.vÿ?vÿRDÿÿÿÿÿÿÿÿÿÿÿ000ÿ777ÿ999ÿ:::ÿ;;;ÿ===ÿ>>>ÿ@@@ÿBBBÿDDDÿEEEÿGGGÿIIIÿKKKÿMMMÿNNNÿQQQÿSSSÿTTTÿUUUÿXXXÿZZZÿ\\\ÿ\\\ÿ^^^ÿaaaÿbbbÿdddÿeeeÿfffÿgggÿiiiÿjjjÿkkkÿlllÿmmmÿmmmÿ.·ÖðÏÿéÌÿè »ÿë<™ÿüGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ8‘ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ'ŠÿÏÿ*•ÿ—ÿ™ÿ›ÿœÿžÿ  ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿ|ÿ{ÿ xÿvÿvÿ-vÿ>vÿQPžtþÿÿÿÿÿÿÿÿÿÿ444ÿ777ÿ888ÿ999ÿ;;;ÿ===ÿ>>>ÿ@@@ÿBBBÿCCCÿEEEÿFFFÿIIIÿJJJÿLLLÿMMMÿPPPÿRRRÿSSSÿUUUÿWWWÿYYYÿ[[[ÿ\\\ÿ]]]ÿ___ÿaaaÿcccÿeeeÿeeeÿfffÿgggÿjjjÿnnnÿrrrÿtttÿvvvÿxxxÿ9¸ØðÉÿçÄÿæ ³ÿê;˜ÿüGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ?•ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿù‹ÿY‘ÿ&“ÿ•ÿ—ÿšÿœÿÿ Ÿÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿÿ{ÿ xÿvÿvÿ,vÿ<vÿP Y·lùÿÿÿÿÿÿÿÿÿ"""ÿ555ÿ666ÿ888ÿ999ÿ:::ÿ<<<ÿ===ÿ???ÿAAAÿBBBÿDDDÿFFFÿHHHÿJJJÿKKKÿMMMÿOOOÿQQQÿSSSÿTTTÿVVVÿXXXÿZZZÿ\\\ÿ\\\ÿ^^^ÿ```ÿaaaÿdddÿiiiÿmmmÿrrrÿvvvÿwwwÿxxxÿxxxÿyyyÿyyyÿyyyÿ3±ÖíÁÿä¼ÿä ©ÿé@–ÿþGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿD˜ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ+ÿ¼Œÿ-ÿ&‘ÿ“ÿ•ÿ˜ÿ›ÿ œÿ Ÿÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿÿ|ÿ xÿvÿuÿ*uÿ:uÿMdÔbðÿÿÿÿÿÿÿÿÿ%%%ÿ555ÿ666ÿ777ÿ999ÿ:::ÿ<<<ÿ===ÿ>>>ÿ@@@ÿBBBÿDDDÿEEEÿGGGÿIIIÿJJJÿLLLÿNNNÿPPPÿRRRÿSSSÿUUUÿVVVÿXXXÿZZZÿ\\\ÿ]]]ÿdddÿkkkÿoooÿsssÿtttÿuuuÿuuuÿvvvÿvvvÿvvvÿwwwÿwwwÿwwwÿwwwÿ4®Úë·ÿã²ÿã Ÿÿë@–ÿþGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿF™ÿÿ7‘ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿõˆÿWŠÿ-Œÿ%ÿ’ÿ”ÿ—ÿ™ÿ œÿ ÿ ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿÿ~ÿ yÿwÿuÿ(vÿ8vÿKqô[áÿÿÿÿÿÿÿÿÿ(((ÿ555ÿ666ÿ777ÿ999ÿ999ÿ;;;ÿ===ÿ>>>ÿ@@@ÿBBBÿCCCÿEEEÿFFFÿHHHÿJJJÿKKKÿMMMÿOOOÿQQQÿSSSÿTTTÿUUUÿWWWÿ\\\ÿeeeÿkkkÿmmmÿnnnÿpppÿqqqÿrrrÿtttÿuuuÿuuuÿuuuÿuuuÿvvvÿvvvÿvvvÿvvvÿvvvÿ1¨Üê«ÿâ¦ÿã›ÿê@—ÿýGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ:’ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ‡ÿ“†ÿ8ˆÿ-‹ÿ%Žÿÿ“ÿ–ÿ˜ÿ ›ÿ ÿŸÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿyÿyÿwÿuÿ&vÿ5vÿHuÿY)Ëÿÿÿÿÿÿÿÿÿ)))ÿ555ÿ555ÿ666ÿ888ÿ999ÿ:::ÿ<<<ÿ===ÿ???ÿAAAÿBBBÿDDDÿEEEÿGGGÿIIIÿJJJÿLLLÿMMMÿOOOÿQQQÿSSSÿXXXÿaaaÿhhhÿjjjÿlllÿmmmÿmmmÿnnnÿoooÿqqqÿrrrÿsssÿtttÿtttÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿ)¢ãè¡ÿã›ÿä”ÿìC™ÿþGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ<”ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ&‰ÿ؃ÿE„ÿ8‡ÿ-‰ÿ%ŒÿŽÿ‘ÿ”ÿ—ÿ šÿ œÿŸÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿzÿyÿwÿvÿ$uÿ2uÿDuÿX'<°ÿÿÿÿÿÿÿÿÿ,,,ÿ444ÿ555ÿ666ÿ777ÿ999ÿ:::ÿ;;;ÿ===ÿ>>>ÿ@@@ÿBBBÿCCCÿEEEÿFFFÿHHHÿJJJÿKKKÿMMMÿNNNÿSSSÿ]]]ÿeeeÿfffÿgggÿhhhÿjjjÿlllÿmmmÿmmmÿnnnÿnnnÿpppÿqqqÿqqqÿrrrÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿotvþ'œéè–ÿå‘ÿçÿðC—ÿþGšÿÿGšÿÿGšÿÿGšÿÿ>•ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ.‹ÿú ÿq‚ÿDƒÿ7†ÿ-ˆÿ$‹ÿÿÿ“ÿ–ÿ ™ÿ›ÿžÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿ{ÿzÿ wÿvÿ"uÿ0uÿ@uÿU7eÿÿÿÿÿÿÿÿÿ///ÿ444ÿ555ÿ555ÿ777ÿ888ÿ999ÿ;;;ÿ<<<ÿ===ÿ???ÿAAAÿBBBÿDDDÿEEEÿGGGÿIIIÿJJJÿNNNÿXXXÿ```ÿbbbÿdddÿeeeÿfffÿgggÿhhhÿjjjÿkkkÿmmmÿmmmÿmmmÿnnnÿnnnÿoooÿpppÿpppÿqqqÿqqqÿqqqÿqqqÿqqqÿpppÿpppÿnprþ'“æêÿçŠÿè‡ÿðC—ÿÿGšÿÿGšÿÿGšÿÿ?•ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ„ÿ¨ÿP€ÿB‚ÿ6„ÿ+‡ÿ#ŠÿŒÿÿ’ÿ–ÿ ˜ÿšÿÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿwÿ xÿvÿuÿ-uÿ=uÿP P£pýÿÿÿÿÿÿÿÿ000ÿ333ÿ444ÿ555ÿ666ÿ888ÿ999ÿ:::ÿ;;;ÿ===ÿ>>>ÿ@@@ÿBBBÿCCCÿDDDÿEEEÿGGGÿNNNÿ[[[ÿ^^^ÿ___ÿaaaÿcccÿdddÿeeeÿfffÿfffÿhhhÿiiiÿkkkÿlllÿmmmÿmmmÿmmmÿnnnÿnnnÿnnnÿnnnÿoooÿoooÿoooÿnnnÿnnnÿnnnÿnnnÿmnnÿ'Žäì‡ÿè†ÿé‡ÿðD˜ÿÿGšÿÿGšÿÿ?•ÿÿ2Žÿÿ2Žÿÿ‡ÿÑ}ÿ]~ÿL€ÿ@ÿ4„ÿ*†ÿ"Šÿ‹ÿÿ‘ÿ ”ÿ —ÿšÿÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿxÿ xÿvÿuÿ*uÿ9uÿLjá`íÿÿÿÿÿÿÿÿ000ÿ333ÿ444ÿ555ÿ555ÿ777ÿ888ÿ999ÿ;;;ÿ<<<ÿ===ÿ???ÿAAAÿBBBÿCCCÿEEEÿNNNÿZZZÿ\\\ÿ]]]ÿ^^^ÿ___ÿaaaÿcccÿdddÿeeeÿeeeÿfffÿgggÿiiiÿjjjÿkkkÿlllÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿgnrýŒñë„ÿçƒÿçˆÿñD˜ÿÿGšÿÿ=”ÿÿ2Žÿÿ&‰ÿá}ÿr}ÿX~ÿIÿ=ÿ2„ÿ(†ÿ Šÿ‹ÿŽÿ‘ÿ ”ÿ—ÿšÿœÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿvÿwÿuÿ&uÿ5uÿGuÿX)Êÿÿÿÿÿÿÿÿ///ÿ222ÿ333ÿ555ÿ555ÿ666ÿ888ÿ999ÿ:::ÿ;;;ÿ===ÿ>>>ÿ???ÿAAAÿBBBÿOOOÿXXXÿYYYÿ[[[ÿ\\\ÿ]]]ÿ^^^ÿ___ÿaaaÿbbbÿdddÿeeeÿeeeÿfffÿgggÿhhhÿiiiÿjjjÿkkkÿkkkÿlllÿlllÿmmmÿmmmÿmmmÿmmmÿmmmÿlllÿlllÿkkkÿkkkÿjjjÿemuüŠðé„ÿå„ÿä‹ÿïE™ÿÿ<“ÿÿ'‹ÿëÿ€|ÿb|ÿR~ÿDÿ:ÿ/ƒÿ%†ÿ‰ÿ‹ÿŽÿÿ “ÿ–ÿ™ÿžÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿwÿ wÿvÿ#uÿ1tÿAuÿU2U™ÿÿÿÿÿÿÿÿ---ÿ222ÿ333ÿ444ÿ555ÿ555ÿ777ÿ888ÿ999ÿ:::ÿ<<<ÿ===ÿ>>>ÿAAAÿNNNÿUUUÿWWWÿXXXÿZZZÿ[[[ÿ\\\ÿ]]]ÿ^^^ÿ___ÿaaaÿbbbÿcccÿeeeÿeeeÿeeeÿfffÿfffÿgggÿhhhÿiiiÿiiiÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿiiiÿiiiÿhhhÿgggÿfffÿahpü‰ïå†ÿ߆ÿÛÿå)Œÿì€ÿŽ|ÿi|ÿY|ÿK~ÿ?ÿ5ÿ+ƒÿ"‡ÿŠÿ‹ÿÿ’ÿ •ÿ™ÿšÿ ÿ£ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿ uÿvÿ uÿ-tÿ=uÿP Q§nýÿÿÿÿÿÿÿ***ÿ111ÿ222ÿ333ÿ444ÿ555ÿ666ÿ777ÿ999ÿ999ÿ;;;ÿ<<<ÿ>>>ÿLLLÿSSSÿUUUÿVVVÿWWWÿXXXÿZZZÿ[[[ÿ\\\ÿ]]]ÿ^^^ÿ___ÿ```ÿaaaÿcccÿdddÿeeeÿeeeÿeeeÿfffÿfffÿfffÿgggÿgggÿhhhÿhhhÿhhhÿhhhÿhhhÿgggÿgggÿfffÿfffÿfffÿeeeÿeeeÿ^juúŒõÞ‡ÿÑ…ÿ·€ÿ|ÿq{ÿ_{ÿQ}ÿE~ÿ:€ÿ0ÿ'ƒÿˆÿ‹ÿ‹ÿŽÿ ÿ •ÿ™ÿÿ¡ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿ vÿvÿuÿ)uÿ8tÿIpõYÞÿÿÿÿÿÿÿ'''ÿ111ÿ222ÿ333ÿ444ÿ555ÿ555ÿ666ÿ888ÿ999ÿ:::ÿ;;;ÿJJJÿQQQÿSSSÿTTTÿUUUÿVVVÿWWWÿXXXÿZZZÿ[[[ÿ\\\ÿ\\\ÿ^^^ÿ___ÿ___ÿaaaÿbbbÿcccÿdddÿeeeÿeeeÿeeeÿeeeÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿeeeÿeeeÿeeeÿeeeÿdddÿcccÿZhuúŽðÌ…ÿŸÿw{ÿb{ÿU|ÿI}ÿ>ÿ3€ÿ+‚ÿ#…ÿ‰ÿ‹ÿ‹ÿ ÿ ÿ“ÿ—ÿÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿ uÿvÿ$uÿ3tÿCtÿU+G£ÿÿÿÿÿÿÿ%%%ÿ111ÿ111ÿ222ÿ333ÿ444ÿ555ÿ666ÿ777ÿ888ÿ999ÿBBBÿPPPÿQQQÿRRRÿSSSÿTTTÿUUUÿVVVÿWWWÿXXXÿYYYÿ[[[ÿ\\\ÿ\\\ÿ]]]ÿ^^^ÿ___ÿ```ÿaaaÿbbbÿcccÿcccÿdddÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿdddÿcccÿcccÿbbbÿaaaÿ```ÿYenú ñ¡}ÿb{ÿU|ÿJ}ÿ@~ÿ6ÿ-€ÿ&ƒÿ…ÿ‹ÿÿ‹ÿ Žÿ ÿ”ÿ•ÿŸÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿ sÿtÿuÿ.tÿ>tÿO Q§nýÿÿÿÿÿÿ"""ÿ111ÿ111ÿ222ÿ222ÿ333ÿ555ÿ555ÿ666ÿ777ÿ;;;ÿMMMÿNNNÿPPPÿQQQÿRRRÿSSSÿTTTÿUUUÿVVVÿWWWÿXXXÿYYYÿZZZÿ\\\ÿ\\\ÿ\\\ÿ]]]ÿ^^^ÿ___ÿ___ÿ```ÿaaaÿbbbÿbbbÿcccÿcccÿcccÿcccÿdddÿcccÿcccÿcccÿcccÿbbbÿbbbÿaaaÿ```ÿ___ÿ___ÿ^^^ÿ]]]ÿVgvó‹ôs|ÿI}ÿB~ÿ8€ÿ0€ÿ&‚ÿ ƒÿ…ÿŒÿ‘ÿŽÿ Žÿ’ÿ”ÿ—ÿ¡ÿ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿ sÿuÿuÿ(uÿ8tÿHsýW"Ôÿÿÿÿÿÿÿ111ÿ111ÿ111ÿ222ÿ333ÿ444ÿ555ÿ555ÿ666ÿEEEÿMMMÿMMMÿOOOÿPPPÿQQQÿRRRÿSSSÿTTTÿUUUÿUUUÿWWWÿXXXÿXXXÿZZZÿ[[[ÿ\\\ÿ\\\ÿ\\\ÿ]]]ÿ^^^ÿ___ÿ___ÿ___ÿ```ÿ```ÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿ```ÿ```ÿ___ÿ___ÿ___ÿ^^^ÿ]]]ÿ\\\ÿ\\\ÿ\\\ÿQeuðña~ÿ7ÿ0ÿ)‚ÿ"‚ÿƒÿ†ÿŒÿ”ÿ”ÿ ‘ÿ“ÿ•ÿšÿ£ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿ sÿtÿ"uÿ1tÿAtÿS:k‰ÿÿÿÿÿÿÿ000ÿ111ÿ111ÿ111ÿ222ÿ333ÿ444ÿ555ÿ:::ÿJJJÿLLLÿMMMÿMMMÿOOOÿPPPÿQQQÿRRRÿSSSÿSSSÿUUUÿUUUÿVVVÿWWWÿXXXÿYYYÿZZZÿ[[[ÿ\\\ÿ\\\ÿ\\\ÿ]]]ÿ]]]ÿ^^^ÿ^^^ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ^^^ÿ^^^ÿ]]]ÿ]]]ÿ\\\ÿ\\\ÿ\\\ÿ[[[ÿZZZÿYYYÿQdsï&“òN€ÿ'‚ÿ"ƒÿƒÿ…ÿ‡ÿ‹ÿ •ÿ—ÿ •ÿ’ÿ–ÿžÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿ sÿuÿtÿ*uÿ:tÿKmì[åÿÿÿÿÿÿ+++ÿ111ÿ111ÿ111ÿ111ÿ222ÿ333ÿ444ÿDDDÿJJJÿKKKÿLLLÿMMMÿMMMÿOOOÿPPPÿQQQÿQQQÿSSSÿSSSÿTTTÿUUUÿVVVÿWWWÿXXXÿXXXÿYYYÿZZZÿ[[[ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ]]]ÿ]]]ÿ]]]ÿ]]]ÿ]]]ÿ]]]ÿ]]]ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ[[[ÿZZZÿYYYÿXXXÿXXXÿWWWÿPhxè*šõ>‚ÿ„ÿ†ÿ‰ÿ‹ÿ Œÿ “ÿ ˜ÿ —ÿ”ÿ›ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿsÿtÿ$uÿ3tÿCtÿT5]ÿÿÿÿÿÿ&&&ÿ111ÿ111ÿ111ÿ111ÿ222ÿ222ÿ777ÿHHHÿJJJÿJJJÿKKKÿLLLÿMMMÿMMMÿNNNÿPPPÿQQQÿQQQÿRRRÿSSSÿTTTÿUUUÿUUUÿVVVÿWWWÿXXXÿXXXÿYYYÿYYYÿZZZÿ[[[ÿ[[[ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ[[[ÿ[[[ÿZZZÿYYYÿYYYÿXXXÿXXXÿWWWÿVVVÿUUUÿUUUÿMhyå,ž÷/…ÿ‰ÿŠÿÿ ÿ ‘ÿ‘ÿ˜ÿ™ÿ ÿ£ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿ sÿsÿtÿ+uÿ<tÿKoóYâÿÿÿÿÿÿ111ÿ111ÿ111ÿ111ÿ111ÿ222ÿ>>>ÿHHHÿIIIÿJJJÿJJJÿKKKÿLLLÿMMMÿMMMÿNNNÿOOOÿPPPÿQQQÿRRRÿSSSÿSSSÿTTTÿUUUÿUUUÿVVVÿWWWÿWWWÿXXXÿXXXÿXXXÿYYYÿYYYÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿYYYÿYYYÿXXXÿXXXÿXXXÿWWWÿWWWÿVVVÿUUUÿUUUÿTTTÿSSSÿSSSÿLetÞ7ží&‡ÿ ‹ÿ ‘ÿ •ÿ ”ÿ•ÿ˜ÿžÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿsÿsÿsÿ$sÿ2tÿCtÿT9kˆÿÿÿÿÿÿ///ÿ111ÿ111ÿ111ÿ111ÿ111ÿBBBÿHHHÿIIIÿIIIÿJJJÿJJJÿJJJÿKKKÿMMMÿMMMÿNNNÿOOOÿPPPÿQQQÿQQQÿRRRÿSSSÿSSSÿTTTÿUUUÿUUUÿUUUÿVVVÿWWWÿWWWÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿWWWÿWWWÿVVVÿUUUÿUUUÿUUUÿTTTÿSSSÿSSSÿRRRÿQQQÿQQQÿKeuÛ3£ò%‰ÿ ÿ–ÿ™ÿ›ÿÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿ sÿsÿsÿ*tÿ:tÿKp÷XÜÿÿÿÿÿ'''ÿ111ÿ111ÿ111ÿ111ÿ111ÿEEEÿGGGÿHHHÿHHHÿIIIÿJJJÿJJJÿJJJÿKKKÿLLLÿMMMÿMMMÿNNNÿOOOÿPPPÿQQQÿQQQÿRRRÿSSSÿSSSÿSSSÿTTTÿUUUÿUUUÿUUUÿUUUÿVVVÿVVVÿVVVÿWWWÿWWWÿWWWÿWWWÿWWWÿVVVÿVVVÿVVVÿUUUÿUUUÿUUUÿUUUÿTTTÿSSSÿSSSÿSSSÿRRRÿQQQÿQQQÿPPPÿOOOÿNNNÿDh}ס÷#Šÿÿšÿœÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿ sÿsÿsÿ#sÿ0tÿAtÿRF‰yþÿÿÿÿÿ111ÿ111ÿ111ÿ111ÿ111ÿFFFÿFFFÿGGGÿHHHÿHHHÿIIIÿJJJÿJJJÿJJJÿKKKÿLLLÿMMMÿMMMÿNNNÿOOOÿOOOÿPPPÿQQQÿQQQÿRRRÿRRRÿSSSÿSSSÿSSSÿTTTÿTTTÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿTTTÿTTTÿSSSÿSSSÿSSSÿRRRÿRRRÿQQQÿQQQÿPPPÿOOOÿOOOÿNNNÿMMMÿMMMÿEiÑ+¤÷„ÿÿ™ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿ sÿsÿsÿ(sÿ7tÿHtÿW!/¼ÿÿÿÿÿ,,,ÿ111ÿ111ÿ111ÿ111ÿFFFÿFFFÿFFFÿGGGÿGGGÿHHHÿIIIÿJJJÿJJJÿJJJÿJJJÿKKKÿLLLÿMMMÿMMMÿNNNÿOOOÿOOOÿPPPÿQQQÿQQQÿQQQÿRRRÿRRRÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿRRRÿRRRÿQQQÿQQQÿQQQÿPPPÿOOOÿOOOÿNNNÿMMMÿMMMÿLLLÿKKKÿJJJÿEj€Î)¤÷…ÿˆÿÿ›ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿsÿsÿsÿ!sÿ.sÿ=tÿN^Ëbñÿÿÿÿ###ÿ111ÿ111ÿ111ÿ111ÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿHHHÿIIIÿJJJÿJJJÿJJJÿJJJÿKKKÿLLLÿLLLÿMMMÿMMMÿNNNÿNNNÿOOOÿPPPÿPPPÿQQQÿQQQÿQQQÿQQQÿQQQÿRRRÿRRRÿRRRÿRRRÿRRRÿRRRÿRRRÿRRRÿRRRÿQQQÿQQQÿQQQÿQQQÿQQQÿPPPÿPPPÿOOOÿNNNÿNNNÿMMMÿMMMÿLLLÿLLLÿKKKÿJJJÿJJJÿJJJÿJnƒÌ%¥ÿ|ÿ†ÿÿ™ÿŠÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿ sÿsÿsÿ&sÿ3sÿBtÿS9h‰þÿÿÿÿ///ÿ111ÿ111ÿ111ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿHHHÿHHHÿIIIÿJJJÿJJJÿJJJÿJJJÿKKKÿLLLÿLLLÿMMMÿMMMÿMMMÿNNNÿNNNÿOOOÿOOOÿPPPÿPPPÿPPPÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿPPPÿPPPÿPPPÿOOOÿOOOÿNNNÿNNNÿMMMÿMMMÿMMMÿLLLÿLLLÿKKKÿJJJÿJJJÿJJJÿJJJÿMMMÿQQQÿKp…×ÿzÿ|ÿ‰ÿÿƒÿsÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿsÿ sÿsÿsÿ*sÿ8sÿGsÿV%6´ÿÿÿÿ"""ÿ111ÿ111ÿ111ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿHHHÿIIIÿIIIÿJJJÿJJJÿJJJÿJJJÿKKKÿKKKÿLLLÿMMMÿMMMÿMMMÿMMMÿNNNÿNNNÿNNNÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿNNNÿNNNÿNNNÿMMMÿMMMÿMMMÿMMMÿLLLÿKKKÿKKKÿJJJÿJJJÿJJJÿJJJÿIIIÿLLLÿQQQÿPPPÿPPPÿLy’¹•ÿsÿ xÿzÿ}ÿÿsÿsÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿ sÿsÿsÿ"sÿ.sÿ<sÿLpùW"Ôÿÿÿÿ+++ÿ111ÿ111ÿEEEÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿHHHÿHHHÿIIIÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿKKKÿKKKÿLLLÿLLLÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿLLLÿLLLÿKKKÿKKKÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿIIIÿIIIÿQQQÿPPPÿPPPÿOOOÿOOOÿK{˜´Šÿsÿ vÿ wÿyÿsÿsÿsÿsÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿsÿ sÿsÿsÿ%sÿ2sÿ@sÿO Xºfîÿÿÿÿ000ÿ111ÿCCCÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿHHHÿHHHÿIIIÿIIIÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿKKKÿKKKÿKKKÿLLLÿLLLÿLLLÿLLLÿLLLÿMMMÿMMMÿMMMÿLLLÿLLLÿLLLÿLLLÿLLLÿKKKÿKKKÿKKKÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿIIIÿIIIÿHHHÿHHHÿMMMÿPPPÿOOOÿOOOÿOOOÿOOOÿOOOÿGw“¯†ÿuÿsÿ sÿsÿsÿsÿsÿsÿ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿsÿ sÿsÿsÿ(sÿ4sÿCsÿRF‹wúÿÿÿ'''ÿ111ÿ>>>ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿHHHÿHHHÿIIIÿIIIÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿIIIÿIIIÿHHHÿHHHÿGGGÿLLLÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿ?nŒ¥‡ÿ$zÿvÿ sÿ sÿsÿsÿsÿ¢ÿ¢ÿsÿsÿsÿsÿ sÿsÿsÿsÿ*sÿ7sÿEsÿT7j‡ýÿÿÿ,,,ÿ777ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿHHHÿHHHÿHHHÿIIIÿIIIÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿIIIÿIIIÿHHHÿHHHÿHHHÿGGGÿGGGÿJJJÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿLLLÿ###ÿQv—ˆÿ'|ÿvÿ sÿ sÿsÿsÿ¢ÿsÿsÿsÿsÿsÿ sÿsÿsÿ sÿ,sÿ8sÿFsÿU0T•ÿÿÿÿ111ÿDDDÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿGGGÿHHHÿHHHÿHHHÿIIIÿIIIÿIIIÿIIIÿIIIÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿIIIÿIIIÿIIIÿIIIÿIIIÿHHHÿHHHÿHHHÿGGGÿGGGÿGGGÿFFFÿIIIÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿ...ÿÿÿWƒ…†ÿ#yÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿ"sÿ-sÿ:sÿGsÿV1U—þÿÿ###ÿ;;;ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿGGGÿGGGÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿIIIÿIIIÿIIIÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿGGGÿGGGÿGGGÿGGGÿFFFÿFFFÿFFFÿJJJÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿ888ÿÿÿÿÿY…uÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿ"sÿ.sÿ:sÿHsÿU7j‡úÿÿ%%%ÿCCCÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿKKKÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿ>>>ÿÿÿÿÿÿ'/׈ÿ&yÿ|ÿ xÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿ#sÿ.sÿ:sÿGsÿT=w÷ÿÿ///ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿLLLÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿEEEÿÿÿÿÿÿ(3Ó‰íE‰ÿ*ˆÿ‰ÿ…ÿ }ÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿ#sÿ-sÿ9sÿFsÿSGŽvçÿÿ666ÿGGGÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿMMMÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿDDDÿÿÿÿÿÿ->Ç ìMˆÿ7Šÿ-‹ÿ ŽÿŒÿ ŒÿŠÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿ"sÿ,sÿ8sÿDsÿQ ]Æc&Éÿÿ555ÿIIIÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿIIIÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿ???ÿÿÿÿÿÿ<_¤÷Nÿ=„ÿ1‡ÿ'ˆÿ‹ÿÿ ŒÿŠÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿ!sÿ+sÿ6sÿBsÿNsÿV-O™ûÿ000ÿJJJÿGGGÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿLLLÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿ999ÿÿÿÿÿ"ðRš}wýL{ÿ@}ÿ3€ÿ(‚ÿƒÿ†ÿ…ÿÿŠÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿ sÿ)sÿ3sÿ>sÿJsÿUE‡xÜÿ***ÿGGGÿJJJÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿJJJÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿNNNÿ222ÿÿÿÿÿ&6Æ _ÃgsÿKsÿ>tÿ2vÿ(yÿ|ÿ}ÿ~ÿ zÿÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿsÿ&sÿ0sÿ:sÿEsÿPkè[*G ò ÿ>>>ÿNNNÿJJJÿGGGÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿJJJÿNNNÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿDDDÿ&&&ÿÿÿÿö7d’pùWsÿKsÿ?sÿ4sÿ(sÿsÿsÿvÿ xÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿsÿ#sÿ,sÿ6sÿ@sÿJsÿT Yºf$7²ü,,,ÿGGGÿOOOÿLLLÿHHHÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿIIIÿLLLÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿLLLÿ333ÿÿÿÿý#3¹ WµisÿRsÿHsÿ<sÿ2sÿ(sÿsÿsÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿsÿsÿ sÿ(sÿ0sÿ:sÿDsÿMsÿV H’t'Çü888ÿMMMÿOOOÿMMMÿKKKÿIIIÿGGGÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿIIIÿKKKÿNNNÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿNNNÿ???ÿ"""ÿÿÿÿ$Ð C‡xsÿVsÿLsÿBsÿ8sÿ.sÿ&sÿsÿsÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿsÿ#sÿ+sÿ4sÿ<sÿEsÿNsÿVJ”t!0º !!ñ777ÿKKKÿOOOÿOOOÿOOOÿNNNÿMMMÿKKKÿJJJÿJJJÿJJJÿJJJÿJJJÿKKKÿLLLÿMMMÿNNNÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿNNNÿ<<<ÿ&&&ÿÿÿö*Á?z~sÿVsÿNsÿEsÿ<sÿ2sÿ*sÿ"sÿsÿsÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿsÿsÿsÿ&sÿ-sÿ5sÿ=sÿFsÿNsÿV^Ëa3Z‘!'Ò...ûBBBÿNNNÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿDDDÿ555ÿ###ÿÿÿÝ'@¥ V±hsÿVsÿNsÿFsÿ=sÿ4sÿ,sÿ$sÿsÿsÿsÿ sÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿsÿ sÿ&sÿ.sÿ5sÿ<sÿDsÿKsÿRsÿV P§l&=ª"&)Ü555÷DDDÿLLLÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿNNNÿFFFÿ:::ÿ%%%ÿÿþå!/»A‚zqûWsÿTsÿMsÿEsÿ=sÿ5sÿ.sÿ&sÿ sÿsÿsÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿsÿsÿsÿ sÿ&sÿ,sÿ3sÿ:sÿ@sÿGsÿNsÿTqûWV¶g8j‡+=²+04Õ668ë<<<ùCCCÿFFFÿJJJÿLLLÿLLLÿLLLÿLLLÿKKKÿHHHÿDDDÿ>>>ÿ666ÿ+++ÿ"""ûòÙ!.¼4] G‘tjæ[sÿVsÿOsÿIsÿBsÿ;sÿ4sÿ-sÿ&sÿ sÿsÿsÿsÿ sÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿsÿsÿsÿsÿ$sÿ*sÿ0sÿ6sÿ<sÿAsÿFsÿLsÿPsÿUsÿViå\V¶gC…x6dŠ,L›'=©$5²"0º!.¼!1·"1·&;­)D¡5_=w€ P¦m^ËasÿVsÿVsÿRsÿMsÿHsÿCsÿ=sÿ7sÿ1sÿ+sÿ&sÿ sÿsÿsÿsÿ sÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿsÿsÿsÿsÿ"sÿ&sÿ+sÿ0sÿ5sÿ:sÿ>sÿBsÿFsÿIsÿLsÿNsÿQsÿRsÿTsÿUsÿVsÿVsÿVsÿVsÿTsÿSsÿRsÿOsÿLsÿJsÿFsÿCsÿ?sÿ:sÿ6sÿ2sÿ,sÿ(sÿ#sÿsÿsÿsÿsÿ sÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿsÿsÿsÿsÿsÿ"sÿ&sÿ*sÿ-sÿ0sÿ4sÿ6sÿ9sÿ<sÿ>sÿ?sÿ@sÿBsÿBsÿBsÿBsÿBsÿAsÿ@sÿ>sÿ<sÿ:sÿ8sÿ4sÿ2sÿ.sÿ*sÿ'sÿ#sÿsÿsÿsÿsÿsÿ sÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿsÿsÿsÿsÿsÿsÿ"sÿ$sÿ'sÿ)sÿ+sÿ-sÿ.sÿ0sÿ0sÿ1sÿ1sÿ1sÿ0sÿ0sÿ.sÿ-sÿ,sÿ*sÿ(sÿ&sÿ#sÿ sÿsÿsÿsÿsÿsÿsÿ sÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ!sÿ"sÿ"sÿ"sÿ"sÿ"sÿ!sÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿÿÿÿð?ÿÿÿÿÿüÿÿÿÿÿ€ÿÿÿÿÿÿÿþÿÿ/üÿÿüÿÿüÿÿüÿÿþÿÿÿÿÿÿð?ÿÿÿüÿÿÿÿðÿÿÿÿÿÀÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿü?ÿÿÿÿüÿÿÿÿüÿÿÿÿüÿÿÿÿø€ÿÿÿÿðÀ?ÿÿÿïà€ÿÿÿ÷àÐÿÿÿÿ€ÿØÿÿÿÿûüÿÿÿþùðÿÿÿÿÄ0ÿÿÿÿ‡‡ÿÿÿσ?ÿÿÿÿ€ÿÿÿÿ€ÿÿÿÿÿÿÿþÿÿÿþÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÀÿÿÿÿàÿÿÿÿðÿÿÿàÿÿÿÀ?ÿÿÿÀ?ÿÿÿÀÿÿÿÀÿÿÿ€ÿÿÿ€ÿÿÿ€ÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿþÿÿÿÿÿþÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿ€ÿÿÿ€ÿÿÿ€ÿÿÿÀÿÿÿÀÿÿÿÀÿÿÿÀ?ÿÿÿÀ?ÿÿÿÀ?ÿÿÿÀÿÿÿàÿÿÿÿàÿÿÿÿàÿÿÿÿàÿÿÿÿàÿÿÿð?ÿÿÿð?ÿÿÿðÿÿÿøÿÿÿø(ÿÿÿø<ÿÿÿü~ÿÿÿü~ÿÿÿþÿÿÿÿþƒÿÿÿÿƒÿÿÿÿÃÿÿÿÿ€Ãÿÿÿÿ€ãÿÿÿÿÀóÿÿÿÿàûÿÿÿÿàÿÿÿÿðÿÿÿÿøÿÿÿÿøÿÿÿÿüÿÿÿÿþÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÀÿÿÿÿÿÿàÿÿÿÿÿÿðÿÿÿÿÿÿøÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿà?ÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà?ÿÿÿÿÿÿÿÿÿøÿÿÿÿ(@€   !$&)+,.......,+)'%"   "/:DMU\afknqtuxyzz|||||{zyxvtrolgb]VOF<1% ¢ÿEcs}‚‡‰‹‹ŒŒŒŒCŸ3p¯@»J£ÅN­ÉM«ÉH Ã=ˆ¸-cª5šŒŒŒŒŒŒŒŒŒŒŒŒ‹‰‡ƒ~uhO ¢ÿ¢ÿ¢ÿ Oo}…‰‹ŒŒŒŒ ’6x²V¾Óhæíqûüsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿo÷ùeßçO°Ë+`¨ŒŒŒŒŒŒŒŒŒŒŒŒ‹‰†sZ &¢ÿ¢ÿ ¢ÿ!0>JS]d={P²·lïísÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿfãæE™¶!ˆ~|zwtpke^VLA4%¢ÿ¢ÿ ¢ÿ'; 9XF„lî’rýîsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿpøè\ÊŠ 2,($ ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿÿZtÿÔsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÅsÿ7¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ$„ÿ«sÿýsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿøsÿh¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ$€ÿ×sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿƒÿºsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿ ƒÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ…ÿ¯sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿ—¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ„ÿµsÿÿsÿÿvÿÿ zÿÿ}ÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿ}ÿÿzÿÿvÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿt¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ‡ÿ½ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿÿuÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿúuÿ;¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿgÇÿ 0šÿÆ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿÿ/Œÿÿ,Šÿÿ(ˆÿÿ$†ÿÿƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ|ÿÿuÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÔ~ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ+±ÿ /™ÿÆ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿ+Šÿÿ%†ÿÿƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿtÿˆ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¡ÿÿ§ÿ-˜ÿÍ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ-‹ÿÿ%†ÿÿ‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~ÿÿvÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿóxÿ#¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ ˜ÿšÿ ˜ÿ ÿ)-—ÿÖ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿÿ(ˆÿÿƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿ xÿÿsÿÿsÿÿsÿÿsÿÿsÿÿtÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿÿŸÿžÿÿ•ÿ •ÿ•ÿ –ÿ1˜ÿO+–ÿà2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ*‰ÿÿƒÿÿÿÿÿÿÿÿÿÿÿÿzÿÿsÿÿsÿÿsÿÿsÿÿsÿê~ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ›ÿ›ÿ šÿ –ÿ ÿ ÿ’ÿ“ÿ/”ÿE“ÿa˜ÿ¨(”ÿð2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ(ˆÿÿ‚ÿÿÿÿÿÿÿÿÿÿ zÿÿsÿÿsÿÿsÿÿsÿÿuÿ^¢ÿ¢ÿ¢ÿ¢ÿ”ÿ’ÿŽÿ †ÿ ˆÿÿÿ*‘ÿ@’ÿ[l·¸m¹á”ÿã)”ÿ÷2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ/Œÿÿ"…ÿÿÿÿÿÿÿÿÿÿ xÿÿsÿÿsÿÿsÿÿtÿ¯¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿƒÿ…ÿ‚ÿ ƒÿ†ÿ‰ÿ'Œÿ9ÿQwÌšÿÿnÂíŽÿê)‘ÿø2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ(ˆÿÿÿÿÿÿÿÿÿÿwÿÿsÿÿsÿÿsÿä¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿ‹ÿ|ÿ~ÿ ~ÿÿƒÿ%†ÿ3ŠÿH…ëpùÿÿÿqÎîŒÿé*‘ÿø2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ,Šÿÿ‚ÿÿÿÿÿÿ€ÿÿtÿÿsÿÿsÿð¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿÿÿzÿ }ÿ~ÿ"€ÿ/„ÿAˆÿW";ÒÿÿÿÿÿsÎë–ÿæ+—ÿö2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ.‹ÿÿ‚ÿÿÿÿÿÿ{ÿÿsÿÿsÿî¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿ|ÿ{ÿ zÿ{ÿ|ÿ,~ÿ<ÿOG†‘ÿÿ ÿÿÿÿ"""ÿŠÖêªÿä+›ÿ÷2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ.‹ÿÿ‚ÿÿÿÿÿÿvÿÿsÿî¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿyÿyÿyÿyÿ%{ÿ5}ÿHqÛe ôÿÿÿÿ ÿ###ÿ&&&ÿ)))ÿ×êÀÿæ, ÿø2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ,Šÿÿÿÿÿÿ|ÿÿsÿî¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿ|ÿ{ÿ xÿyÿxÿ-yÿ?{ÿS5Sµÿÿÿÿÿ!!!ÿ$$$ÿ'''ÿ+++ÿ///ÿ°ÛíÍÿë,¢ÿú2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ(ˆÿÿÿÿÿÿuÿî¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿ{ÿzÿ wÿxÿ%xÿ6yÿIjÖhôÿÿÿÿÿ"""ÿ&&&ÿ)))ÿ---ÿ777ÿEHIÿÀãðÔÿí,¤ÿú2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ „ÿÿÿÿ{ÿï¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿyÿyÿwÿxÿ,xÿ=yÿQ;e›ÿÿÿÿÿ ÿ###ÿ'''ÿ***ÿ999ÿGGGÿJJJÿMSTþÂãñ Ñÿê=§ÿùC—ÿÿ>•ÿÿ8’ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ.‹ÿÿÿÿÿð¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿxÿ xÿwÿ!xÿ1xÿCxÿY!*Ôÿÿÿÿÿ!!!ÿ$$$ÿ'''ÿ666ÿEEEÿHHHÿLLLÿPPPÿSTUÿ½ãìÉÿâB¥ÿ÷GšÿÿGšÿÿD˜ÿÿ>•ÿÿ5ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ$†ÿÿÿñ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿwÿ wÿwÿ%wÿ6wÿIh×fõÿÿÿÿÿ!!!ÿ%%%ÿ111ÿBBBÿEEEÿIIIÿMMMÿQQQÿUUUÿV^_ýºéá¾ÿÛC¤ÿõGšÿÿGšÿÿGšÿÿF™ÿÿ>•ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ-‹ÿÿ‚ÿñ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿvÿwÿwÿ)wÿ:wÿMJŒÿÿÿÿÿÿ"""ÿ***ÿ???ÿBBBÿFFFÿJJJÿMMMÿRRRÿUUUÿZZZÿ\bdý²éعÿÔC¤ÿóGšÿÿGšÿÿGšÿÿGšÿÿE™ÿÿ9’ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿƒÿñ¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿvÿwÿvÿ+wÿ>wÿQ3V¡ÿÿÿÿÿÿ"""ÿ777ÿ???ÿBBBÿFFFÿJJJÿNNNÿSSSÿVVVÿ[[[ÿ^^^ÿ^fgü°ëÖ¹ÿÖC¤ÿöGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ=”ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ#†ÿó¢ÿ¢ÿ ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿvÿvÿvÿ-wÿ@wÿT';¹ÿÿÿÿÿÿ,,,ÿ<<<ÿ???ÿCCCÿFFFÿJJJÿNNNÿSSSÿVVVÿ[[[ÿ^^^ÿcccÿbknû¶îÛÁÿÞC¥ÿúGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ=”ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ#‡ÿߢÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿvÿvÿvÿ.wÿBwÿV +Íÿÿÿÿÿ ÿ666ÿ<<<ÿ???ÿBBBÿFFFÿJJJÿNNNÿSSSÿVVVÿ[[[ÿ^^^ÿcccÿeeeÿdnqü¾ðäÇÿæB¤ÿúGšÿÿGšÿÿGšÿÿGšÿÿGšÿÿ;“ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ„ÿ£¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿvÿvÿ vÿ0vÿCvÿW#Ûÿÿÿÿÿ'''ÿ999ÿ<<<ÿ???ÿBBBÿFFFÿJJJÿMMMÿRRRÿUUUÿZZZÿ]]]ÿbbbÿeeeÿhhhÿfrvüÊòìÐÿìC¦ÿúGšÿÿGšÿÿGšÿÿGšÿÿF™ÿÿ5ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ.ŒÿÿÿQ¡ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿvÿvÿ uÿ0vÿDvÿX âÿÿÿÿÿ///ÿ888ÿ;;;ÿ>>>ÿBBBÿEEEÿIIIÿMMMÿQQQÿUUUÿYYYÿ\\\ÿ```ÿdddÿfffÿjjjÿewzüÎòíÎÿíC¤ÿúGšÿÿGšÿÿGšÿÿGšÿÿ@–ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ#†ÿ×–ÿŸÿ¡ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿvÿvÿ uÿ0vÿCvÿW"Ûÿÿÿÿÿ444ÿ888ÿ:::ÿ===ÿAAAÿEEEÿHHHÿLLLÿPPPÿSSSÿWWWÿ[[[ÿ^^^ÿbbbÿeeeÿgggÿjjjÿfuxýÊôìÈÿíC¢ÿúGšÿÿGšÿÿGšÿÿGšÿÿ5ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿÿÿ{™ÿÿ ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿuÿuÿ/uÿBvÿW(Íÿÿÿÿ ÿ555ÿ777ÿ999ÿ===ÿ@@@ÿCCCÿGGGÿJJJÿNNNÿRRRÿUUUÿYYYÿ\\\ÿ___ÿeeeÿkkkÿoooÿtttÿl€„üÅõêºÿêD ÿúGšÿÿGšÿÿGšÿÿ<”ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ!…ÿàŒÿ%–ÿšÿžÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿuÿuÿ-tÿ@uÿU%5¹ÿÿÿÿ$$$ÿ444ÿ666ÿ999ÿ<<<ÿ???ÿBBBÿEEEÿIIIÿLLLÿPPPÿSSSÿXXXÿbbbÿjjjÿoooÿrrrÿtttÿuuuÿuuuÿl~…û±ôç©ÿéDÿúGšÿÿGšÿÿ@–ÿÿ2Žÿÿ2Žÿÿ-‹ÿü €ÿkŽÿ “ÿ˜ÿ ÿ¡ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿsÿuÿ+tÿ=uÿQ0PŸÿÿÿÿ'''ÿ333ÿ555ÿ888ÿ:::ÿ===ÿAAAÿDDDÿGGGÿJJJÿPPPÿ\\\ÿeeeÿhhhÿkkkÿmmmÿnnnÿpppÿrrrÿsssÿsssÿizƒúŸöç—ÿìDšÿûGšÿÿC—ÿÿ2Žÿÿ2Žÿÿ„ÿ³…ÿ0Šÿ ÿ–ÿ ›ÿ¡ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿ sÿtÿ'uÿ:uÿNC€ÿÿÿÿ)))ÿ222ÿ555ÿ777ÿ999ÿ<<<ÿ???ÿBBBÿEEEÿQQQÿ]]]ÿ```ÿdddÿeeeÿgggÿjjjÿlllÿmmmÿnnnÿnnnÿnnnÿnnnÿduúŒöéÿîDšÿûD˜ÿÿ2Žÿÿ!‡ÿåÿKƒÿ.ˆÿŽÿ”ÿ ™ÿ ÿ¢ÿ¢ÿ¢ÿsÿsÿsÿ sÿtÿ#uÿ6tÿHaÍdøÿÿÿ)))ÿ222ÿ444ÿ555ÿ888ÿ:::ÿ===ÿBBBÿRRRÿXXXÿ\\\ÿ]]]ÿ```ÿcccÿeeeÿfffÿhhhÿjjjÿkkkÿlllÿlllÿlllÿkkkÿ`q€ú†÷èÿë@˜ÿû&ˆÿözÿpÿ>‚ÿ*ˆÿÿ’ÿ ™ÿ ÿ¢ÿ¢ÿsÿsÿsÿ sÿsÿtÿ0tÿCtÿW#Ôÿÿÿ'''ÿ111ÿ222ÿ555ÿ666ÿ999ÿ===ÿMMMÿTTTÿVVVÿYYYÿ\\\ÿ]]]ÿ___ÿbbbÿdddÿeeeÿfffÿfffÿgggÿgggÿgggÿfffÿfffÿZl{úŒ÷ß‹ÿÈ ~ÿŽ|ÿNÿ6‚ÿ#‰ÿŽÿ’ÿ›ÿ¡ÿ¢ÿ¢ÿsÿsÿsÿsÿsÿsÿ*tÿ>tÿQ/P›ÿÿÿ%%%ÿ111ÿ111ÿ333ÿ555ÿ888ÿIIIÿPPPÿRRRÿTTTÿVVVÿXXXÿ[[[ÿ\\\ÿ^^^ÿ```ÿbbbÿcccÿdddÿeeeÿeeeÿeeeÿdddÿcccÿbbbÿWk{÷ŠõŸ{ÿU}ÿ?€ÿ+‚ÿŠÿŒÿ ’ÿšÿ¢ÿ¢ÿ¢ÿsÿsÿsÿ sÿsÿ$tÿ6tÿI [Àfùÿÿ!!!ÿ111ÿ111ÿ222ÿ444ÿ???ÿLLLÿMMMÿPPPÿRRRÿTTTÿVVVÿXXXÿZZZÿ\\\ÿ\\\ÿ^^^ÿ___ÿ___ÿ```ÿ```ÿ```ÿ___ÿ___ÿ^^^ÿ\\\ÿTguòŽóf~ÿ2€ÿ"‚ÿŒÿÿ “ÿžÿ¢ÿ¢ÿ ¢ÿ¢ÿsÿsÿsÿ sÿsÿsÿ.tÿAtÿU".Àÿÿÿ000ÿ111ÿ111ÿ555ÿHHHÿJJJÿLLLÿMMMÿPPPÿRRRÿSSSÿUUUÿWWWÿXXXÿZZZÿ[[[ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ[[[ÿZZZÿXXXÿPdsï*”íL€ÿƒÿ‡ÿ ’ÿ ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿsÿsÿsÿsÿsÿsÿ'sÿ8tÿL P¥nûÿÿ---ÿ111ÿ111ÿ;;;ÿHHHÿJJJÿJJJÿLLLÿMMMÿOOOÿQQQÿSSSÿTTTÿUUUÿVVVÿXXXÿXXXÿYYYÿYYYÿYYYÿYYYÿYYYÿXXXÿXXXÿVVVÿUUUÿTTTÿMaoê/™ì=}ÿ†ÿƒÿŒÿ‹ÿsÿ¢ÿ ¢ÿ¢ÿsÿsÿsÿ sÿsÿ sÿ0sÿAtÿU"1¹ÿÿ&&&ÿ111ÿ111ÿAAAÿFFFÿHHHÿIIIÿJJJÿKKKÿMMMÿNNNÿPPPÿQQQÿSSSÿSSSÿTTTÿUUUÿUUUÿUUUÿVVVÿUUUÿUUUÿUUUÿTTTÿSSSÿSSSÿQQQÿPPPÿJ`oç.šï9zÿÿ}ÿsÿsÿsÿÿžÿ ¢ÿsÿsÿsÿsÿsÿsÿ'sÿ8tÿK cÑbðÿÿ000ÿ111ÿDDDÿFFFÿFFFÿGGGÿIIIÿJJJÿJJJÿLLLÿMMMÿNNNÿPPPÿQQQÿQQQÿRRRÿSSSÿSSSÿSSSÿSSSÿSSSÿRRRÿQQQÿQQQÿPPPÿNNNÿMMMÿLLLÿF\hè(˜î:vÿ{ÿsÿ sÿsÿsÿ™ÿÿ¢ÿsÿsÿsÿsÿ sÿsÿsÿ.sÿ>sÿP;mˆÿÿ)))ÿ111ÿEEEÿFFFÿFFFÿFFFÿGGGÿHHHÿJJJÿJJJÿKKKÿLLLÿMMMÿMMMÿNNNÿOOOÿPPPÿPPPÿPPPÿPPPÿPPPÿOOOÿNNNÿMMMÿMMMÿLLLÿKKKÿJJJÿJJJÿKcqæ<¡ô?sÿuÿsÿ sÿsÿ}ÿ•ÿ—ÿsÿsÿsÿsÿsÿsÿ$sÿ4sÿDsÿV%8±ÿÿ000ÿBBBÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿIIIÿJJJÿJJJÿJJJÿKKKÿLLLÿLLLÿMMMÿMMMÿMMMÿMMMÿMMMÿLLLÿLLLÿKKKÿJJJÿJJJÿJJJÿIIIÿKKKÿOOOÿNdqà;ô<sÿsÿsÿ sÿsÿÿ—ÿsÿsÿsÿsÿ sÿsÿsÿ)sÿ8sÿInðZ$Ðÿ$$$ÿ===ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿHHHÿIIIÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿIIIÿHHHÿGGGÿJJJÿOOOÿOOOÿOOOÿK`oÚ ‰õ7sÿsÿsÿ sÿsÿŠÿsÿsÿsÿsÿ sÿsÿsÿ,sÿ<sÿLeÚ^!Öÿ000ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿHHHÿHHHÿIIIÿIIIÿIIIÿIIIÿIIIÿHHHÿHHHÿGGGÿGGGÿFFFÿHHHÿOOOÿOOOÿOOOÿNNNÿ...ÿ3CÊ„ê0sÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ"sÿ.sÿ>sÿLgà]'Èÿ:::ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿGGGÿFFFÿFFFÿFFFÿFFFÿFFFÿIIIÿOOOÿOOOÿOOOÿOOOÿ666ÿÿÿa˜c{ÿuÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿ#sÿ0sÿ=sÿLo÷W#4¶ÿ???ÿGGGÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿJJJÿOOOÿOOOÿOOOÿOOOÿ666ÿÿüUŒqˆÿ4†ÿ"ÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿ#sÿ.sÿ<sÿIsÿV3\‘ñ;;;ÿIIIÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿHHHÿMMMÿOOOÿOOOÿOOOÿMMMÿ111ÿÿ",Õ\¯Zzÿ4~ÿ'~ÿ{ÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿ"sÿ,sÿ8sÿEsÿR [Áe#1º///ûIIIÿJJJÿGGGÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿHHHÿLLLÿOOOÿOOOÿOOOÿOOOÿDDDÿ%%%ÿù,F©gßOsÿ8sÿ*sÿsÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿsÿ)sÿ4sÿ?sÿJsÿV Kšr'2Ã999ùLLLÿMMMÿJJJÿIIIÿGGGÿGGGÿGGGÿHHHÿJJJÿLLLÿNNNÿOOOÿOOOÿOOOÿOOOÿGGGÿ111ÿ÷"0¼ P§lsÿKsÿ=sÿ0sÿ#sÿsÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿsÿ$sÿ.sÿ8sÿBsÿLsÿVW¶g 4O¥58<áCCCúNNNÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿKKKÿ???ÿ---ú!Þ)D¢ Z½esÿSsÿHsÿ<sÿ1sÿ&sÿsÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿsÿ'sÿ0sÿ9sÿBsÿJsÿRqûW U¯i!ErŒ,>R°6=EË=@DÜ?ACä?ABè:=@å26;Û*19È.B®7g‰ T¯jpùWsÿRsÿIsÿ@sÿ6sÿ-sÿ$sÿsÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿsÿ sÿ(sÿ/sÿ6sÿ=sÿDsÿJsÿNsÿRsÿVsÿVsÿVsÿVsÿVsÿVsÿVsÿSsÿNsÿJsÿDsÿ=sÿ6sÿ.sÿ&sÿsÿsÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿsÿsÿ%sÿ+sÿ0sÿ5sÿ9sÿ<sÿ@sÿBsÿBsÿCsÿBsÿBsÿ@sÿ<sÿ9sÿ5sÿ0sÿ*sÿ$sÿsÿsÿsÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿsÿsÿsÿsÿ sÿ$sÿ'sÿ*sÿ,sÿ.sÿ/sÿ/sÿ/sÿ.sÿ,sÿ*sÿ'sÿ$sÿ sÿsÿsÿsÿsÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿsÿ sÿ sÿsÿsÿsÿsÿsÿÿøÿÿÿÿÿÿàÿà?ÿÿ€ÿÿÿÿÿÿ0ÿÿœx?ÿøüÿñäÿùÿÿ€ÿÿÿÿÿÿÿÿÀÿÿ€ÿÿÿÿ?þ?þ?þ?ü?ü?üüüüü?ü?ü?ü?ü?üüüüÿüÿüÿþþþ?ÿ?ÿÿÿ€ÿ€ÿÀÿÀÿàÿðÿðÿøÿüÿþ?ÿÿ?ÿÿÀÿÿàÿÿÿøÿ(0`  "'+/35799999753/,'" 6IXclsx}€‚„…‡ˆˆˆˆˆˆˆ‡…„‚€}yslcXJ6¢ÿCu…ŒŒŒŒ‘3r°V¿Ôhçínõ÷nõ÷nõ÷nõ÷nõ÷nõ÷nóöcÜåL¨È%Q£ŒŒŒŒŒŒŒŒŒŒŒ†vF ¢ÿ #4CO'bQ²«kíésÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsþþdÝà;‚§yvsnibYPD5#¢ÿ ¢ÿvùYsý×sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿnôÂRµG ¢ÿ¢ÿŽÿ}ÿ«sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿósÿh¢ÿ¢ÿ¢ÿ¢ÿ€ÿ«sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿˆ¢ÿ¢ÿ¢ÿ¢ÿŽÿ7{ÿäsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿŽ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‘ÿ,{ÿåtÿÿvÿÿzÿÿ}ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿ€ÿÿÿÿ{ÿÿ wÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿs¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿÿ,„ÿçÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿ{ÿÿuÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿøuÿ:¢ÿ¢ÿ¢ÿ¢ÿœÿ3(Žÿì1ÿÿ0ÿÿ0ÿÿ0ÿÿ0ÿÿ0ÿÿ/Œÿÿ*‰ÿÿ$†ÿÿƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{ÿÿtÿÿsÿÿsÿÿsÿÿsÿÿsÿÐ~ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ÿšÿ9,ÿî2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ.‹ÿÿ'‡ÿÿƒÿÿÿÿÿÿÿÿÿÿ€ÿÿ wÿÿsÿÿsÿÿsÿÿsÿÿtÿh¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ÿšÿ—ÿM+ÿñ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ+Šÿÿ!„ÿÿÿÿÿÿÿÿÿÿzÿÿsÿÿsÿÿsÿÿsÿÝ€ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ ¢ÿ  ÿ šÿ˜ÿ–ÿ6 ”ÿ~)ÿö2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ+Šÿÿƒÿÿÿÿÿÿÿÿ{ÿÿsÿÿsÿÿsÿÿuÿT¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿœÿ šÿ—ÿ-•ÿP–ÿ¦“ÿß)Žÿú2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ'‡ÿÿÿÿÿÿÿÿ{ÿÿsÿÿsÿÿtÿ¦¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ™ÿ’ÿ’ÿ$’ÿBtÀ¤ÿg²íŠÿë)Œÿû2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ-‹ÿÿƒÿÿÿÿÿÿ xÿÿsÿÿsÿã¢ÿ¢ÿ¢ÿ¢ÿ¢ÿšÿÿ †ÿ ˆÿ@ávýÿÿd´ïŠÿé)ÿú2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿ „ÿÿÿÿ€ÿÿuÿÿsÿþ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ…ÿ‚ÿÿ@€ýl '>áÿÿÿ"""ÿv½íœÿç)‘ÿú2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿÿ „ÿÿÿÿ|ÿÿsÿÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿ~ÿzÿ2zÿh O‘­ÿ$$$ÿ'''ÿ***ÿ---ÿ000ÿŒÂëµÿç+”ÿû2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿ‚ÿÿÿÿvÿÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿÿzÿ"xÿSoæ„&*0ô'''ÿ'''ÿ(((ÿ+++ÿ///ÿ111ÿ555ÿ¡ÆîÄÿí+•ÿû2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ,Šÿÿÿÿ|ÿÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ|ÿwÿ6wÿtBsª'''ÿ'''ÿ'''ÿ***ÿ---ÿ000ÿ444ÿEEEÿNNNÿ(²ÏóÇÿî2—ÿû<“ÿÿ6ÿÿ3Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ%†ÿÿ€ÿÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ‹ÿxÿwÿJwþy *2Ý'''ÿ'''ÿ(((ÿ+++ÿ...ÿ222ÿDDDÿMMMÿRRRÿUUUÿ)²Ñï ¿ÿå<šÿúGšÿÿD˜ÿÿ<”ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ/Œÿÿ€ÿÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ€ÿwÿ%vÿ\bÌ„''(ú'''ÿ'''ÿ(((ÿ+++ÿ///ÿAAAÿKKKÿOOOÿSSSÿWWWÿ\\\ÿ,¬Ôä µÿÛ=šÿúGšÿÿGšÿÿF™ÿÿ=”ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ!„ÿÿ¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿ¢ÿ|ÿ vÿ-vÿkH‡›'''ÿ'''ÿ'''ÿ)))ÿ,,,ÿ888ÿHHHÿLLLÿPPPÿTTTÿYYYÿ\\\ÿaaaÿ.§ÕÛ ¯ÿ×<™ÿúGšÿÿGšÿÿGšÿÿC—ÿÿ6ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ+Šÿø¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿzÿvÿ4uÿt8Z²'''ÿ'''ÿ'''ÿ)))ÿ...ÿCCCÿIIIÿLLLÿQQQÿUUUÿZZZÿ]]]ÿcccÿfffÿ,«ÙÝ µÿß>›ÿûGšÿÿGšÿÿGšÿÿE™ÿÿ7‘ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Žÿ×¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿyÿuÿ:uÿw0EÂ'''ÿ'''ÿ'''ÿ)))ÿ777ÿEEEÿIIIÿMMMÿQQQÿUUUÿZZZÿ^^^ÿcccÿfffÿkkkÿ+µÜæ ¾ÿê>œÿüGšÿÿGšÿÿGšÿÿD˜ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ,Žÿ•¢ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿxÿuÿ<uÿw+:Ì'''ÿ'''ÿ'''ÿ***ÿ???ÿEEEÿIIIÿLLLÿQQQÿUUUÿZZZÿ]]]ÿcccÿfffÿkkkÿnnnÿ*¾ßï Âÿð>œÿüGšÿÿGšÿÿGšÿÿ@–ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿú‘ÿB ÿ¢ÿ ¢ÿ¢ÿ¢ÿ¢ÿvÿuÿ<uÿw+;Ë'''ÿ'''ÿ'''ÿ///ÿBBBÿEEEÿHHHÿLLLÿPPPÿTTTÿYYYÿ\\\ÿaaaÿeeeÿiiiÿmmmÿoooÿ)Ááñ¾ÿí>šÿüGšÿÿGšÿÿF™ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ.Žÿ½™ÿÿ¡ÿ¢ÿ¢ÿ¢ÿvÿuÿ9uÿv/DÀ'''ÿ'''ÿ'''ÿ222ÿAAAÿDDDÿGGGÿKKKÿOOOÿSSSÿWWWÿ\\\ÿ___ÿeeeÿkkkÿsssÿxxxÿ{{{ÿ3¼äì¯ÿèAšÿüGšÿÿE™ÿÿ2Žÿÿ2Žÿÿ2ŽÿúÿQ”ÿšÿ ÿ¢ÿ¢ÿ¢ÿsÿ uÿ3tÿs6Z®'''ÿ'''ÿ'''ÿ666ÿAAAÿCCCÿFFFÿJJJÿMMMÿRRRÿUUUÿ]]]ÿfffÿpppÿuuuÿvvvÿxxxÿzzzÿzz{ÿ2ªäé—ÿéA˜ÿýGšÿÿ2Žÿÿ2Žÿÿ%Œÿ©ˆÿ)ÿ—ÿ žÿ¢ÿ¢ÿ¢ÿsÿuÿ,tÿfI‰•'''ÿ'''ÿ'''ÿ777ÿ@@@ÿBBBÿEEEÿHHHÿLLLÿPPPÿ]]]ÿgggÿlllÿnnnÿqqqÿtttÿuuuÿvvvÿvvvÿuvwÿ/”ãìˆÿî?–ÿý2Žÿÿ'Œÿã€ÿE…ÿ'ÿ”ÿ ›ÿ¢ÿ¢ÿsÿtÿ"tÿXdÑ'''ù'''ÿ'''ÿ888ÿ>>>ÿAAAÿDDDÿFFFÿLLLÿ\\\ÿcccÿeeeÿhhhÿlllÿmmmÿoooÿqqqÿsssÿsssÿsssÿprsÿ-Œæì‡ÿê$‹ÿì €ÿuÿ=„ÿ"‹ÿ’ÿ›ÿ¢ÿ¢ÿsÿsÿtÿGtþu!'0Ø'''ÿ'''ÿ666ÿ===ÿ@@@ÿBBBÿGGGÿXXXÿ]]]ÿ```ÿcccÿeeeÿhhhÿkkkÿmmmÿmmmÿnnnÿnnnÿnnnÿmmmÿlmnþ-ŽäÝ€ÿ„{ÿMÿ0…ÿŒÿ’ÿŸÿ¢ÿsÿ sÿ2tÿr;k¢'''ÿ'''ÿ222ÿ===ÿ>>>ÿAAAÿRRRÿXXXÿ\\\ÿ]]]ÿ```ÿcccÿeeeÿfffÿhhhÿjjjÿkkkÿkkkÿkkkÿjjjÿhhhÿfhjý:ØŸ}ÿ7ÿ!†ÿÿ “ÿ ÿ¢ÿ¢ÿsÿsÿ tÿUiá{%&'ó'''ÿ...ÿ===ÿ===ÿEEEÿUUUÿVVVÿYYYÿ\\\ÿ]]]ÿ___ÿaaaÿdddÿeeeÿeeeÿfffÿfffÿfffÿeeeÿeeeÿdddÿ`cdýG˜Ñ€ƒÿˆÿ •ÿ›ÿ¢ÿ¢ÿ¢ÿ ¢ÿsÿsÿsÿ8sÿu6Y¯'''ÿ)))ÿ<<<ÿ===ÿJJJÿSSSÿUUUÿVVVÿXXXÿ[[[ÿ\\\ÿ^^^ÿ___ÿaaaÿbbbÿcccÿcccÿcccÿbbbÿaaaÿ___ÿ^^^ÿ\^_üN›ÊrŒÿ—ÿ ÿ¢ÿ¢ÿ¢ÿsÿsÿ"sÿTkèz#%'é'''ÿ555ÿ===ÿNNNÿRRRÿSSSÿUUUÿVVVÿXXXÿYYYÿ[[[ÿ\\\ÿ]]]ÿ^^^ÿ^^^ÿ___ÿ^^^ÿ^^^ÿ]]]ÿ\\\ÿ[[[ÿYYYÿW[\ûKšÉmžÿ¢ÿ¢ÿsÿsÿsÿ4sÿlI'''þ+++ÿ===ÿOOOÿQQQÿQQQÿSSSÿTTTÿUUUÿWWWÿXXXÿYYYÿZZZÿ[[[ÿ\\\ÿ\\\ÿ\\\ÿ[[[ÿZZZÿYYYÿXXXÿWWWÿUUUÿZ[]ûV¢Îj¢ÿ¢ÿsÿsÿsÿBsÿt2S¯'''ÿ333ÿJJJÿQQQÿQQQÿQQQÿRRRÿSSSÿTTTÿUUUÿVVVÿWWWÿXXXÿXXXÿXXXÿXXXÿXXXÿWWWÿVVVÿUUUÿTTTÿVVVÿ[[[ÿ\_aúX Ïjsÿ sÿ¢ÿsÿsÿ sÿ"sÿMsÿt,@À(((ÿAAAÿQQQÿQQQÿQQQÿQQQÿQQQÿRRRÿSSSÿTTTÿTTTÿUUUÿUUUÿUUUÿUUUÿUUUÿTTTÿTTTÿSSSÿUUUÿZZZÿZZZÿZZZÿ@DFöx¾]sÿsÿsÿsÿ sÿ)sÿVrüu,A¼,,,ÿKKKÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿRRRÿRRRÿSSSÿSSSÿSSSÿSSSÿSSSÿRRRÿRRRÿTTTÿZZZÿZZZÿZZZÿEEEÿ'''ÿ'7C܃ÿ(ÿsÿsÿsÿ+sÿTsÿt8a§///úLLLÿRRRÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿRRRÿVVVÿZZZÿZZZÿZZZÿCCCÿ'''ÿ#:TÓ{ëk‚ÿ2†ÿsÿsÿsÿsÿ(sÿJsÿt P¦‰%*1ÚGGGÿTTTÿRRRÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿRRRÿUUUÿYYYÿZZZÿZZZÿVVVÿ;;;ÿ''(ú>k¬pövtÿHvÿxÿsÿsÿsÿ sÿsÿ?sÿgpùu?wœ47<áMMMÿVVVÿUUUÿTTTÿTTTÿTTTÿUUUÿWWWÿYYYÿZZZÿZZZÿVVVÿCCCÿ--.ú*:É U¯†sÿqsÿIsÿ&sÿsÿsÿsÿ sÿsÿ0sÿNsÿorüuOžŒ,=S¿@BFæMMMùSSSÿVVVÿWWWÿUUUÿRRRÿHHHÿ999ó)/6Ø4X« [¿‚sÿtsÿZsÿ8sÿsÿ sÿsÿsÿsÿsÿsÿ2sÿJsÿcsÿthãz R§Š?vœ5Zª1N³2P±6_§D— W·„oówsÿtsÿZsÿ>sÿ&sÿsÿsÿsÿsÿsÿ sÿsÿ*sÿ;sÿMsÿ]sÿhsÿosÿssÿssÿnsÿesÿYsÿIsÿ6sÿ$sÿsÿ sÿsÿsÿsÿsÿ sÿsÿsÿ$sÿ,sÿ1sÿ3sÿ2sÿ0sÿ*sÿ"sÿsÿsÿsÿsÿÿÿø?x?x?€ÿ~ÿÿx?ÿyÿ±€ÿÃÀÿçàÿü€ÿüÿüü?þ?þüüüüøüøüüüüüüü?ü?üþ?þþÿÏÿÏÿ€oÿ€?ÿÀ?ÿà?ÿà?ÿð?ÿüÿþÿÿÿ€ÿÿÿàÿ( @ ¢ÿ <[iry}€ƒ„…………„ƒ€}ysi[> ¢ÿ¢ÿ >[g&y/h•S¸ÃkììsÿÿsÿÿsÿÿsÿÿlïïU¼Ê2n¢'Š}zvpg[@¢ÿ ¢ÿ9XyÚqáRqö³sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿmòÅTº_9% ¢ÿ¢ÿ¢ÿ„ÿuÿøsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿüsÿxsÿ ¢ÿ¢ÿ¢ÿ~ÿuÿ÷sÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿÿsÿ‰sÿ ¢ÿ¢ÿ€ÿqyÿôzÿÿ|ÿÿÿÿÿÿ€ÿÿÿÿ}ÿÿ{ÿÿ xÿÿvÿÿtÿÿsÿÿsÿÿsÿÿsÿlsÿ.–ÿ{0ÿö.‹ÿÿ,Šÿÿ)‰ÿÿ&‡ÿÿ"…ÿÿƒÿÿ‚ÿÿÿÿ€ÿÿ{ÿÿvÿÿsÿÿsÿÿsÿïuÿ4Šÿ¢ÿ¢ÿ¢ÿMºÿ 2šÿ‰3‘ÿ÷2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿ)‰ÿÿ#…ÿÿƒÿÿÿÿÿÿ xÿÿtÿÿsÿÿtÿzÿ¢ÿ¢ÿ¢ÿ›ÿ –ÿ–ÿ4"—ÿÆ0ÿû2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ-‹ÿÿ%†ÿÿ‚ÿÿÿÿ zÿÿtÿÿsÿôwÿ.¢ÿsÿzÿ}ÿ#‡ÿ?•ÿ‰•ÿÙ‘ÿó0ÿý2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ+Šÿÿ!„ÿÿÿÿ yÿÿsÿÿuÿd¢ÿ¢ÿsÿsÿvÿ-yÿZ~÷….QãÿWò‘ýñ0ÿü2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0Œÿÿ#…ÿÿÿÿvÿÿtÿš¢ÿ¢ÿsÿvÿ"vÿ[vú§ T£Ì ÿÿÿr¦ï¨ýï0“ÿü2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ0ÿÿ"…ÿÿ}ÿÿvÿ¹¢ÿ¢ÿ¢ÿsÿ uÿ5uÿ‚cË´!+òÿÿ$$$ÿ000ÿ0™²ò+¼ýô7˜ÿü:’ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ/Œÿÿƒÿÿ yÿâÿ ¢ÿ¢ÿsÿuÿFuÿ B|Âÿÿÿ'''ÿ999ÿJJKÿ4£¸ô+¸þï?šÿû>•ÿÿ7‘ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ2Žÿÿ(ˆÿÿ€ÿ¼¢ÿ ¢ÿ¢ÿsÿuÿRuÿ­+DÖÿÿ!!!ÿ111ÿDDDÿLLLÿTUUÿ<œ»é1°þåGÿûD˜ÿÿ<”ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ1ÿÿ…ÿŸ¢ÿ ¢ÿ¢ÿsÿuÿZuÿ­",èÿÿ'''ÿ<<<ÿEEEÿMMMÿUUUÿ\__þ@˜ºã-´þèEÿüGšÿÿ@–ÿÿ4ÿÿ2Žÿÿ2Žÿÿ2Žÿÿ‰ÿk¢ÿ ¢ÿ¢ÿsÿtÿ^uÿ­ õÿÿ///ÿ===ÿDDDÿLLLÿTTTÿ\\\ÿdeeþ?¥Àí-¾þôEÿýGšÿÿ=”ÿÿ2Žÿÿ2Žÿÿ3ÿñÿ:¢ÿ ¢ÿ¢ÿsÿtÿatÿ­÷ÿÿ222ÿ<<<ÿBBBÿJJJÿRRRÿZZZÿaaaÿeijþ>¬Áô+ºþôGœÿýF™ÿÿ2Žÿÿ2Žÿÿ1’ÿ¦ “ÿ¡ÿ¢ÿ¢ÿsÿtÿ`tÿ¬"ìÿÿ555ÿ:::ÿ@@@ÿGGGÿPPPÿ]]]ÿiiiÿpppÿqvvþO¥Åï1§þðG›ÿý2Žÿÿ2ÿíÿS•ÿŸÿ¢ÿ¢ÿsÿsÿYtÿ¬".Úÿÿ333ÿ888ÿ===ÿEEEÿUUUÿ```ÿfffÿkkkÿmmmÿlprýHŽÁï3–ÿõ4‘ÿø$ŒÿŸ…ÿ*ÿ›ÿ£ÿsÿsÿMtÿ¬3ZÄÿÿ111ÿ555ÿ===ÿMMMÿWWWÿ\\\ÿ```ÿdddÿeeeÿfffÿdgkýD‚»î …ÿ¢}ÿH…ÿ Žÿ Ÿÿ¢ÿsÿ sÿ;sÿ R¨´úÿ000ÿ222ÿCCCÿNNNÿSSSÿVVVÿZZZÿ\\\ÿ^^^ÿ___ÿ^^^ÿ\^aûF€®Ô“ÿK…ÿÿ ÿ¢ÿ ¢ÿsÿsÿ'sÿmmì¬'=Ñÿ---ÿ111ÿGGGÿJJJÿMMMÿQQQÿSSSÿUUUÿWWWÿXXXÿWWWÿUUUÿSUWûG€¤Ç¥ÿ2ÿ¢ÿ¢ÿsÿsÿsÿGsÿ U²²ñ'''ÿ111ÿFFFÿGGGÿJJJÿLLLÿNNNÿPPPÿQQQÿQQQÿQQQÿPPPÿNNNÿLNPûH€¡Ã"¤ÿ5sÿsÿ¢ÿsÿsÿ(sÿiqû«?{¼ÿ...ÿFFFÿFFFÿFFFÿHHHÿJJJÿJJJÿKKKÿKKKÿKKKÿJJJÿJJJÿIIIÿMQSûO€¢ÀŠû<sÿsÿsÿsÿsÿ7sÿqø«7gÁ###ÿBBBÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿGGGÿGGGÿFFFÿGGGÿMMMÿOOOÿ>@AýU‹žÿ(sÿ sÿsÿsÿBsÿ†qù«E‡º,-.óEEEÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿHHHÿNNNÿOOOÿ>>>ÿ2Jç n߆vÿ4sÿ sÿsÿsÿBsÿ~rü«bЮ$9SÎ@@@ùIIIÿGGGÿGGGÿGGGÿJJJÿMMMÿOOOÿJJJÿ234ô??ùDsÌ cÑ–sû=sÿsÿsÿsÿLsþ‰mížX«´.NuÑ:RpÝ;Snà/LnÖKˆÁcÔ§rýsÿRsÿsÿsÿsÿsÿsÿ0sÿOsÿnsÿsÿ‡sÿˆsÿƒsÿxsÿ_sÿ7sÿsÿsÿsÿsÿsÿsÿ(sÿ0sÿ1sÿ,sÿ!sÿsÿsÿ   àøààààààààààààààððüüÿ(  ¢ÿ¢ÿ ¢ÿ¢ÿ^+]R¶ÃbÚßbÙà]Î×B‘´‡ymD¢ÿ ‚ÿxtÿÝsÿÿsÿÿsÿÿsÿÿsÿøsÿ sÿ¢ÿ€ÿ| xÿû|ÿÿ}ÿÿ|ÿÿ{ÿÿ wÿÿtÿÿsÿÁsÿyÿsÿ $’ÿz0ÿù-‹ÿÿ*‰ÿÿ$†ÿÿÿÿ{ÿÿtÿÿtÿŽsÿsÿ zÿ&…ÿO”ÿÜ1ÿý2Žÿÿ2Žÿÿ/Œÿÿ&‡ÿÿ~ÿÿtÿé¢ÿsÿsÿtÿC Z ¬"1ûŒâö0‘ÿü2Žÿÿ2Žÿÿ2Žÿÿ*‰ÿÿ|ÿþ¢ÿ¢ÿsÿsÿ%vÿ\#,íÿ%/2ý,³âö;˜ÿû6ÿÿ2Žÿÿ2Žÿÿ'ˆÿÿ¢ÿ ¢ÿsÿ sÿ+wÿjÿ ÿ999ÿELNü4¨áêBœÿú<“ÿÿ2Žÿÿ0ÿú¢ÿ¢ÿsÿ sÿ.vÿmÿ...ÿGGGÿ\\\ÿdhjý3ºäôCÿü2Žÿÿ0ÿÒ ÿ ¢ÿsÿ sÿ3tÿpÿ222ÿMMMÿlllÿ‚‚‚ÿsvxþAŸâô-Žÿø#ÿx•ÿsÿsÿ5sÿtò000ÿOOOÿmmmÿuuuÿmmmÿY\\þ+ƒÏÒ{ÿ={ÿsÿ sÿsÿ sÿ/sÿj'@¸)))ÿJJJÿZZZÿ___ÿZZZÿNNNÿFGHý8ŒÃ´yÿ:xÿsÿ sÿsÿ!sÿRiåy%-ÔAAAÿJJJÿKKKÿJJJÿFFFÿGGGÿMMMÿg­tÿ.uÿsÿsÿsÿ0sÿ_içx%7M»AABõGGGÿGGGÿJJJÿIIIþ7APÙfÑssÿ/sÿsÿsÿsÿsÿ0sÿSsÿqsÿssÿssÿssÿssÿssÿisÿDsÿsÿ ÿ@` À€€€€€€€€€€€Slic3r-version_1.39.1/resources/icons/Slic3r.png000066400000000000000000000401251324354444700215170ustar00rootroot00000000000000‰PNG  IHDRššQÿQgAMA± üa cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFbKGDÿÿÿ ½§“ pHYs  šœ?WIDATxÚí½y´$Wy'øûî%—·/Uõj{µi©’JBH-,/mƒÛ´1à¶c|Ú`slÏÐ ^Ʀ=í¡Í±gÚОÁÇÝÆ046à­ 4±a°@¦´"IH UI¥R-o¹EÄýæ7âFdäö^¾WU²¿s"32"2ò.¿üöûñÇ— •¿û×£øwŸŨAJ‰F£ÕÕU03¤”p]¥R J)¸® fNÎ-//£\.ƒ™ADX__‡çy(—ËÃŽã CT*DQ„ à8ˆD„0 ÁÌh6›¨T*`fÓ²REs&ÇÙ `†ˆæÃ0,I)ǰRÊ'¢Ž¢¨.„Xˆ¢hÁqœGÂ0\w]÷4WJRJ„a"B(•JÉoµZ-H)ADˆ¢J)ø¾0 ¡”¡Ñh \.@Ò/ó0Rh4[ø›ÿy·e 5¤¹y³êzÚ¹ØØ¹œˆ™¡”šB¼ÀÕ®ë^áûþU¾ïtg¯ã8)%™IB$ߥ¢(BEÃa¢Ñh Õj!Š¢¥Ô³žbæ„O8ADßÄðàpÑèŸÖƒ˜y\)u½”òÆr¹ü/Êåò5•JeoµZ…çyp]7áxñõ=ïi_«”BhµZSkkkSõzýšF£ñªV«¡ëºßaæ$¢/0ó]иØã2(ý3Ð ˆ™G”R· !^[*•^5::ºll Õj5AF$›m3äy|ßÇØØ˜A`}}+++ÎÚÚÚ•­VëJfþWBåyÞ#ÌügÌü "z@t±Ç«úg eéEBˆŸðêÉÉÉSSS ¸€” ›ò`u˜˜˜@†X]]Åââ"ÖÖÖ3Bü;¿"„xDñ§þÀâżnôOh±Q0BD¯uç§}ßÙôô´O”é­W¯vàI)199‰‰‰ Ôëu,,,`qqaúRÊ륔×ø9)åˆè£J©»6Ëa·‚þI-ØDµZ}«ëº?ëyÞÑ©©)LMM%Þvƒ«[[ p*• ªÕ*¦§§qáÂ,.."Š"!æ…o“R¾-Š¢Ïzž÷^w_ì¶ÛôO hñ„¹®ëþŒ”òÝBˆÃعsg≢KWå1à/—ËØ·o¦¦¦pæÌ¬¬¬$†â‡+•ÊDQô)f~?3?t±Û âb7`;©\.¿Á󼻥”.—ˇ8€ýû÷Ã÷}DQ´i¥~»Èü!*• <ˆùùy¸®›üI˜ÙB¼Éqœ¯{ž÷a!Äî‹Ý·@#¢„^*•>)„¸avv‡Æøø8”R—Œ˜””R`fLMMáÈ‘#˜žžNŽ3W]×}»”ò"zÝ CfmC¡ç5Ј¨,„øUÇqîPJ½Öu]ÌÏÏcïÞ½R^ÒbrŠ¢žçaÿþýØ·o¤”ÉŸ'Ýa!ħ‰è¿°_Mv¨ ±àñ>aˆ€{ÞMJy³âKBˆ÷)¥FFFFpäÈLLL\Vb²_2œyff‡B¹\.âÔ?#¥s‘|½ €o4ЊÀllÏK I)ßíºî—ÜE¦§§qèÐ!xž÷¼ábÈèn‡ÆØØ˜þSÅçˆ×á=c#Þ§þ¿úØ©3Î |¡Aæ"š 8:``{Þ™!„˜ý´”òªJ)ÌÍÍaïÞ½™ ô媓õKJ)H)qàÀÌÎÌ@BÁw€²”]`Äî~Òóÿpæ‹ÿ-ÿf”¹ ¶<àŠ8ۆ貚ñ3ß066öE!ÄëÌñÝ»wcnn€Îbˆ¢®ëÂqœç¥ø4DbxáðÁ½Ø37 )8p%0Yaœ[“/x秦þÛg¾Qy=|n€€Ùàâܶ!º¬ýh1C©Tze¥RùSfž5ÎÖÙÙYìܹJ)´Z-ìØ±¯xÅ+pÕUWa}}wÞy'î¿ÿþ$µæù@‚4Ȥˆ÷ÁDØ·w\X¼p®”ñ5%)±ãßvò£k ñ®Ÿ¸mí#ÈCmÛ¦þ•—=Ð|ßÿ7¥RéƒJ©2 ÅÆøø8víÚ¥šÍ&®½öZüÒ/ý®¸âŠä»·Ür Þÿþ÷ãž{îã\¾Ã ¹Wö ÷B0¤öíÝ WDX]Z€ë H„¤`0Ãýý¯Œpµ)æ~úöÕÿà‚lÀ@\梓ˆÞ à™¹ hàU*ìØ±RJA€;vàï|gd0==W¾ò•p粡„”sîeƒÍ—Z\ †+ùý{135OF(y–ÎV\‡ð­gý—!IZBèÌ¡ ÎeùWf„¿FD¿cxž‡©©©Ä´WJáÕ¯~5:TxŸ;w&€¼ÔÅ'%/)Ð ÷¢˜; .•œs.Ç8p`Î< lÕá:RPàփͯÿê.¼¡ê«DdëfC¡Ë hB‹"¼;2)%ÆÇÇQ*•’´ëjµŠ[n¹¥ãí.\¸€V«•dÂ^ŠD¹#aq0{s¤›“LÓû nÉÃüü>œ}ú RhE7ìižøÕ¼ðúJIFHÀÐÍòKw„óä÷=BøÏwx¿2Z¢اˆãããp]¾ï'ÇK¥FGG;Þò«_ýj’›)Rd)'­gQ &×ÑïžÙÀwR¼•=À—&ƪطo„ \;×¼ç]ß¿ð•’z!9œ‹õåú¸<8Œÿëo'ßòÔRù}eNFƒ™1::š¤UKµµ5œ>}:qqØtÇwàë_ÿ:\׽ؽëØe³“ÄDÊÑlëRJÀ¡T'seêÆ0›#mQÁ›œÅ®Òâ}?õ‚'Þ8^Uß9YˆÁ€Ö÷?ôÒpïý‹±ïýÜC••™lyž‡jµ f΀†ˆÐjµð'ò'8x𠦦¦’s_úÒ—ð¡}(Yåt©QÈlkÒRþý+0/æn®y)Ð füå‡~äøéŸœ(«GcN6(ÈëÓ%¿Ü®Ìøó¿/ú™ÿwò™kîÔÔ<σR “““ðñÿ<»,oñ³ýB Z­êщu²NJ½ëº—$°l²A&Ì;i½Ìv[kÒ±8˜o8˜ïç7 @¸˜pWûž÷¾~̯ Š´Ÿl ÀXÍH^š@#$ó{ÿrì­_zÔóx9;ÌŒ‘‘H)fÖV^nÎWÝÝtÇÖÉ„%.¥02ÏIåǦçh ÓKÄ(„ƒqgõ;/9ñc£~ýc16,7¹kÀ¾¶KhŸùK÷ûWðË£-µƒLJ‰J¥r±Û9¤Î¦;¶E)(Uü÷…HÅ —˜ïZû1À< w׿múÄëGœúCP‰¸´¾_ÀåEmÏï^j@#HðÊŠ¿ñ™ñß—UQð+—ËB\–Ü+íhöC2Ûª´\6KfùÉl y’à9€%ª=õâɯqk÷odæ;h þ¥´4‹ÓgþàgFîSÞ+'*íÆÐó‰›e|d1ËÇ+ÛÂurNØ<È\Àw(¢>ÕN½xüÄ{ë÷C‘ƒTñ7ã=HÀœæ§ œ6t©MwÄcuï£Þß1òž¼È´Ø,•JÝÌ>w¹p8;7:ÑÉr Ë(ü²]L€•œvIéÀÃúS7ŽÞóúIwí4È…D϶ѯuiØIç¨] @£ø•Ãð›=ö¾Õ&íÌ[™†J¥Rû ˆ’Õ?—j8)í¨isünLP6>™¸,,%? )9YNæ;šëI)á¢~úÆò½o˜«ß@<ÚP°\`ÜÌÜàR B‰ù¿ßW~ùßöÿuÈŒçßÔ<Ë“Rê’Ž[æz›f\XúXÈœÔeQŽVqÓ}½‘ 8Ž„‡Æ³7¹ßxÍ4¯üÖÉC[—Q7Š[Ó«µ…Ül º˜-• ¼º,åû¾0ú[n—™ÌŒ" 13Â0„çy±K½; Êzú3á$JŠ-KßÖǼ\¾%.ýØ•! O5N¿'^;®ž@@e(&OV!„^óDc ›ro–ᙆ8Úź³óÇ¿\yíƒÏ¸/›(«D”ÉÌ(¢ .rw:u0ý÷ô‹"ËÒr¾&œËËêcÆÊ4âÒãÆéë£{þõ4VîÓ$T,,5À"D±Ø^_`‘HA´§u_ÒÆ@v Ÿ?/ä‡ï¨¼³â·›™á8N×lXD7¥4/9àÈlqéÙî #2Û8%Í•€t$¤jž{Aãž7LñòÝ4[— Œ pApAÁ`8N–çjFdÊxßdÛnHtn·ŽÖ¾Õcþè]Õ}âœ{«';·ßó¼žú—R Æ¥Q 1ãr§4—LäÄ¥ÌY–ž“Í+»6Ø(1<©u2©šç¯[»ç'§Âå¡h *˜!€&4D$¤Žfš ë4?öšNÆ&¸°½@k™×Vþì¾Ê/–ÜîíîW÷j6››å­Ë¼#6ãí—–>fƒÌÍYâ”uéJÕ<|íÞ·íPK÷ƒiˆf‚²6A„%¼)źåšoëe6Èlm ºXM“Çü¹K·<|Ú¹Íw:·ˆúÊ3)ܵZí¢YŸyQ',ï#3® ™…#6ÈJ±›C:Žj.\·zï¿Ý.ÞÐ4 'Þ à4p à@m­ …–%Û•hpmz‘ÊÅsoMàîªþ‚#;ëŠýègyªÕjˆ¢hÛÁ–‰[,—¤˜YŽ›%z™ÑÉ['k­_¾ç×v„‹÷4½ºÜ'`Ó@ã˜; 8@¬›™ŠlyÔ(i¾áfF/+ZÛ¹!ÀmÐÚgÜeþÚþ¡{Nºÿ²äto{· ¶Š·kkkÛÔµâÎæ³0Lš[²’c‰J£“y1'sÒ|3!%n­_»vïfƒ¥‡Á4 À÷@––3`P šl-Y˜ÑÕŒgƒ,Â&Afn¼ãn¿k’Àß|ËM­Ec½04è*%óP‡z½¾-+œl%'Y'RG¬½*É‘i¬o».òŠ¿qaq)%|Ñjîý¯;¢…oiŒ|(¸P1Ðs4$û „FRl“jÝ-„ FåæCY¡nh«ÝÅ ÓF}þ¡òkÊ}ä#šªØƒÒÚÚZfÁÊVvÈåöçóŠlªuÆGææÜ†“Åœ„€'ƒð¸ß]Sjá™X\.ÇhWÖµcÃ@ ˆâwNÜ´š‹Xi«”bQ ´‹ÊM¯ñÜNŽ–’Ã|ßI÷šÇÏ9·ºNïöohF„.//o‹¾–ÑÉ dãy†— )qé‚J2ˆ®¿÷ñ)ÿB ’vBb „ €r,6u}3† -2Ó’S*“”èlH¸švÞªä\´Æ{~Y–D à¯,ÿH3€×Ïôo”£+tyyyKйdÄeâo‡”LJudYå?§“ ß ÕõS÷žž(_hBÐ$\žÏSp0 Prc¹ØÌ&À‰î•V "ë]db˜›ò—u¢­Zqš0Uƒð§¼ðúÜöão6ÔˆøÁ]Ã[‘…™çbù„E£s•ó óRF 2_êºé{ÏŽ—š€¨@rÓW܆©ù‚Çã(Cƒ­¨ž68±>u`âè§H¢ ˆýmz/ ¬~ï=iûýhÔcgœ]=çëh6 žØlN–/I×Éò)×½, -Y:™£Aæ ÅÇgî_˜(_h”ÀÄáý˜¾j/¦v¿Ss¯‚Ç#ðÀ ÉØHÝ*’éꥼöe-0C1ÛÔ mÐì¥Xíä0Nœôn[®Ó¬ íóà°-..n¸¨K›uÙ…›%‹y-Ù®‹râˆÍ‰KðdÄÇgï[œ,Ÿk‚ÉaôÐ4Ưž£€2€é=·bjî‡á²²,Î4tdÀ–êd*n¾öT ã.Ù¡éÛ¯£EÀ—õ_.¶V7/$£³---¡^¯$–ÛD% ä¹µ–v¿qc”]dÈéd®T||ö¾Å©êÙXH0FM`ìè Ášõ•0¹çåÛù*H¦¸Z£Y›!µ8ó{F[`¥íu p³&äçÝÛ½é÷›oDœÏ¶²²‚•••¾Diár8ÑCñ·¬ËLÜÒËæùÛœÌ•Š¯ÝqÿÒdõ\,@#G0zt«OÆwâBkg“{_êäí 6N ›“¥ C\6´ìœ[êÖýÍаVì73Ÿ«³«böܪ8(ÅE|¡^¯cqqF£#wËG›;*þ2».±.­ØeÉ—ÖÚK²kv>¸4=ò\LPŒê*F¥ K¯y—Ç@‰Æçß ¯|ày]Œ2ŽØt~„µŸB®hî†BÛÍÑpjQî^mЉAö[ælµÜKKKI*x!à:ècEY¥BQi;dSg¬ G2›{pqfìÙ ˆåù F®iCW"ôì¹ (¹UŒÎ¿ެÄ'³|+;(#\í{_@ëÄÍR’À?|×»¡Ö¢¾U´­~ÒœV³ÙÄââ"VVV…!D ¸A@–8d­ì‹²g±”ÖÈA&ãø¾û.ÌN>»®ï¬Ê«¹6 2Ûd•Ã%7ÊcW¢²ûÍh%0£Œ~fkfÈñ0û|·¹í>¿]h{# xjAÔÚÜŽ‡PÝ­^¯caq+«+ÂBÞz€,YHb[•±Èô·?'.©püÀç§'Ÿ‹9™R(Í r´“b*J]«°íx5¼±›AÜLF=ë¢ÍC®Ëåç¬h7@Û›½¡€§—äü qîí|Ú‰ÚI^¯×±´´ˆ••eA D GIíËጸtòNØ4+ÖˈK‚”Œ£º03s2R ÞÞ*J׌iéWÄÉ(”J]« ØpÈEiïÏCŠ2ˆ£Œ)`ö ðDWÖÙ=eÏç ã:äyêÜ^]îwÎ9ûµ8·ëI'¶j&„ž…V«•åE,/-¢¶¾±û‚âÍZoiéd¶ óc—B«FÇŽ<´°sî™5@HÅp÷Tá]7™AVÞGÏZ²"R€¢4ÇŒ¼P€S½ÞžŸ…@3纠 gËÏXû‘¡ÒVs´ôŸ!8ZX“‹51¿ѹÕéÙÉ‚^d30$¤ ° Ñj¬c}uµÕE´ê« n“Œ’K({z+y”„”J6Èdz¯kŽ>¸°s÷Ók`!!Cì¼nR· Èg‡…1ÐDœLйóMã·ApÃlYšíž5Ѱ^\m Ðz7Š€Õ¦o†42ˆSÞ<Ãi+fƒ È„°%Á+ôà°† ¶„V}ªµ ×á ’ ³n GO’€ ÂÑ£/íÜ÷L R1°g¸nJ·"fOvWmÉÂ@,bÍÑÌfƒM@@îz;‰`ȈÌ"H® Û•F›!•¼>Rƒl2«ÐóeC‡Ò@j()¹ÉzKJÝŽ‚à Ƞ`:i‚%ˆI ”€¸úºGWvÍŸ¬¡)$„R窈®‹ë€ÌŽC*hý, ô¾yÜ„y·mRÞ šx9ÄâçAävð¨Ù0Ë/µê@ƒ£õºh´¨ªxðßdæ-]œhd-‹³20’ÊŠVnÙ*~¼••¡ì m]ŠMP¸ÕXET[ÆU×Ü¿:wød JHHDsU4_P ²|²Ž‚ÖÉÂZ@HYn–šîs¿!*ÌV®F'ëw3Ó2ÈÅ[&”ù¥'ÎÉjmLð7›Íá6Ž,å¿ +6Sˆ8R²V)UWG$$H1‚¹ j×O¤©ùÙ`˧!¶ê@Èí ³Á–N\½<óHÔnf‹M3ûQP¤Q—si˜:ZÏ+!•7ÂX™²ö-lÅŸí%£rY±… óÒB+ž›*þ„ã/>¹¶÷ØÙ:"!@ŠÑœ+cõãi)‹˜:éef #Íͺ¬­2Ñì[9¯ÈúÔäøZ~¿õ=ÃZ·ü¥¤ÁJ‘»QÉEÂ0ÜØ—íFu™i­X³îÒdÄzVÜÒ­âeÚÊ,±À57?½¾÷èÙ:‚d%¬\?Îq²<˃,4€ "êÌÅÚ2É ÊÇ F^Áõ\8JÏ % ïÆüÜö¢Ím0öBqᤠÒfËäAf¯RJ@f-‰ór±e/æb W³JÄYIÁ„kn>µ¾ÿš35 d>–n ­¬ê.da4jYåßZ^GËŽ€pæõ°—ž˜™£Ü^ÿóyÑVªSÏOBõ—öTðqëañ£`‡ A‹Kã'kÖ ÒÜÌ«7kãjNü(¢Ñ› ¸™ˆN3K})=›§ÍmD9‚ÏKŽÜ¸ÕEZ­Öà í¦“2‹{íj>™,Œ ØR÷†ïdÅ属Y?vf -@)4f},¾pÊ%PJ˃!²±ŸŒ€ Ð@3"Óæh½Å%N¸&8¸› ´||`£qξ€¸  Œ˜µmÚ V¯×º¾d2ÇÉò·I8™›‚+/.“ø¥H¡•Ÿ£ÇŸY>pôÙeB;ÿê3o…’Y™÷6R™[} UVDª‚ïv[}I Ç^ %'‘.:ó´A¸X_3º]~4€=“jÑ•ll&ï¿Õjõ'>sa¥äI½ÈÌ"ÚÄ¥«•ý² 2[ñæeÄå•Çž]:xìô"D*BsÚÃÒ Ç5'SíÁqó^$.#hÖXZ­¬ÐIùïÄÍ̇¨tÁØ‹’ÌŽ‚”»¡Ï¹E[-:í¾ÒXIµ|Ñf˜3c}}½wã((uÆŠ"Y‰âïÇÍÏŠK;ýZ Jàè±g.¾ê™óh @©ÍË7MNÊÉÒqÈr¡6q @«ÔÙщ“0áø‚öÙ¦sÏŠh-ß­bV¬È™QgG|uFñÆÛoŒ‚n+™2±KÄ\ '“Ò| ¬ËŠ»0¤+È ÈXâèUÏœ;pèÙ³#R!š3>Öo˜ÒOÐ,ðø›÷<È”ÅÉ‚&P_Í‚«›¿¬'È Åg0ò°3§|Í×F&fËu´Á~œA%‡W=ç6«§uåj¹yRÎ3Y²ˆÄdźÙ4ŸRâ„ýd–uéæ@vÅÁ3ç:}iqLúhܰì  C]'3 3a¦ÚŠÖËl7Fžƒ+è:® D¥£*/¡—EÕ—aÚ  u(D Q­ªÖÑ]ÁwƒMæ1š…%y]͈K34v9ÏŒŸÌY&vi»0\Ë…‘ãd¬$ÏŸ9wÅ‘SO#€âá„àÆÝå¹N‘,ÃÉB`}IûÍlWF^Ôp?^Ð’G kusn =Uf«6]¸ÀütôpHyŒkkkÖÓí¬PÎÛo‹KÑd帎".-gl2–¸òÀ³g®:tê$B"D*„šª ºiØ—mœ,¯“%ye°@&´ÀLQ»¸,‘UÇ -1×å‹ã•ìÛº m+­ÎB•sÇhô6¡£%·ŠuµZ­–©Ö©L“óö·Å.­°’m]ú±u™ˆËýgž»âàÓ§t™NŽÀUð óúJ dEa7Å¿¶œr2[\öª Û/\€j ç¥åªàŠÌs_´Æ@çã!Ä˯h)qK áÿD¬¯¯# ƒx5S6—Lˆ¬âßæ'ó²ÊÞOfJ¬;1Èï{îÜ•‡O“CcUˆi3¡‹¸´¹˜™" ªæzdÿ¢Qt £‰œ= î–w54kÓÐ0¶Ôáx{£ÉýSÑs£%µ4ŒüE]-ÂêÊŠN$5Y±&ŸŒ²ùd%; ÃﻌéÄ¢—•Ä¡ýÏ¿êÊ“§â¬F 2çæ# ÈŠ8X'?™"­ì×–Újjäã–ã—d¬€°°ðÐroÚ–ŠÒ­ð£uþ7(ÈÉj´¸{<úvmîOct2)­VëëëfYœÉ'+ˆ]=ÌxýËEr'åḋœ½põU§Nƒ…^VCc#7eAf¨d™ 8­&°¾4é"“|cßÎØ>+ S¯FàÂàO·Û·Òê,tq¸n¼ìŠæÿhnhvÚ5A¯,ªÕÖÐj6ô’¸‚²Q™GÝø¹ÐRdNj]:pvéêc'ÏèL{ÐXò¦# ²ÛQños_ Í)«/µ%maÚ+™ò S¹{æGwP êé w%fÑ}î‡Î݆±fÀ,‰è÷8®Ù<VÒbZÒ3²¤9˜?pnõêã'Ï"‚â4RÝx(gu2ó^èõ'½j©Uמþ¼.–·,ó ÿ¦AFzÁqØL°é^Hø ¡,ÀضXç`’wó|pbvT=© ¦ ™ÍRúAˆ°¾ºA*)n——^6P^Ê…•©Å-‡óÏ­{á“çA 0R†ºéPuûi}¬Uj ÚÓªî!¥"‘9Ðtv¬°¡Á–|æY(.Xž¨Û¯oid`ãÑýΡ¹àÔû[_o0›—VªŒ•~)×!¨¯-‘*Râ+³ô²$¬dÌdûŸ¯»ù© & ZFxã>`ÄM5R¨ø“Þ"[“ @}¥83¶H'ëT9vsÁaÍMmOvКCȳHÖÓÏ/]t÷Fa÷ºž•À û‚;úd÷ )™Zþv¡— ¸… ¾ßQ¨x¤ýc&PnüdNV2«T(±ÿÈùÆ5/~j‰kñH ›wƒG;€,—‚Îéo®j€5V-€Q1Àòá¨zü» \Ø¢)N‚2"T1¸A°aÚ(Ð6®3Ÿ"x?r]ãó^èåO˯ /,´b)þ%¨øMDõ%”Ül¶âoq2×âdóG.4¯¹õ©,&¨ªÚ w@8)Ȭ¤{†öMë@sh.iN–ˆÈ‚dÅN:™=Ryü»:ëöؼˆ4%BAïòîÃâ«Û®£éÆä]3ßzô•W6>WouæÈme LVlÁC"òeÕ«>Á¥&Tc%¢RäIì;|¡yÍmO®±Fª¸X½ijÜÑ©>1°8¢ÐZš \­5BJ±H·n ë–‚½YiKÓæfDú³ tÙx›£mÞ—„1йâ¥GZÿ_§¸gWN–ËŒí”ß?R"ø27ࢅ’'Úýd¡Äžƒ ­k_òäQ<ÛQÙÁÒS*ÔÕ€`h-hÎÕZ‚šöMËRuà^y÷E'q9L™¸fs­]³ ë1'ÆfŸ >Î6¬’\Ôñš€JßwuóK»ÆÔ3«M±Ç±KZ 3ûò"³íÁ]9™„ÅŠO(¹ ÔZ„,ÁsʱNÆP¡Äž‹­ã·?¹J`-j_âì‘1´B œE2뉉u²Lpí {Í3–:lØÖe~Ô5miÚ>!f Šb­Î¢ð~sAú¦í[@œ§îÁ=Á©·¼¸ö×,ñ™_<Vi>9qiJTK@Õ*% WùñZKàÖ2¡ÀÊÁîù¥ÖñÛ¿»"¤b03BOàìá14ÊŽ~˜eÜ5£—Ù³óû;¬_lCS×i&H‡šZëíŽÇ¨©óÝôÒü|NZ·_ïľ[<¬Å)½’ýÿš½•7Ý´þé™jtÎö©Ù™±¶¸´³b;‰Ëjæ´+Ã8n%a å%ìØu®uÝmß]B)DÌ]ÂsGÆÐq ¢´â–Êô«ø['3#Ç 4VŠoتÙÇÝ¿ÊÅ7îú¹'m%Gë\7ƒ{p.|úeGš_¬Y!^»^¬ 2;¿ß€¬’YÕ×ïe/­…á»é÷9”˜Û»Ú¼þ¶'EÈ®GsÄÉäø÷YÈY`Ù•}:­X²ïÛmj7: Õœ;#>¡"íêHDV ZÓIŒnD´fh+K"äÏqÁà½íÖõÿ\õP#"½ÎÒNóÅ K‚â~ .#.} ìSRnݬ»t%À‘ÀÞùzãÆ—-.8Œ-pîÈà ³ë“…è²­´. ‘ÐVf^d±ZÓÕ" 9h‚»r¦~ZÖ7g¦ÕÙßå¯jQéöcÍ?~ÝâGVš1¤i­XѾˆ¤l‰K›‹i€’jضu©anO£yãí ©‚˜“-žDsÔí²|êuȺY—Ý€54‘öç5VÚAf,ЖåO#0¬"­ƒÖ-øÕm·EG3?ÔâcÑY#¢ÒÛo¯}T¶Î/ž}îÖV– Â$±.e^o*^û–âïÛV¦2i,"Ìíj6o¾åÂ9IPÀÂâ‘i´ @f÷,“êÃAÖ­ºÐÝØÅb±¾¤±m§Iƒ,2Ë  :R Ü´çüDÛ·€8m¸m˜nûGÒwÞñ=øãF+Dm} K ç°¼xõµ%pX‡'e—Pñª>r²|ª#¥æv6›7ß´p^ f ¥Àê‘i8YášKtVú{­·Ü´†ÓmèYƒÌR淢ذ~›PƒÄÚ‹"bM^÷F¯Ædõ³ôÿ’žPú…WÒ§ç&pžW°Fˆ°¹Žúêê+gÖ ¢uxÔBÙcT}B¥$PñEZ¦ÀvÆ*ÂΙfëÆëcq tꇦ{›ÒÉú®Q†-âdñÍêËÚQ´Æ•DÌÍ‚ìy‰3XB»Ÿ³ßù˜†½f ·lO_9s$„·ožý£÷9‘#-«ÒJNÉu ¹U?nœ‹pÔ:| à;:5Ès\I`Ø1Ùj½èú ç=7Rh ÖáD¥$v™¶¦È,qÙKéß6ÅfycE… © Pa{t€”ðÜ„£uÒ"{‰Í\[ýœöÆjnO]c4PþÉ—â³?ûú‹V˜f\«r´Œ”‘PvB¸\‹P³Põ³ˆçÀ­%´5ÌŽ¯·n>¾pÁ•ŠPB@™û’‚ ·~A64M'@‚æd­ZÅãÜ\óÐìè§s-Ìï}Æ€ç34l  úà˜¯¥SÀÿåÀGŽïÁiA©U9ƒmÄÏ'T|ãõ\Š TÍÚ*¦+ ÁMÇ=G1” ÃS@d@;ÀŠ@fs²^ÖeȆEdéd]AÖ!:`ÈÅ©Ülô㤵]~´AÑ~mwv Ͻç‡èÃ&^9RŠAV2þ²ÔO–ÔÃp¬s³¼äEb±T"… ,Ρiˆ‰R×ÿ6ëý[–½”ÿ¡i«²¾'2v‘\Ì@c蔩íãÛ=æ‹<Þ“¶?Mˆ u‚,Oi¡ô=Gñ•7½ˆ>/¤æb£†›y9gll]F°cš‚Ûo¢%σBÈ„€h rÊÏÔ5éGéÏs±~Å妧¤€(ö…Õ–ºèdæZ¡E¦½FÀ&Jx8§Ÿ â?ÛP¦íVgØæý1¶v–ålM=C!‚ü7·à#/ÜS®4þ2JJ$)ØBÿÑwL!xÉÍXò}Dˆ˜@B tprÒwQü3`ëC'ëU£lØDB;ck:ƒ'¹–ô5ÍÕâë€'PÂãPIæÎ ºÙ%ÁѲ¾±N ‹5¤òîKPÁ¸wŠÖ<©A–YA.uTefáK_LK¥ "(ÖÚ²p¢+'ˈJôædªúl¥uI¤u±õÅö”Ÿö‹cѺØYdj‹óëpÐ@»³vËœ0ÀÖ퀣äºìtq¶ª>!äÎ1<ö¦âÆ«Lq–d0;…ð¥/¢…RÙp2"”NÀéS\vËÄè¶°w+§Èª± 4–u>vú´^–÷™åggwæÒŠÞ‹¾¹©žmU5!Êí§"4/:)æf”Ó–XW×Gg~ ßx͵øxÙ›|4VÀô$—¼˜Î—«uI¼cpfºƒ¬M\rVñ/JõÉßÞÊT˜‹W•×µ¬•ÈèeÍõî\O@¡Œ‡2þËì(·çuÃ4ìG]SÁçìqý)YQØZ? â-‚88…;¿÷*ü…ë€Ã˜Cð²ÑéJ-D¬ïé‡3[ꨓ`ô·Ee§j>ݦf Ñ£TTö£%ß:3£0 žk£‡‡Q·ÀphõP4Ða¥rç9˜},oÝ9` À!'šRB ¼“øJŒ<ü,n{Ù ôd¥!—DðÎ@ÎtYQQâ^î‹"ãahÃoFJh‘×\ÕOH1 %z~/Κ­/¢§xUFñßᡯ(âÅ[☆´NŽ=^—¾§Ó-Áˆ@1ÐJ¸'S€ÐÅΫvÑ‚wxwî߉ç\shò,’ðçgàÌ”qÙË«úY‘â´ßS#gÄ6×´sµ§ÂŸûnè5¤Jõþž@„ üU±Ù‰†Àa>¯“P,.mÐ D¬úSb˜'péé'H誄€fò™á …EWà$šJÀ'¥ùyÈ#Nf†¦'ëGñσlhËmã0RØÔ ›š« 2jEaïïi±ùTñ ²ý˜1Cár›Z^LöºV?ÿVsµ(î‚J`§ÝtàH@ 2‚ÇÚ[“ €ƒ‘/3; ‹ª(Ö»Tˆ$‰p˜à2?š„¡ ¦w#ø$fðÙ˜›õš“‹ê°í‘ÊYȹ:‰ÍNFBî8+(0Æöþfv¿§gfl¿ I€Qö^Jiq1° 2SV€â—¡‚ËÜqjU»AŒ…ºYbËØ_Izíó‘Õm¡a6õ wØàTÚƒ½÷71ƒ,ï‰ëé b=L±ÞBíB *Å¢PYfÉVËãÝo­¥Õ}†ù› À.¼£xª‡ÈÜVÅ@ë7@ÞÍÂìw©}š´2¶ç70µçÓŸã> QœæÃH«^«ì„ú %aDJƒ*Ršsäu2¦ö“'n¼Î2¨`ˆÇ¦Š/a¿_àÎè4OÛFy  «çEÎÛbЍNü&fþw¨F † ±î_§8.­®t¼ þDñqNË|¶édÏ-rÛ‰[O·¡¹A F[àöàpb7Qç+·´ç(´N. ûs‘%Ù­SEd2l‡Þoõ·±ö( Ê€ðu³˜R±g',Úyý™ÿ˜»)ê·Ü²¡6à2Öj#¢[/–À¼ãx´È. 74кqž"¯°S/J×AIúeŒàýë”ä°@äkÐÁXZâRõïÂØÊÌØ¤'1—4k‡ "ˉk”ÿ­¢À$>„ø³8h^Dd@Æ@Ø md:ÔäлQÅû“_€5iQPu@I> \€ã-@H–!À½A¶élŒ¸}8ÌÚb  ª¨G¶ÀÏÖ´cö«Ø_ŽûvÑòÍzQ¿VgÞûO¹ãýtÌp2IïBÈ$µ)üˆõ®uK4:1è<@91·“‹T3~¯dÜçP炸»&*ÀJs-jßW co¸ìQvð]À›á¡Q 2/jÈ)OÃL|ìÝ!CÐ/b¿ÓdÈ-ê%³TEs0ÂZ|Nh°±-r%tUó8ÿžEA3sª%[ 0†ˆñ¯q[¬AÌ­,Ðn‡Xì<ް„ýø ŒàÉ Û8À¬aD¸á»dh åõ±¢¼•¢Î%arz'Fðö•"¶"am”ÓËXƒ@éwÍOf¬Ìn)6–•Ëöç|â{\ `åG˜QÇ~¼S¸{h €yhÏÊ2ðÕG{¿ Üù(£Óc”lú»ï~~PŽÖÉ8°37Û3k*ádE«<³Åb q ¸(\En<þ…V¥Å¥ú"«[Û)}F€}x vàos \TJhp `}øâ=À'ïîz”qòÂp›¾ÑYäɳT—côs¨â÷ ¬|Ú¢pRÂÍ,7†Y§ð’ÿ,´2/QÐldÔ-ìÃ[±Ÿ.Y‹ÎópÿIàÏ¿ÁxüÌÖ5#@ËûÏl€$ô¡—ž8ôK¨  “…yq‰þ@f·(¿¹“¯&öá§± а0“¹3±ÈÀƒ§W/>|òkŒg·§ ›áhI`üY"]x¢W91<8xC²<8´€!ûð®™Ý—eìDZŸCXXฌXÐX¾xðG_>w£ß'?“†TÏëf¦ ‚Ðä·é?ÂÇ÷dÀacëdyo¿â؈†ré“ ñ$æñLᎮ«˜Œ,‰gteøìߟ¹xà)Æ·ž¹¸]!þåÙð@ß/èªé®ý”[c.ý6<ü«$‰¢_°~KźÙóÿ€ƒx3Fñ¢xŒÍòDȬ9€Ó€GÏŸ9¡u®a+ô݈{]Ã\©^ÄgÀa ¿ ‚ƒw€!hqÖ¢,*Œ—÷öwâdÏ'ŠTð ÆÛQÆF ê:Kpòàs÷3cÀßø.ãÛg€ k»ñÅ4,љ敵Oÿ7uëнé·Ñâ¹ ÈTÁÖ–[†.Öåó€´<¨cÿvãáéÜÈÇÏw=‡»?úÞqÆÃ½g–±ðµÇ5§ 3 üå×iâ‘m“‡Ôia÷ÕèT€Ö¦®˜) JI‘Äe§ôÝ"0,㸀ÿö¶_ÞÄP¹sùë/WÒÏ®}ì!þÝ÷܉÷\Iž:׋6’9³eÔKGë 4 ÜõðhΙ73\1\=§»ûÐI`­ìŸÖ¥Ú…ÔÇP'¶ "‰ÛÉ« Õ¹†yÿ9ü¦×Ä3v«qGvÒÇ.‰áÝ pÿ³ôå÷ÞíþÆŸ?л廒™“^›ýx"ícÉ{£³å£µy ùÀË“qÇ#í§<¸n¿~õ=Ojö>ZöM˜M‚J.0Y탘¨)Aø.IÏ$0ŒÎ+ú©[ÇÔ¯—}Ì'å¤.Ù¤—MPì<]X¡Óïÿ†ø¼O~¢ÑBèy(S¢@Dy5„(Ö”W*TE˜É»È"û|(#ÚÐ:³ßØÚ¹í×_{¼çoå#عŽDD £eˆªO²ìA–]’¾ÃŽƒBF}w™çt^½ùG©×MŽ`~XØpæ÷¢SìôY\Åâyˆ>ÿáÄ_=±@'I¢êˆ$n’×L“-\‘™Åç¢øº(`²ÏÌ4èÌ9VJ) „›Ö|7´ðÇ_~ö8§Œf‚ˆ !„$"D’˜fvAJW)à,Ápˆà„ ܈Ð<2Î{ñõ†·\ï)c4±L/GŠ3˜Î,aéSâÄïÝK÷E< $´*‘áV³Ag@” +!µ•Œes_ô±€ˆ‡3‡J©È€0Š¢N¶}WÚ8ÐHW(~ÑÿÊxðTñw)¥t]×!"—™=föxÌìÇï´Êë2à‚Ùe†K‡.ƒ7~gÑÒµ†Â£3ØûÖkù¶×]NaV/¸¤û‰bý xüÎÿñøÿ#ßÄ}§×pÚòv rNò“[dkÛÜÍŽ¯Ø\ÍZˆ(-§´âÏ-"jÅïMs,Š¢ ‚ÈpÂ~º¼q •€¯>¼ô½…7 Â÷}OJYbæªRªÊÌf.(Ç@ó ÐâwÇlÌl¢‚â › 3ˆu™ÑpÄCõûçqèÝ/­/Þ…}‹‹]õ= Û@&ÂK@­ŽÖß<‰ï|ä!|óïžâ“ëêHÿL™ù)š³‚sm $¢<‹œC:u4å`¡0]ˆhQ@ˆjBˆu!Ä:3ךÍfK)eÀÜ•6àxæãu!9"ÏóÇqÊQM2ó”RjB)5  Í™d $fcWêjÜ–jkŸÖZPùm|篾“×íÀÌKöОŸ9Žã7ìÀ.ò!2ÿíížpc`½†æWŸÁ©O?†oå$Ÿz| 0 ×@¹Ï»æý‘/,¶8;qÅ|1—2¢4€ævhk–,!.ø¾¿Öh4̼i7Jg Ià¿Ý[2 )¥dæ231ó 3Ï‹%Ü+*d†¢éëÐHø³8÷ÀY>óáûñÍvbö–Ý´ûûöcÿM;±sncÂKê«e‡y£dZœ{bBÐ@øí X¸ó<óÏâÙ;NñÓ/a&žF"ýµ^cÑÏù̬å~Ãa`RK1ÐJÌÌJ©†”²!„±Þ¶)ê ´øÁëG~ï Œõf×)Ð@`––HL86²ü5'bð‰38sâ ?óŸîÅ=“%TŒaì‡Ò«¦0}hóc›ðá–QJ„VQ&W¾è–#ç×±~¾ŽúÉU¬<±„¥ÏáÙ¯ŸæçžXÂR3JžgåN\’d÷N1³ I,˜Y‘™CAD€ MCùñÎå&[À+®^q0Z"üÚ§²¥Tä8N“ˆÖ˜y1nT‹ˆ*ÌœèfDÔUlZïçßœ¿Æè|¼Ø@s±çî;ËÏÆçå¨Ò‡wlš¦§Ë(ÏUQÝYEeÒ‡¿£ŠJAzð[B) ¨×„ß]ÁêJ­35¬?·Îë,`q©‰f+Ê8\LŸü-E7âNjĨmÑÚ¹3!DÔP°FDKVˆ¨®” c¿Ü¦‰øc=+ôºÝÿñMà7>É8¹¬ÖA¡"§T*yDTQJ*¥F™yÄÐ,ØX .Ú 4A2Çùb ¿Å±—)ÊùèÄ]íÉ1Ç‹þÃ&îq¬È0ÈyÇnÞ °ÝAÌZÐAƒˆêD´ND«Bˆ5"ZmµZÍ0 ûòföcuöPÒe¼Öðš0¾ú Ïó!„maf6踦 6íæÈ//n%bÎGD†浤çp;xbÀÚ½°ß»õv3Ô/Ç4@ìóÝ\y­3ïîÈ$ZÅ–¦áZF³Ý-h‹Ó¸6š1gk2s3‚ Š¢(צÎJ>¨¾øe`ÿ4`ÅÌÜl6…±â(³–™oÍp1®ä²`3"6IžŒÁ–Ñ÷,ÑÛ €&Jª#œ¬PéÈ"’fÏÁ‹¿kï³õ½n¢Ì€¬˜’ýœØ+ò¥UHRwF¶—øð Cf#MJ)eÿöPh0å5î–5I<- CÓQ²H˜ˆ)„HÄež‹åÄ©Ì]—³fŸP >ê²O 2à‹AC*.!jµÀ˜ˆÌg¶× PEéTyPå³v¨I×Xb6b1 A¡»ETì‹´Q©TžâX[š€5¸•ÄÀ÷^ üׯå¦ïÌœLPEöD3ÆÞ7Ö0ŸÍ±˜’sùãÖ}D^¿‹Ïçfƒ,ájÖqÄýèÊÕôÿFƒÎp3‹«u3#¯SÅç•õ½äX„É1f¶?s &;ènï#vg´Í×°AUDƒ­ ¼õûPÞùñB·G¾áÉ~ÜÙNŠ|FŒ!¡šÃd&ëXæ¼”29o€cÑþ}Š’Ü“™¡”‚J©® 3 B$›ÍÕâï'àKžÙ&ÛÀ̃Ó>f‰cšÌõöùóÐíØ–ÓÿfÄ£0~|#%tEXtdate:create2016-10-21T19:31:55+02:00ËÐ0¶%tEXtdate:modify2016-10-21T19:31:55+02:00ºˆ IEND®B`‚Slic3r-version_1.39.1/resources/icons/Slic3r_128px.png000066400000000000000000000313301324354444700224570ustar00rootroot00000000000000‰PNG  IHDR€€Ã>aËgAMA± üa cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFbKGDÿÿÿ ½§“ pHYs  šœ1ÚIDATxÚå½y°%Ù]ßùù“Ë]ßVïÕÒµ¾ª^Ô­Þ»º¬Å0hI-d ²A…¡ÃŒÇ3ÂÈ–a{<˜í@ÔÑ2aÐ’Ú‹ZR7’¡Þª»º»TÛÛßÝ2óœù#óäÍ»½wßVUíùEä»÷åÝNžïoÿýÎI±®ò-oûµ}|ê[õ²Ðl6Y__'Š"Êå2¥R ­5Zk¢(Âó<J)<Ï#Žc’$!´ÖˆJ)D„(Šð}c qcŒÁó© ×ì€ ^ àû~µV«},‚÷Çqì×j5æççñ}Ÿv»M»Ý&Š¢kVõoDq–º>urž}Ó:¡@9°hÍô¿ýÔäþùOýûvŒ‡g….ì|¸ÆÀC·ìß¿ÿóžç½-Iêõ:'Nœ@Dèt:>|˜W¾ò•ÜtÓM(¥^RL ž-†r¨¹áúyf÷M¡0Ê>ÌÖ ¿ÿXõŸþ³÷ýÖ⺞Á·]Àmv¶™e¼f£k-¥Ré{•R¿2ÆP.—9~ü8ZkÞùÎwò†7¼ÙÙYVWWyðÁy衇œÉ¸Ú—0”DRô”%Åàµâä‰ã¼øüšë+¾B+¨— _y¶ôÆw?´ïÀûÞ¸ø®c3ñ$9è ;H1_Ëà~­õˆÈ!c Zk<ˆïû4›MÞüæ7óÎw¾“¹¹9D„‰‰ ÞúÖ·277wMúB ´VÙ‘=W ´Oƒ§ %_qòÄ1¦êeBm¨P `ÿ„á¯/÷}ólð=hÛ:@ÌH×–Ð餈È[Eøm ´Ö¢”bzzšjµJÇÌÎÎrÿý÷|¼V«Q«Õ®©hÀI¼’I½H²Ç”ÀÓ†Àó8uò8^xl„VB;QüÌ«Våõw4~“H„®úß];  aqÖåMZËoƒ Ó L¥»^¯£µ¦Õj1??Ï¡C‡¾baaË—/ç&âj‘ž]ÀÝ£RØNúø:=< ¾6•2UÿÏ!N„ûïXýõÅò{H$Áî^UñÚ`ËϨþš¿x&øZ)ßZK­V£\.AþöQ*þ³Ÿý,/^¤T*]ÕKqPßïL€ÝË€<ð=rsPŸ™ÂÄyå'~åm÷­¾—DÌ /:Š#éÚðJ–OüyùÖO|sâ¡J(UHÁ÷É7¿ùÍž¯øÔ§>Ń>˜wý\ ê¿xz !ô ôSO¿@%„jfïÝÿž'¼î–Ö‡þî=«ïÅH»3‡o蘯zKXhùêÁÜ[~cß›‘zY »×7==M†ø¾ÏÔÔTâÅqÌxÝë^G­Vãßø?üpÎ4WƒŠj_ѵ÷EÇÏS©”;‰½ìÑO   ¬øÜ2ù쇿ïð·~Z ÅÙýqÈ ¶åfÃÏ\]Ð%ðÚ_žû½¯=¼¥¦Î›µ–J¥Âää$ÆjµÕjµ'Æã˜(ŠÒ‹! ëú¹_éEð½‚Ï÷¡ä§ÏK„:=x€ø¼lâ̇^yàñŸlÛsa›1Ðe³\=@eyïƒS¿øÈsþ[êa×sWJQ«Õòÿ‡9užç]5iï¿ ¿(õž³ñ:•ôRzÉ1Av.ðÄã†Úsûž¹Çè\  ¶ðÞ«È¡åÃZyío<\ý×¥îX­µT«U´Ö¹Ä›3¯%êÀækðU*Ùa&ù%?¿\x }=A”Ï|õìÇïþæO ¶Õþ8ä Dc‡ˆWƒ{é²®ü»OOþÇrN^~JQ.—¯Â°¶zÙc_x§¤+õ¹ÊwRïC9è_Ük‚hƒÁ¹ÿ÷ôô7Þ%bøî°lªSý[ñ®8¤óæ[~éÓõŸ?»¨oš(uU¿µ–r¹œ7mÏ_K4~ÁáËcú,¤ ýð~êÝ—ósBàhŸƒþ pïÔcïPb–±¢Ì÷o6 :;ì>sE åæÐšÏ|­tßo~¹úsµ°7c'"C¥ÿšÊìåcÍ’;øZ2gOw2©¯]ɯ8õï ¡JûÌé?yºþWïðb³L,š ÿ©­xýš^m1])HÁWØfCé÷}bòWcCÙ/øvÖZ|ßÇ÷ý‰ãø só‹€!ŸtU~ÚÏ$ß1@9“zçø)í3£Îîîòc?îu’eŒxx€AÐØ13~BWú·\¼²& ´ö߯½ý[çüûŠªßQ¥RÉ\éZ¨õ²ùEð¯+õ¹ÊÏ’:%ʾPÒ÷ˆç3gÏýñÝòèÛýv²Š•ŸT–c ‹°IPtq,–†¯ '°ëÅjì¥Kºúk_¨¾¯ìŽM)E†ƒ_ ’¯ùë÷ ®Ýï¢Úï/âô8{è•‚³Wò%õøƒüÙ仟¹Ë<úc~œ¬ãI‰´õËa²G(eš`p8œm ÛÐW"¾JXûá/—ßòÌ%ï&_÷ŽÑeðŠ¡_ÿëNç uƒ‹(tt!ÎïWùÎÎW‹ª?H%? @ySñå?»³õØOøQÒ©`ÑX„H¾‚B1Üò¹ º]AÐ þØL°— Ð]_Vúw©þ“aÒô{†Q»}e×Xû¬zl¾êÆù¾ôô+a÷Hí¿äæ@ù>“ÑÅ/ßÝxäǃ$j`¤ŠA‘ tH+ü‚BgMŸqÆÒ«ˆèÚ}/{n¸Æ4@1†ßÚ©¼ö‰óþ½7záåÈ/ËÚ¿ÚíöI÷ö«}ü@AÇï-èT¨…éQ {Á×ÏTçâWî^ÿú%µ@&0hT~X‡Æ#!íõé YËGqXÎî;ð¶hû‹_´·s¨°U¥>øpõÝ£Àwëû7£F£±GÃq=-[Yœ¯ q~fïËAWúÝó’_ðö=ZgáÛw®~ý=%µA&±x€Áà°( pUÿˆsà’=Ř߿­Xy/ WDk¿øðÞoŸ÷^zÃí»[‹¿‘ƒç´@³ÙÜS-Ðç«ÌæT¾“þ’“|'ýa·¤ëB½’Êó™Š/ÿÍ=+üBÙtš SXB,ð2@§9R„CRР½E`ÇÎã/‚¿ey·£€Áe ¿÷hùï;:Ÿ­µ«£WDX[[Ë—cïvD°Qa§ê9É®ø]o¿’©||àƒ(Ÿ¹|îöÎ×>&Ȇ&*/•ónúÆÒ!Æ’Ú}‹`QĘ,7þŸÒŽT¿£½4fyI—¾üTð†ò¬¶•b1†ÕÕÕ= ¯ŸiWÐ)÷9}ÎÖ»  ˆÇ¤¿Ò¼-üú—Bé2¥Œ%Äb²þ~›iИÌÞ›|§ø%‹Ü0ûÁß6í¦”~ÏògO÷½°¤OU‚ÑãÜJŸÛïgee…ÉÉI`終ô®ôÆùÅŠž³ù•ÂQ$¯îDyÌ–š·M~ýÉ Ó*á³`sˆ,1’i1 :«èI!ÜSÙc¶ ƒ ŸmOÀ^&‚à/ýpb6.gnµÜ+"´Z-D„z½>4{¸¥A2<ÉãzöœÚw¡^5,0A˜%x²r/Ɐ´Ø¹}ß×ÎûÒÖXµŸ©™IÚ—Û$6ÆH Äøikw ™#hQä&ËtgÏfºa€w´[ 0(ý 5D}åLðš`“_ÙŽS'’î"f­ebbb[LÐ~±ª§ ›®¯Çã/H~˜1€ˆÇD¸–Ü6ûè%_·!&dj~™ßOüí*É Ò²-€4êï: 0¨ ~Ç636‡[†‚¾¥p/L;§ákÓ´µO_ôŽ?· o ôÞ¤o&X^^Ƴ%FÊÁÒ«ï*zż¾‹ó«…¿tÛºD¦^¶Ÿ0wènf¾ Ÿ ÝÅ~æý»°ÎÙ~ NÙÛÌ×/JþŽÃ¡½ËxðÕ³Á]ËMUÝË܈Ðn·Y\\¤ÝneN\÷®d œ=-\CŠ:å¾0¯˜àqßTi%¾ýÀ×C¿a°(ê7L1ñò)°©œ— Ì8Íôu?‚¶.‹§ ‡*©PIÏ#™S(]ÖVi§ 0úGxø‰ào_‰>MW0Z^^fmm-?·Ñ€]Û¶R»=]<o¿ßásRïÀ¯ÍäÖ.~Ó`ªÇjÔn¬a³^$…»d`꺠2ý*Ä&€ÎT½süTt ôœÁ–g{·L@ïÿ Ûnˆÿõçƒï÷½ÍÕÿn„tðµµ5–––ò´q‘6røzì½×›ÝË‚ä‡~ê•W㻎üååJi5ß+Ç«Ôo­cûj3®tS¶0uâVoå KAâé9#°m GÑÞ˜e͹e}àüŠ:áÉ•a€|f²ŒáÒÒËËËÄqŒAå<÷ö‹U½²¿¹ÚW¢©‡øŽc\*—×",–Ê|•ÚmulÁLýu| e]¢~ìgðT±¶|`‰aÚ`÷ ÚÉ\<¯à…%}ÝZKÕljðv»åËI~«Õbqq‘•ÕUâ8B)AkÉ9”*´qy½›•>o¿¨öšrÐ6·ÎýR©ÜHÁW(ß\ÏÕ>ÝŒcSgNÏ@©v’Òþ7#47‘ ðÚð¹ß¶Øm@Ü·>»àè$¨qF³W˹•J££f³ÁÒÒ"++K´[-‹¯…À“|™V±–_)¼ý°Wí+4õJ#¾ëe_9_ŸXJkÔáá*åÛ§Ò_íß’1ÝÔ"e‚ò¡ǯ܌²­>O ØãÛÕ ½Nà®èƒ=KŸ¹¤çoŒ{²³WO«¹ Q§Íúê2+Ë 4ÖW0Q _[JP„J(}¹ýno˜I~µÜJn¿õ«—ª“+iõÞ;Zÿs:¯Ìƒ4z“—w‚P´®οåM 6éqUá:¨ž«»ª #ιp…‹kjNÆÈM¸mÒ¶Ç;À|GO ž'( IÔ¤ÝX¦½¾HÒ^A™&Š)û–j(TE9P„~ª)M)ŒìËo}l±2¹šnÓ¢TQ·NçÁzñr´Ûe€Øö2PÕëñfïGÑì )øƒ&`ÛbŠdGÿ˜…‹kzÿ¸Þ$IH’dwÖõKa&ú×èâüÐsv=Ƴ1*n"A<Òíùh«ñQHâS­·Ìmw=¶XŸ\ìÐAa–1É/\{7#$E]àcºeI@í'rùãHt!õXIÄÞ¶šâ"‘q°K¥î 0Њdj+ìèz´»©ê_[\šíÒºÕ’P+ µ’P+AÙKUϬ£¢âæ Ìm§¿²TŸ[L%?>R£s÷Lúí†ì|±RßiClºÿ"Ò7KxÙÿ÷д{@WLãIùžön¬þl$¬´¤2–˜Ñn0@q ÅuzÅ8òþ=ߥt»)Þ4½+Ù¢ A‰& ¬½ã•ϬÖ÷¯Ç„h®Lóåi²¿ §¼“öN{ü¢pßcæþÊ?„¶qÏö£2£±Øèüí¶c‘ÄH8ö‡²ï¶ÃA)¬/ì“|¿ öKA·e»–yúõRö¼$y]?õöÕJlïù¾§V÷[î#´÷—X½këÖ`0¼8ëŽ:Ðé@,äu?÷Z1*°Œ?K2û­B€f€!ŒøCÚˆ-¹õn½ÿ–AéuøtŸÍ÷ûb|×ÅS-0A5*™V=Ð(ʥĞþ¾§VöÍÀo*±tÏ$Ö¤ü"%¼µ‘íÞíëeèe1Íýxû›ôµƒlgÞÇúÔnE=ÿ‹°-¾Ùlnk B_3‡’Þ-¤v«=Y¾B3‡J¾g¹ãoŸY8°Ú!Zû|–n«a _1ÎïßÙþNéeŠ‘ ÜLx˜dæ ¨!É¡Þy¾j©àQ?ܳzEyÖÔKfÝlA¸ª^Çci¢öõ0É/dôœÔçGØUû%<Jabï~ÕÓKsÇ›DXZ3 wO`üTòû%¾|›‘…æú ÚPýô2:ÓoEðòº¿ëÚØÞ_µLàðX°–-/å1ÆliHQò‹Ë³½! 6òZ~©þb=ß­ß³Üý=O_ž;º¸F„¡¹/`áôd ~2(õÃÔ~¢ µh´ôêçIõ4Iù”F@¹kI Ø:l¶AAúfªæòv’{®Ëg£_É7_ìõ yýbQ§'½[êv\§ÁSÂíw?{qæÀòm,­)Ÿå»z%ß]ßPðm x» ­ÆpðG5qÏY]!šúA$ 7ûŠ7„Œóƒ‚‚ÃSÉ…dÌTpþÁ,pý~Ã~}d’§ãûƒj¿žyüi–¯Ûãç)!ð¬¹ãŽgÎ<|y‘ í)Ÿ•ÓÓ˜P!…:ÅH©ÏÀïDÐXN3~Ã<þ~dž0„…¸þw@¼Þ+ß]Éw´[y€ÞK˜©š·›Þo4CµÀ(‡¯§m»ož+êTKia§Gí‹ •pç­Ï<ðºË—icèL‡4NÏaCƒ?*ÑS?J`}"Ó+ýÙü¡Ý}âÊß" O vÏ7FØŒ‚µËŽL'Ï{jëàêù=Z ÏáëéßËbý<Î/n¼ööð¹ÂŽSûZ„ÛnzîìÜþÅ…tUîD@çîƒØP§è±±·Ÿƒo ±’2‘ÑÀ55AG߀lèG]ÃÕ@‹œÜ—œ <’í(auu•$IÒÚ¾l!Îï߈ɩôJJ{Í©~—ä ºIO ¾¶ö¶›ž}öðuÎaI&BâÓG±%oüâÆìEo?èݾí,äëOõ³ýzR&†õà{³^à%z6£=ÉbÐ'“óS³l3¹—$ kkkŸ>'ùa¡–_Tùµ¬ž_ÉLBQòoÙ™3‡_чÈ’-á0öV1{W JðNÎÆg&Kfq\? çîÎÞk!ŽÚ4«øžô&yü!Þ~fó‹à‹UÜtËó ‡ç/,cðJ¹g>5ŸÜéÀúRæéÛ®³×_ØÙ®Í/RÔr³ïFû×dнÜD¼#³Éów¾Ô‰Çk7âØ–%ð…¨½NÜ^Ï7\ªóúýIž‚·/VxÙm/¬Ìßrn9]ƒW.aî9ž¦†âÞ‘çIš´Ïƒ¨ Í¥Tå÷{ùý’¿Uoàú’âv7æ’{€QmåË·Z”ÚøÕ×·ÿ´³™#8LòxR¨èùBÜZ…¤E%T=7YHWëö©}$QÜ|ç «ó·½¸’‚_ ‰î9Œ rðs©• x›Vòš‹ÐXLS»1›Wõv*ùíõ´7À}A«s([8ºYyÛ´•žÀa1éÆïOð^}}çájhÚÖŽ*òõgøŠ÷Ô)®Î­`ÛKhc¨•«T“™€A›QÜxç‹·['BaJšæéY’)I·Þêª~qq'•ú¼‡¯ ñ¥vw ~.ý­Lúl¨ŽÕ³>˜üÒí…zé*’ðžùÎc÷‹þGkˆÈ·ba0»çî®ö'xJ ãe<³šöñ¹¾ý ÝÄÁÙüï8×>…oס³ˆ§|­Àh®¿õ»Íëïzq=Ñ.\?Åšö‰/A¼I; ³\Û¶‘.øñˆcTagWà°Ð^-øöIœf­xØ@Œ« ö¤-|œØ³÷y$Á G£ïüØéƯ7¢ÞÎÆâÚüâŒ.¯_ÍÔ¾}".yÇ—6¶y™¸ÝâÔÍßmÞx÷ó«$ÖÒñ…ó§&hÖ½®Í/ýÝ»®w/bxgãç– <#HZ«©(V»Rõï¦j³ùh+ÙÊö¢ Ëng‹„òOÜ×øoµÐ6]›ØÀœý·S z›7ëa·Ëm½žÞiK0‘åäõÏ7n¼ëì2‰µD.Í×iNú¨!%ÝžÝ;£Ôþf<Û!‘4äë¬÷ª~kRõŸÎvÉcÕ]Ó9EÚ‰0þ€" n¼.zòõ7·>ÑŠu–™”ü`ˆÚwmÛµro_è§Ÿ³FqÃ-ë[N¯,[Kä ¯Ÿ¤1 FÄù»þ¶aèÖJßéÌH¢L:z ÿWGýú–Fµ›&`ðu›]­D¼÷ü/ËÿÁ‹Wš­vÚ³ï©Þì^¡¢WTù%¨;ð3óà+°‰âúÖ×o»gyØX:.Ÿš¢5ŒìäéWûEO/C½" Ð\Nm}¿Rí¬¥Ñ€«íì·Æi(Ûòèö6 HϦlDxã1óäëoZûƒ /±´p‰Vc1†r Ò4nfßk¯¿Z?ôR­apr~½qÛK $Æ)X91E{2Hm~FýÙº~ÉOæí ø’J~ÔêUý"i .œ×,m †dË#Û«b:4Cø oÿ:Y¡Ñîth®-³¶|ÆÊE’ÖžmQñcê%¡^QÔ+B­¤ºàûixˆUœ<ÞhÜyûÒ‚$:Z±~jí}åÉ/:oýàZ®5Êáƒ]pú´×Ò˜XV¬µRÈç‘m••Ʀ½ÐE…Õ«¼:/;ÎSx“ü²¨nÏ~¨"T²†m_Æ6/¢:—ð“%JÒ¢ìÅYŸ"ðkó‡Í;nYZH½}Ú'÷Ï”zzø†“‚?JíoÚ·¿•:|íÕAðE¥Ž_Q+X äÙB¯õVtÐU[ØÓš±=ÏÚ”öùè[î”/$¶Û§?QNzÉPÖm|»†t.Cë¶u ÛY$j58¾­uÇMK‹b iŠ÷Ø f¦4˜Ûg8øÃR»ÅÅš{¾@ÔÈœ¾aØd¹€~òynäün¬®˜0´¯5ÿÛ}VdÁ¢ÿÕóëÇ÷±Vò¡ž?YqŒ ÔËB5BÏ¢m›¨¹ÆÑ} ­»n^ZTˆ•Fœý•±ÀOìh©ßSðt©Ó7êõöZ_.€tãØ§6qÇÅd$íF&pøkÝü`ÚÄÐ!˜?Ä“ïþ~ùPÀdül¾Ûž%ôÀZaþ¨jßw—ZR$(üùiÔl¹§³|'õIAíoîífœ) íUh-~=‰Í‚%už¡{ï°aÍjãb2’vÃŒúáþå½–¹Cð¦;øÃ¼Yó¼,Þ/Æù^š2ޤsß,+e!AN gKØq~QÅJë%·š9rÊÀl­dª}“ÜZìv×ç;\Ü ¼,FÒnú]«?œ[»¥[¥[øg_Ã/ßy„çO¨g™?WÖÅÂñÃÒyå}²èù6½›VéÄÞÁJøyûýIž"ŒRûÛšJ7 Y)·±˜zû£Àí•´ü<,"¨óGèì6R»‡ ÐN3Å"PqùRw«cÛ3ÅÅ6ËÕ€‹?y¯üê±},ûY&0ÍðÁ±ë¤óŠ{XÔÚ¦déø$Þ\ ;ÂÛï¸/·?ª¦¿«à«4½ÛXèíìú¾fZõž:_ØÂh¶Í»µO`¾v£ïÕ~«Ücˆ5Uáɾ…ÔËÄÙÖ~ÚOûoÝÍ%íÙc-þu5¼½à÷³UqáÆVìýNÁw…öZ*ù&ÞüláGsiøYÀãeÇä•Àþ+ÞµºÀn.W}6a¥p3”ÔŠ'QVìh“ ‡ê|õu·ðÑЧ½Ÿ4_}Ÿœ a°Ç&ðWsµ¿ø.ÎãoTÏß.øIœŸçö7XÊo 4.O»qTø2°=·„݈¶ÍÛÝ&N†<:ðÝ€Ü6'“Û~w£$'› øÄ„û+üå÷ß‚*OÙ¾’ b[#<¶ïÐàÛ;Q’¿]à-ÝÌž5£¥¾HÍ…Á¯H˜à“¨l¦ö˜¶ÃÃl~ÿ‘¾.ùÍÏÒ=ô»ÀÝ6¥ƒà‘Þ<©I‡`¦Ä7ѲóTB¬'ù wøF©üí‚ï€K:)øÎ‰Ûü¬ï­¹4Xè'Mƒ:Ÿ2®Q¡ßŽ˜d« 0» †ÎnfsðU¦"¤ç~yé3Ò B*‡_ø@*Ylnó]Q§¿_3o+—.’vê´×ÓÔ­µcH}6øæÒè€#ÔùSÊ<•ÝS F¼+Úa« 0Êëw¯™ü‹‡à#hQÆ’3BÊ*Ó *{n‰mƒ¹#ogâð-ùÎÛÛQûý໵â¸S(*uÜðÆŒ!õnŠH[Ì;Íßoi~…Ítçf,»c&Ø­›F¹¸ivŽ`zkE‚)ô‘íesàSoרf<Àܡ׿èߌ?¦7Ø¥î7ºLmÕ4—ŸÄ)3ŒcëER Ñ\|ŸsLð™‚÷¿ðWEŒ¨d ÊÝÞÈÝ¥­§ËýS&°ù]q Ö6ØèÝ8ò3)ðvcµ¿•æÍ­N¹º:“dºnÌ ITúùæbÚ„:ÃX`–_¢Ä%’žVð]IùŽ¢íj€â Feª eÁ`ŒÎàvÒŸfô%½]¢]föºpàÈX3~Ï Ý>Éè•øÀ9uÒ½Š¸VšÈqþÇVv=•~Gc!cœ1Á÷y‘9~3»Kàf¾kL° 0Ã䬛m—ÌÝKèî†oˆ1v•é¹ÎþÃÿdüaûðõK½a´Úß|é^‰I²A­Tb-cÚø!ß×YK‹?ㆅdcžâC„,‘à±±ô_u Pd‚QƒÉ§¶0h»ëˆ±¶ÉÌ¡÷ràȆª}ÇBýàS»?0]} ÇQt·O±Èæ1ÎÀÕªlWåÔlHÑ7c6=.²Ÿ_ÃhÓþwî:b€âè·ë·¼é>Ë ‘m1uè_°ÿè†J~ÿÚüÍ:xF­Í&éq;[‡_}«­Ýg:kip\•_$ìçW(ó"I^úÝhNw•ÆÑE®gpE&}çIó‚¶Cmö'™=ò¯7£Š^1Ô³€Û—0ܤΘ‰wt÷®‰³½æÖ}7cg˜ãƒ˜ µê®«~GÃ@Fœë—æaZ¢ÿ³fà}Æ&TþcfüGÄʆ6ß?ªk7‘ø6Û^%Î~f&[ ;=»<— j­¥¡¡Ù‚­ï'‹á0ÿ(óüõÈwíø0È;œ»Ç”ŒµT¼‹¹cÿ Ì&6ŸÁ Ší]‰I8J÷åO’TÒ­éÕ?ìôÂw˜$MÿvÖ¶ A 0Ío±ÏôTýúçpOëýÛ‘n2 #£éÍœÂÔñ«Ìþ]fý§<ÁƒôJ¿Û/¶ÙZ=“n™ts¦È@œd‡!¿_þË2hvByN N¥=Oí©RÇï<×ñ/GÌ­{×5W ålô2@ÿö~ú¼M§2û”EËß§ÊGiWh+T%}4ªÐ"b ]<Ò5ã.ÚØI×TX“:vQ3=’¬ ¹cß¡@ 0Ë¿e?eà÷Ó6·ÖÜyl¨³¦ã„†ýŸ´(¹Ÿ*ÿ9Íý«ÌηÀ¶Àz`°!˜îÌ‘H7ôKìð͘`àcüì²M’mÏÒéF Öîž´ÉU>ËQþåˆ1_©/ÒF& ßÓ/>ïoUöi›ª}>„¢Ü+µNªãložuˆTÊ&€ÄO™Ãx™†P½a^îC !éÿÇù6UëÎYL:Ùc”žÏõÛ.J{ÿlú<Á þš(+ø_=—£¨Ø‚³M*¦G »66Ϧàÿ(5~…î±Ñ=q¾T¾M7gŠ£nÂǤ»~€"õ4ˆå×ÅdI¿Ü˜,*H²PÐt‹ëïòHag¹!¥?·Âq~” çúTÿÖì½&Ý5HÀ4`ymãÏlòuã8ü}Ç{vàõtíÏk©òÁð÷8|ä‹ð—O[.¬lã7w@Ûmég‚ˆô†çm"û~Œ,ðžBû'tìpÐ7ÛŽ ^:à'@È_pŠ·Qç, ”p dh.§.ÍãOÁ™Ëðß¿ Ÿö¥>vnE.hªV½ m"RL}õ<‘ÄZ›çEÝskmñ°Ùùœiž[ö8 0¨–JðÑ?Ÿø eÄý~ÒZ™ˆˆ(ˆÒJ{´I—4{Záù Ï×Ö÷4¾¾µ¨ÈÒžíÔ;n2÷ÿÓ»Í?Ü7Ál \‹”©úÕuý þ?—ßfIžG(i•§Ã‹ÞL""–ÞžåDD6¢‘<’=w1Q$"iÊÌÚØ“d‡c˜±äÖ@A£ ·½×òô…Á÷‹ˆö}_+¥ ¬µ¡µ6ÂìyÖ€ DðÁ¦¢àÅIbÚ§¦í‘»Å¾ê­7p÷í8‚*LÛÕ$!ÏÈ]béwÿšoü—¿â‘'9ø"ymØ‘{5MPÔŽºìèdŒÐÉžwD¤ ´D¤´¶1&Šã86Æ8ÆÛEáÏ¿¯xÿÀE)å…aX&Œ1uk턵¶f­-[kK@)ßÙzÖZ—öÐÒ]5¬1Æ&@h)ÿÀ N½ëvîzõaŽî› Ú3eW t/Y«Iô™gyæ£óøgÎØ3‹-Öè–dò¹íŸë!ç$ÓŽIŠ&Â1BÒÇ-i‰HSDÖDdU)µ""«ív»‘$IÌL° -¯µ†¾W‚ ðEd"I’ëŒ1û1S@ÍZ[r’ïÀ¶ÖºTq¾kHêú .1ï$Ö~ò)Î|ò)ž9>ÁÔ;o•[ßp’“7O3;Q£”E½-à;Û®YdÓj=zŽ ô Oÿ÷'ì™Wo³9*ø–­óüòç…óÎ9LË_]?!&5N¬)¥–€‹ZësAÄ­VËX;òÒ`"HàRŽÎÀÙ…îi¥”xž§“$©Xk§9`ÚSÉ€÷ú@wǰž‚a“<»Âúû¿l¿ôþ/óÇ&¨ß19z×~æî=À¡g˜™-SŇÒÊF "ŶlÅâùuÖŸYfù+ç9÷¥xákçí…'YÂe5ÉÂØÝ£QÍ5ý䂃"3$ÖÚŽˆT¬µ¾µ61Æ,{žç)¥$I¶ÿð(Àj8·§ßg9·”¿U—Ë¥’ˆÌ&IrÄsÀ3m­­fêß'ËrÁÁT¬ öOÂ()¦Œ,àÏ•)ŸœbêÄ$'&dâø“seÊsÊõ€À´¯QÖ¦‰¶fD¼Ñ^jÑ~qµ³«¬>½ÌÒÓKvåÌ +—›43À‹ú`¯ É£òEÓ‘û™ÿà|ç¬+¥Eä‚Öúy¹Øjµ™/0üG· á/ž€ÿû“ðÇß°¬4‹öÃ0¬Zk§’$™²ÖNZkk@93Ž|k­«v'WeÌ1z[™áÅ *n÷àÞ£­Ñ‚¸vØ í$ì°[Š6o+@—ÂXlrÙ,)8…Qfÿ[€óV”RKJ©¥N§³DZ[½Ë 1,´à­ÿ—åóßFi­µïûˆ„@ÉS΢ÇAö1B@7E¬‹NaÆ9sô2ŒQ€b­Ý¸Ôí¸Í­#%UÒŽ¢þø|d4@o~ »ì¥7ˆ2ÇÏEíì±Eê¶3G°e­mÇq܉ãxÓ˜i'p4µÓéš™†cû°&I›$I¬”j+¥ÖDDg‡'"¾ˆ8Ïßi÷ÜyÑN3x¤~C®% ÌÐÏN{äŒ!) 9“dêÎç » ؈a2ÏémË“0}€÷´ºŒðêóç‹À÷6Âe_ÈD"[k# Îr±1&±ÖcÌ–rÛgwÙ&ÝÁ³p†l@®œ'†”RJDòPƒäÍ#"R”þü¹ˆ8ÿ!÷#DD ‹(èÓÀÚÂZ‹µVD3¤2#"î°ºõKw„‹HžÕ+Hºû¿XðŽÝë™÷î2ÎÙË3‚iÞg ñ³«IòñÊÁxïÀS„Ï=Þóû=ƒ²Ö’$É€úuR™M°þW…ç’%ÖZ¥”ÊMAáùØîsN³ô¤_Œ1n\#/O)…ˆ¸Ç|²1N3ØôgìH¦°éà{4Dö~“}Oþ–ê%{Náµ~€÷¼"òÿ&8AßY¼%tEXtdate:create2016-10-22T16:52:13+02:00!pñ%tEXtdate:modify2016-10-21T19:33:36+02:00IÿK-IEND®B`‚Slic3r-version_1.39.1/resources/icons/Slic3r_192px.png000066400000000000000000000527121324354444700224670ustar00rootroot00000000000000‰PNG  IHDRÀÀRÜlgAMA± üa cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFbKGDÿÿÿ ½§“ pHYs  šœTÌIDATxÚí½yØ%iYßÿ¹Ÿª:Ë»¿ýv¿½MoÓ³13ݳÀà°DE4#.(1æ£&Q Ä-j®€šˆ4bäBÀ\ þ\PÔõ'²ˆè Ì0‹Äa˜}ïîé~÷³UÕóüþ¨å>Ž1†ÉÉI|ßG)…çyh­1Æ "4›M‚ `bb‚v»~×÷}&&&p‡0 ñ<€ ¨Õjh­iµZT*D×uiµZcPJÑn·©V«{1‡ǹø*×uO8Žs“ã8”RcŽãŒ;Ž#Žã ”B$ºŽÆŒ1„aHh­ÑZ†a²-ÐZ·ð°ˆ|xÊs§ëº_2Æ<Á9¥Tèû~zÌÉ9A@¥RADÒ±p]—N§CÔëušÍ&É÷•R8ŽƒïûéxˆÆ|?D‰æÏl›¯Šj«ä{tÏ&w§1yYRОn‘§¦¦¾¦V«¬T*óÕjUÕëu*• 6ØÀ™×ö>­}§ŠÐétÜf³é¶Ûíqß÷_Øl6_èû>a"²ì8ÎÀGÇyÄóàn ØéqÚ ¹¬;$ƘYà6cÌKDä333·×ëõêØØ¸®›=qü½ÌßÊã88ŽC¥Rarr2Ý—ïû4 ÖÖÖhµZîÚÚÚž n‘Û]×Ð"ò9à”R÷)¥îÎîôøm–\V€mc̘1æÕŽã|£Rêk'&&NLMM1>>N½^G)•ø¨ ðÛ™çD\×ezzš™™Œ14›MËËˬ®®RJ½DD^+ÑÓÆ˜cþ@D>iŒ Šf ‹E.+À‹1Çq^¤”ú¾ ^Q«Õ®ß¿?“““Œ¥ OxúNŸ­µZ±±1æææh6›,//³¸¸H³Ù@)uHD^'"¯«T*w…aø7Àû€{vz¬×#—`“Ų´Sžç}‡Öú»Œ1¯˜œœ¬ìÙ³‡©©)*•ÊŽ‚~Ðñ'Ç”(ÃÞ½{Y^^faa•••ÔYnuçVàŸä8Îï…aøl­+»©rY6YÇ™¯T*?lŒùg"rbjjŠùùù42´A_&‰2(¥Ø³g3334 Ξ=Ëòò²­ÓÀ÷¹®û}ŽãÜ'"~ͳ²Óç0H.+À&‰RêD½^ã8ÿ88>>Îüü<“““(¥.*àçÅžÆÇÇ9~ü8kkkœ={6?# "§€·ýk×uÖó`m§Ï¡L.+ÀEDöW«Õs]÷õZëéJ¥Âüü°oß>æçç©T*—ðó’œÛÌÌ “““œ;wŽsçÎ¥”ɧSÀ‡Œ1w8ŽóËqôh§?µÓp±I|Q¿¹V«}ºZ­¾=‚µZãÇsÅWàº.anjs7‹Ö¥ääÉ“LLLR=¹Ýó¼)¥ÞÌìôq'rYF9î8Îû”R‘j­™››ãäÉ“LOO?¯€oKâ#ŒqâÄ æççKóJ©7:Žó ùGCî^⇲ɶ ËeRDä J©Ï*¥þµÖáÈ‘#=z4­ûy¾K2\qÅœ8q"­Ÿ*[DäcJ©_WJCŽ =·6ðÅú܆á² ¥Ô ×ux7pHkM­VãÊ+¯dïÞ½iÜe‰$™ ¦§§¹òÊ+™˜˜(õ…DäûÜŠ÷D}›ÇPj€?œÜ#?Àá²ôù®J¥ò×J©ïÃÉÉɾ\÷²D†aj(fggcz½—˜ ¨:æªz­ö‡oû³=ï~æœ3NÕt­¾c=)Âå`3%±äJ©ÚÄÄÄQJý6p¢é}nnŽãÇ÷›Ú/‹% UûPõ ßý?ö}ì¯ï¯ž¢bZ.Ñ `Ó ~³Àºä²Xâ8ÕjuÿØØØzž÷ï‰ÇGk;}û8zôhšÔ²Kž¯Îï0bŒA€£GsüÈAª®¡â<Üøá(˜®žYvþÁþæÜŸ}èscß+CW Yþu]€Ëy€X´ÖÌÌÌÜä8ÎEäÚÐa²wï^>œYl099ÉÄľﳼ¼œY ò|‘èá(1ˆ„<0£ gÏ<ƒPJ¢çø³Ï 5ó?ÿ±Ùßnúꇿçe«¿C(U4Š,À%þÛšu‚.+J)&&&¾Åqœ÷3 øµÖÌÎÎfÀ{öìá«¿ú«¹í¶ÛØ»w/kkk|ñ‹_äãÿ8gÏže7%z¶SDbÓƒZ©ØLKd¶Ñ<¸ŸŠ'œ;ó J N¢*VÀT~å/¦ß{fÙ½êǾaéÇhBÑD‹rÀk6~¸¬c¨V«?P¯×ßmŒIÇCkÍØØHiO:tˆ7¼á ¼ô¥/ÍìçÔ©SLMMñþ÷¿ŸF£ñ¼™ l€K x”äµXV^ÐØ¿‡óçÎà9’‚ßQÝÏÕ<Ãïß5þÚÈøë¾fég¦k¦‰&¤ ø ƒ.ûˆÈ[EäØà•‚ùùyªÕjZÖ099ÉßøÆðC4‹¼ò•¯äĉÏ Ù¶òŽŠÀŸ‚Üz/yßQ1çP„>t€ƒö᪪K÷áEÏuöŒ>úwãoxð™Êu8¦E4„tÁY6(¿¼ÙÞ`ŒÁó±öžYõJü\õÀS†±šÃW\ÁXÕ¡æª^Ôåâû_²ôÓ¯ºiõ]øRÅl®Õ·åù3Ôà·ÿBøÕOÕþãd-K{ ÿÄÄÕjÇqððÀÊÊÊ@EÙÍRdõó|?CyÈÒGÀq²Ü?u€U÷='ó½ssøí&ÿàУ¿ðš­þ<¡ƒßŽül蔊äÒ§@´}áç><ý_>W}çD5 ~èòþ¤OÒµ-×uyüñǹãŽ; ¢Óéð¾÷½Ï|æ3=ß½˜$þ´ô2çìfœ\ǹÏ鎓¥;v„§æA-þ»æAÍD9¼úTó­ßqëê&ƒ¡CõÙ2ðÃ¥>àÞñÇ/yÛŸNþúžq]Ɉ֚©©©”ò$mülGVD‚€ßüÍßdrr’Ûo¿=]àþÐCñÁ~O|â8ŽsÑ&Á¤l[Žêd¬¾êÆîó–>)sð¬×®EƒÜôûB€Ãm3¾óGz Fƒfã–ß>ÒýÈ%Ý´nøàgÆŽ¾î·fî¨Wä ’ì8$ÔgÏž=éßÕj5m•— ãºë®czzš¥¥%|ðAÒ~™£ä9)‚ë++¼éZ³€›s‚ep2Š „ÆåôÌW~ée‡ø©¨VNðoÆie¹ÿ÷èç‘à}ÚõÞú'S¿îª^ðCdÙ'''3|?iÞZ$IÛÏþói“\×u/ Îÿ‘¡=*WÚ VH3æð ð= Ø ²öç"%B\n˜|è]/;ðÀOc0I¢=ö¡­g°Îôýþ¥© -ü‡Mÿ—‡Ÿs¾n¦nzFÁCÒtÖ¶öƒ"8J©‹ð¶‚ßæûä,¿ѱ¬¹ òJè*Bþë&~ÏKæïÿq@Çà7E‡¶ŽÓzÀ¥©ÃÏýÞÔ÷ÿñ—j?6]~ˆ€lgz´]úóAŠÀ_Vãò¡MÛê;]§7¾9¿Éöðë'|Ïm{ïÿaH-¿)8ÄÍ´þ…ûºÔ@¨óÇŸ¯^óîOO¾}¢Z<~ÆÆÆÆ2±þç“ä£=*ywvÅ Uæ\nÎÂWÜøN÷9Qב˜ ‡ëÆzß­{îÿIzÁoëåFÀ¯†ÝÇ¥¢‘¶»˜³Ï)ï§><ý`N•L‚J)ÆÆÆŠwt‘:²ÃŠý]üžÄ–Êòx;Ü™Xüô×þŒ¤ßÓ¸\Uô¾pÏßÿàcŠAºžB7»s„mýŸ7>€àóö?›þáÏUþñL]¬1™:Ÿ¢÷/Eé—Ýí©âÌ?Ö´,¼gÞV€ø6ÿñ8ì>öþ[f¿ô£@ƒ_Þz/@ê¶Œ²ŸKA¢i¯jôTŽÿú_ÿÌdM—ž}÷^I.ÌIøs”' afÀïæÀŸ¡=’¡Iêñß~Ñž/ýˆÓ±¢=™ú:Ö_ïcƒ¿Èú—îóbW€è„¦ÕõæO¿=4LWKXL÷¾qÿ|EgbùÉ'·$ÛÏYþÔ±_µèN5õ$û7â²OžøÝÛfÿîßx¢tĠиDkä³Ë×{zö"y“{ô•‹Yº¡®Š1ïùèäw~ö¡êwN×Ëë¦D¤”ûC·§M¿\ÀÅ&…å̱‹¨r¼ßÉ…9{*9cW¨xYÎ_q¥'ä©ÅcŽ'ï¶éû~ÀÓº/`0¸!›Uˆc+ÀÈr±*@ׯ{F?ò”;÷ÎOLþÒxµ\á1iÒªŸõ‚Kç^pE…mÊJne®”2xVˆ³â–<œü®~Z¹Ì™'ïÖñûþ•§u‹Ž(@ã €ÊXþ|$hØÙ ïøBïj±KÎ ÎòFoûØä,4äød­ÿ¸å«<‹Ä÷ýK‚Iîuí!ËùËŸ&´ìð¦Eyò|?u|ãýÔ½¨~ï¿ôBÝ¢%‚A!1d%v NüÜ=ÔQ?ßÈÿ%í'ÔGÿÍßW|ä¾úûYˆèO­Vø™äÞºù‚¸‹U°Û¯3‘§—öx¹X~Êù㥊žêÒžŠeùÎ?<ù¡[Ý{¿ßkë6HâÆ& ‰aiЂX*=‘›a!¾¥³É@¹˜ kùãšÁ·ýéÔ[Z¾ì¤Žã”†>mIš]9޳Ó绡A«~Š#=’[³ƒ;µø ×Ï9¼)øîw\´ò˜óŸúß/”{¾×阤‚ƒÁAY0€½BLÚú¶÷TLŸÓLÀŸ|³¨MÊ@E¸ØòþÝë[5æÃ_¨]ýÙ‡¼ïþ¤ÊsØ2‡v»½Óç¹±Á±¯¨ò0§]™éZ\¿Gu*¹Eê¶Ã›–>Äß7ÊeÖ?óg·„÷þk'0€T!µûѳA0ø‹±S,¥UÙyF—Þò_23€¾LЄ_ù‹Éÿ2&Cœï°l"B§ÓIûZ^,R¸’« š3SÉ©ŠKR‹ïY¼ß/~†ö(0ŽËtëÙÞâßóa‘*Nä41Ý1qcЦь©FämÍy0ÓŸ¡”`·ÏÅÕUcþä¾úUw?Yùöš7ø<•R#­Ó ‚€v»}Ñ(@>Ôi7©*­æ,ªç±Wn%«¶*óý´Ì!áü®Çxëì'oiÝóúª:©cP„:~@€‰×w  ך Ц ²eÖßöú#ævó PŽ> ¿ýùñ7ѤÏOrCêa¥ÙltšwZʲ >Ìiwk°f€Ôé“[y˯ÉÆ3¿¹}ïÕBŒŒ!&spIÿ™˜)AÆŸ'¦G^a)Cš¼¦Û$7‰úØÜd%Øí3@o]wùÙÿñû+ÿt¬2ÜyŽºZKDð}WÏ}²Ð§¦§üÝÉwhË[~WE¼ß8.Ó§?qKãÞŸ3~ #Øws‰ò½Ñ `b胊ÿEŸ ‰Ö~%+;±/P,E¼ßÿºBv»UÊQ§àŸÿÁ–/ÔØ\ïBõf³¹ûC¡6ø-‡7ßžÐ.e¶­}è½ø­î‰õ׎ËdóÌ·6îyS¿cßÃàÄq“*BDuTL~$΄9ÞQ!;Úg?ì˜?ôZÿ‘`ؽ @V\Ì“Ï8Οÿ}åÛÆ‡Ä´ˆ¬+¤™8ÃÍfs×Í)Bòà'WÞPPÔ–,Y,ŠñÛÊ/mHhŽÇdëì]·¬ÞóŸ*&4 “˜ÒÛ9±åïÎ+C·ºÕñ)é4¼]$ùZŸ<ð×ÅÿawúåhóŒùÄ—k¯|vÙ½¥_Í-J©‘ùz "¬­­Q©T†Ê!lëàØ%Í%kwÓ‡ÕÁnW’çúIä§âô&¸\%¹&;ç¾|óÚÝ¿\3~dð3i(±¢1IxNÇóñÖ€0îý`ÛtC—yú½ŸÊ[ÿuËn¤h‹îÀGï­}“ÝLm c6ܪDkÍÊÊÊ®?‡7ß‘-)eÎXüÊE|$éñ,ðO›sßÜùk5ã·Ñ2…¡?¼xp£eIqú+{;£h² 1ñ#àØGÔW€ U¢ÿ|ƒÜuQŸDvÛ Ðëô&âý¹û«Ç?ù@õŸ×‡t~!Zä>¨þ§ïÅThmm-í·S“¼È³‘£<…m™fUn6»[ÍÄú%³ =éá£ñØç<{æF¹û£^'d XM|ãO ¬­JŸÈÆ+«âG£Óù8 c‚”œf’5žõ½e”ç’¸CŒô}Ï{žô^ÞòÕ\žUäf”4$TÈu]jµÚ¶+”ü¿UÕi·#χ:3aÎL›d­~ ~ƒËÞʹ•ë÷Ü㵑iI¯~cÑM’öJì:qÕöŸ½ výqcE0>Pµj‡Ê^’ݤE×:٪Öðá{êßä¹£÷f9°"ÂÊÊ J©žV*Û6 ÒmUÒ³x½ÏÚÝŠë÷²¼¿üŽ þÚùÆ©©/>䆾‡/³¸¬á°Š"@§ð´A½îvwKJ!’œpDk :ÝN¼—H$u›»CÜü½PN{ñŒ¹÷1ïÈ]{ßTQ6³Í‰Öšååe‚ Ø–ÈP¾¶GIÌùÕðX}›îØœ¿RRÞ ÇÞÚsÍÓ{î|Òu;ŽšÀ3“xf†Š™Âe¡ THnjm¬ûúš‚›['$)9+cf¤&’ù× zÛé½dî3¨’àÀT¾¦ÑQÓ£ân3*"„aÈââ"¾ïo©ôãü§Wõ&º’ŠÎtÝnç·ŠÚì¾—}õçZ7ì¹û¬£|ª†è*o–}7|=ãÓ7ᚊ:¤J঎08ÑU‹ÁoÒ’‡nL_¥«$ó:‘ngP;ÚST6½¡‹°(Pÿ“0>|âËÕÛ]gDb,%˜žžNï!¶Ù’œ"à—,d±±TËBùŠNkM€ÁeOíBçÔÞ;Ÿs•Õ8‹qS·œ€½“„•„wÐK  ãjŸ$ˆir Ôü»ÇNjãU|²!½î¬Ä®°-Eë.z Ôï$…>{Á¾ç)ïUÕélî `ïSkÍÒÒÒ¦'Êò´'ú½˜ö¨àws–ߎíg_k—ÕÔÖà2W;ß9µ÷®ó®ãÊEŒB9.³7_ÁØþIÜê®ÇžßÂÄä-¸ÆE¥4ÈIÁo,ð‹•Ú º1¤èŒ[ö8Îýxÿ†§ÀŽ‹cÌã êðbCßM] m%h4é½6:ö‹ž{oчó»%^I†×êëãZ–®~¡sjß]*nÛ`”Fq˜½yž±CcˆÝæ± Œ9æN¾–ññpŒ ý1i±šÐÍwãùÙ@fv³‰N+‹”ÕFe'!5è$¢tàÓTonúâ쮂RЯ¬¬°´´´¡5…å ø)¿Ê‚¿¨Î§âvËš½Üâu›öL×–‚Sû¾¸à¹ƒQ 1Ѽ3sÓê‡j?:@%ñšHu·ÆÌÉF­v%Ê7]üb'À~o?g9· BuÀ{ÝÐØE6µð´ŒîÀ§¿R}…»‹W(Š­V‹……:ÎH³A¶å·j{J£=¹WÆâç#= çË"‹öÌÔýÓûï¼à¹-1øE Ó§g¨®¡}ë`ãƒqІ±ê4Ó'ÿ%w/b¢¥.Bw˜XYà¢?û>iFX ‡(?týþJvJ†±þ ÐçÜñθ/¯ìbÒõ‹‹‹¬®®5HÉ™›Ñ©l¤Ç±œÝ•\JP±|ƒÌJ®Èòû§Þu¡ê5CŒ’˜öÀÔÓÔÔ0Añ;DtÈ ¡>v€‰c߃#²µ;ݨO×éí*A~NHömrðßDÊ“—žúi¶ Æ,6åP££NÝàbÙάmøµµ5hµZÃÍíQkxíÖ%ù~=ykŸ®çµ“\n¶®Ç‰ÕNÕ–ýÓ‡î<_õš!(A´AÄ0yã4µ#]Ú“fî*9€+ÁØž2~ðÛQ&¤àäÊ B]@·mJïïmÑ,°Ó Ð_øÊgÿj[*ëõ/w¢v'™ –––29[ CÊ2ŸEmvŸýžâ6¯÷a×ö¸ÊæüÓõeÿôwž¯VðƒRÂÄSÔŽ[þhD³)(EœÓ0vð5T§nAL§Ç¾ƒØ¦®‚ض?;,££s䋽 C¼—P î{Ú;å‡ë¯ìXñZ øv»ÍÂÂKKKiYÙŠ d’\va[¢ý:µåcüi‰C®ª3Mr—ÉÚjpÓ±/œ«Õ~ ~ ŒŸš¢v¬^hùÓ¨»£³yp x¢;úƒ¸Îbtú~–óKÁ<Ý&eiØGýÆv+€”¼.–îÖ{ÁF`›¤ù„c°'NrZJ¡¥¤ç®ë’X~ûyˆÎ )ø®åw-à» 0.õÕàô‰;ÏV«Í ŽöÄÀØÓTÔûÒƒ¥Ò«ކJýõ+¾E¡—çw=ƒìüìɦLª'RTFƒ.º(PÙw£ÀfuU¹÷®cÆë«Áé«î<[_óA)”‰Šj7LS=>†É-Èí¿€6Å Óåê!Töþc¼é#¦Uûé „JN¬ßÌãf_—ÝæØ'¨›5Öê0¿‘üÒniv›œCB:í6ËË‹,,,Ðl4Ð:@‰ÄwT‰îªâäZ—dZæ-¿×ºŠË¢=ãõFpÓuwž›Xõ11øÅª/˜Á»r<âüÚzÀŸ\ÓUM÷5XÑq©ùw8î b‚œ3Ln& TºáÐAÌaÝa7)@öÄ”1gWÕþÕŽÚ¿ÞÝësÇNÊ.iˆÿ‹hNä ÐX[aiñ«+‹´Û ŒöqU7~Ÿ/m¨:ï_µãü‰Ó+1ç_ nºáÎsc“«A ~e Þ fq®œˆÊx,)Z䀬‚ œú1¼#?„Ð!YܳéuƒmqÖìae7)@VV[2Þò¥º‘À³c]Þ¤÷‚gÝDGp Msm…µåškK5оâã+…G{ôu³pr²ü¶ô,Aп þ¤¯OÒÝ!¯@‚‹sðu˜å¿BŒNc¼ÙTXöo{)|w؆™ †ý\F¶sü‚Îz³¾Þ¸åö}ÃûéD «]¿_v RWÙe ±Å÷O(Ó‚`°½Hؼ€i/a:Ëà¯!º…C€+GÚe¬Þ OßzçÂøÌJ€QÊÊ@xÝ úªÁ?Ât˜µúùGž ÉÄ‹PÓ_2ÍÀÓ³ Q„¼ 9ô£^«íšÖ…bƒ©oF¿ÓélÛñlðÛ³@jø,EHBœE}ùm·æF´§–&¸4 Qd2 q!.cãZßpëKã³Ë>Ëám\5MpÕ$b¿wÐ{×\%ÀöÛ…tÁ£=öh3`æ¿gésÑ¢¢ˆtÐâçî"yC¯u/³ö»zV$9•3Kê¸Ú wOÚn‡¿õœr~EáYÒìn~+›[ó Vé‚¿ÏUW¢nÍ®àºÑžÀP­4õ­/ÿ»Å©}‹>¡eù[WOÓ¹®üy°çA¯-†à·zÁß­á/qŠ50õr̾ïÀ¡‘?œ®ó[4ô¥äþî¹%¯Ke'aý4„§ÝÊz#@¶$7½ØÒ˃?ïð’¥<=u=5ü5/¯ %Ê/fq*ÆÆ}Ë×<¼<9ß•B (ch^3IëÚ‰RðÛç• µþ>a/Øí×e7( á7BåP%“K$±ú½¨Öɽhg€Â õÍØ¹1†N§³u? Ú“¿ý¨Ý¬*¥=V˜³ƒ?}¸øÓιÂ6´¢>è[¿ö¡åÙC±ÃKíY;9Iãšþà/âû™À€ßÈR{èGÒìqåzæP4sô§W²ˆ(ÿðÆ´ì(< ”ÉÍÊýmU£Û¼å·ï¹[ÖžÜS½m5·—ú$ÏO2…oö1ø_øÊ—§¬Fœ_ ˆÕ«'X»n|(Î_dù`û~ôÐÒô†>³€`ïkAMÄý!z£Av´?Ð׳½Pv›dNÂsÌòf¤B’„Øf·3éqx¥·oOþÎë™U\¹²ÛêwoP!Q™ƒc‡;#ç­¨×sËW?¼2½?? øO޳zíXÄÁs–¿Œ÷ç»n&qþöZìüRL{Q ˆÞÔc·¢Ç¯C¬ˆPQØsc—h$Äì´ô=Õv S›5l6 *rxóaÎð;½à¯ðþ„öTmÚc—8(­¨Ösó?xxyöÐr‡v¼bZŒaõx•ëÆÖE{2àð;Ñ#q~z̪Îþe·¾Í’bêÓ¯®ß¶…rۡ냰ÀdÍ,l&kiµZ›sBo²h=y¿´geÍ+nžçÛ4( ~{%FQ©„æÖ¯~xqÏáå6m…EkV×Yºa¼ÐòÛ¯‡±üÚ@k5²þeNo>þ?h¦¿ƒ°~Êtï 0i‘›Þ J_mµ SÄ$…Ÿuàª}Á‚Þ„DtámhUí±cü‰Ã›†9mÎoE|²¡MËò'´'.wNÚº Ä(ª•ÀÜò²‡f,·i«xµfõXåë³à/+o~v3ÎüJoسø:÷;ùß@A0ó ¿ :ÁÃ@ËÝGò€ßÑ ¬—ËàƒÓpfE—D¨¿ó£qŽÍOOVõ’Þ"PJŸÕÕU$æ;v1Jšà²-¿“óÛ‰®|a[Í×ô–X~÷ ½ñšÇ?xèü9B‰o*mBd~ÎMÇ{Ào =’ý,¿‘ðÍ%h®tg‚2‹¿UàO$hE; Õ~´Œ±ÇQœÞ]A†‘þ©m-Îl]Ÿ«˜'µÙ G8yš5Ú­V¦EaQ“Z»Am¢v¤'ë/tx㇒¨ìÔ {âÐÏ'P^4&F£öÏáÞr"ÃùG*o0ÝþТôjÔÔ”^}ùÉöÇ:›Üá0Ó±!¶ú+ËË„a6­ÍÓž¤UaŶú¹ݳ«<%Sד©êŒÿ]Ý“Ï:rîªÎñ¾ì÷äÛnnþÙæÖuŸÓ×JÐ&dey‰´a­tAï:Ù:þ4ÎïõÖöô€?íQb„nxüÙ#'ΜnEŠB´}³øG¤=ðÇ\¿Ó‚Æ´Öº1ÿaBœü?ŽˆßˆfDBuŒÞмŒòëCç¶+ ´þƒqíÑgÆ*¦±~@¦’ÓÚ–´) ük«Ë]àçcý+¹jVÒ«Ðò[¾ƒB0F¸þÆ'Î]qâìblù¢5²oÕÇòÛ#TÞ„×AÌõ—¢²æ<×/«éï7¾›þ8úúVÌÙ€ï]ÇæÆý‡¦K»Å(-Îþéðé™1ýP¸Áphaßž\E§«„N»A»±uiοÈòW‡ =I´#\{ÃS G®>³ƒß‰,ÿÞiÌ-Ç"u+0†}³»±ÅØî,DÖ?‰úäûøñýí°üøÍ˜þX2‹)mvµ¥²Ó 08—¢ö̆Kß~só÷ÚèpX¸’+—äêR¡ÙXÆo7©xª[×SPÓÓÍîæêùmðKÜ] /8ýäâ‰ëŸYD«ènŠJÌžIÂ[Žô€¿ht2щÚ0*bk\ˆ*9S ü"ËŸÎÿîfBÒ˜H2„W Ç1™;ƒ­+µ¶ž£Þ.`ƒ¦uû‰ÎëÝK~%—]å™Fyò‘Wh7–Ð~3­Õ/«çOºµÙ _Ò;³HD{Рn~réØõÏ®¤–_iƒÞ3NðÂÃQ*¡å—è9ð¡µÍóÐi·-ì·‚kP9Ãf‚_$î,dá Ö:W0S°>`#™†¡d·øý“ To¾Â¿gß„~pÔ²ˆž¾=XEmI”Gº=Q‚ˆÂÚE[qW¶‡×.lËÝ„Z$¦=7=µzì†gו±ågÇhÝzSÂò'Ö^G@j.@ótš]‹_Ô´j˜Ö%-l &r~ó¢Cè´Ç¤R yZ4ìîŠ`]ÃTxÎûƒs_{MûOšþè+¹’Í´%WÝV…I}—‹óGÙ[ƒß\Dé6µŠ*rkÒ<ø•&P\}ú™Æ‰›žY#TQEµÒ†`¦NóÖýPUZóg.]ÐkaÚËоí%«MyIí~?º3L’k³­ЊŸù騄êq& ê¥a ìíT€ ©à¾öÖÆï‰0”'P¸€Ý*mp,%°‹Úò}:k¡æ‚Ö¬Q¯H¦oOµ ÉåÄ´Ê„Â5·<Ý8yóSk„JbÎþtÕîE×,ðÇ`O®•ö!hB'ýBôwQƒª²–…ýÀ_Và¶³€1E+¸¨-ÐfŠÐSTε0nWDÖÃà»îKí¥';w];üu'¼’Ë~ÝSÍigxsíÉSŸ©âŒ@o:K¬Æ7§ˆWrY}:mð£WßüLóä-O5ÐÝ$H´!˜®°ò¢=˜1­æ’ ÚÝ‚`:‹à;Ë1èµé1Å vñúvq~ût½Ö"ëŸ8ÅNO"¬,À€Ï$»•‰!D&§uóÕ§[Üì3dhíì =={’½â¬äJêÉ£cž þ t–"ÎïÅ·#µVs)À„Wz¶yÕ-O­ˆ`$¢=™ n™Åwá+à/Bg!~,¿Ñ;Ê“Xð¢ÕZùY ð·²¼¡øjÄ­ü"QAœŽâ6³%‹ä“£“ÜßEÛG–î Tö¢Ï !µ¯¿¶õÉñªY.ª-*oH"=™u¼¹z~»a•ׯç¡VZE¡Â&º}W‚讎Ê¿¶ÀŠ`DÚc.gOÓj*‚ç"K6@·ÁñÇGœ€>>ƒiÏ ào;øãÓi¯öÆýñ[Ñìg€ñܑႶ%ÛÙ®ìà‡+j2@G*·_Ó¾û›®o}h­“ÝuQßž4Ö/ÙPgÚ¡YåÖîæ­~lù땤y•¤nŽé6/@ØÂïø¨C‡«O?ݸúÖ'VS5¿Ã³WOx I€6Ø%;…¦ü£Ðž­Lrõ\9‰î(ÓiôZ­£¨Pò–bµ/.»ôÝD†á|‚PyÍMÍßRBa§[»Î§gKÂ÷­zþîÝsÕœ)èãç|Ó*Wp$D· ÚËh_¸òº³“§Ÿ^%(Ötjг'§+1ç/9Ã2ðçiÏ(!΢ý÷ÝM½šÚ+%í­$¦yVN@3Mÿ(ÐzŽv =ÚM P~â&þ?šj¯º±õ·_u¼óé|H4¿˜ErVßîÐl;»vr+±ö)å±îÑÝŸËnQ.(A³Áñ“4®½å‰e˯éÔÎ\=_s‚?䫜™r*4Šåß.‰ÀÚOrâ7²Ç¤X¤÷߬#ÞñÀ>€õ;+ö^4âÕþÖSÍß÷cjiGz2‰.+Îo÷æOÛZu==|?}H |Ïò’Që@¸æÔÚÚ ^|~1¢)b0:¤Ss8{õ4~}0øóìÆ¢;²”Ýœ®l(ÏÍ–$ãÛ^+¦>‰c´ó•‰î2GÙ´møì¶;0è€MßwiËØw¿°ù±«öÿ××* z²–ßµikòÄòç2» ßO9lùówfÉÄù¯_]»þÖåE|% í©»œ»j† æD=úûœmžö$à/º ]Ñ}yûÅù‡áÍ‘Èê7—JÀOïNcl¨Ð± ®ìs#ÚÝBÊ£ÓÑHf«Æ™™×~êë—ÞÒi7;ŽRÑj.+ΟoX•¹%QÎÚ×+äú÷XàÏÅú•€'¯Y[»áEK Ý6#ZÓ®»œ?9ƒ?æd2¼ÉñVs’¿ýw@qbËПûìVÓ c¢æZ¦ŒÊKäüvòq¬¡8 8¹#]O±Æ®›W|ŽTæÓ¦ç[ûÎÛZŸ¸~Ïù¿|úÙs,//àwÚÝqUZÊœ6¬rééÙS³"=ö Pu%uˆÓªNü¡põ5««7Þ²x!rx´ñk Wí!s‡¶ü§×”‡7Ý›k'’\™+&ñBûNë/à¯fßèjwP´l½‡‰*ŽtÆ»µ+D÷’v/¬= DÛ•¸?÷jy§cüöÊò žcáÂs¬®,¢ƒ6ž#T]•©ÑO£*¥=ž“«ìСâÊ#ÆÍ7.žüaÅ¡yÕ^ô¸Wjù“×…`6Å€¿(À¯"«ßZî~$ÊvwV‹S]žÃÅÏÌ÷£‡B×5çíV }Çd@_¼§Õ[Oòå7½Jþ['ìÖö$Žn½YøŠ qh£ÛËøkg gÑ­ó¨pO|**Œ;·)ï÷ßä¾×¡rüýè+ä†\9sN êž]ØߊÔ%ªìtG aWÑ­[oì,*1ŠŠÀ﹨«ö"ë‘åTÑ9(ܹÝ%~k8Ë]¡_tHÑ¡ÊØ,–åFC¥¹Ÿß>)šÎ†Õf±ùÏ‘ÔÿÙWñáox|QÓ}&ÃkßNry–Ãj8~HÚ/:-‹"!*B®ëà܃ÚDð÷D„èoùw’öt߸޿Õçþ&ÿš1Ãd’`ýBŸýÎtä9p»g€A×[’ütß×¹¨F#µ íÿð ü곬¸ŽE{ ,Õë½5QÂñÃÒ¾ýV•4íÁs¨\=‡šª`6þ²PgQ¬¿ ü;çOÚ+f.Ó€+ÙZÎ5Á*‡ó(Ö® ¢ÌÝ_÷l·Èïe/³XkL ’aÄññ®ØËC?ø2ù­Z%ïKÚÃÇn\•Ô÷„;DçÅ7±ß”H0ÄUTOÎâLz¿fãàÏÿþV_®æR\Ú<$»H¨’ßj|‡ôôUÖ5;1hùBˆ ‰2dížéQ’F>µW^ÇG¿õ´ü…¨lUgR ‘¿iÀÑC´_òBYp½ühƒ¸ŠÚÉYœ Zþ~•ùï qöŽÒæKÒ±¡q!^Ð>,µŽ«=›‰“<àøÇùTŽÚŽšùÝÐ’Èí ƒŽ"6÷7ñô¨É¦ÊMüžŽ?‘<‡4Þknä7:¡Ì?pŽS ç/j_päí—¼HC˜4ªuj'§q¦×þ¢ò†2g7d°Õß.Ú“†9—#?Jgb1Q„H‡ƒ¿çrIîÈñÿ2,ô‹ %NÉÈ£²Ó™àÞPgQáC–òØûˆà#éë.Ì ¸ŠÆ?9Í{¯ÞÏS"Yð§´'„+Ò~É‹dÁqbË/Æ ŽP;93üe5:öÕó—Û#?ñð·×"›QÁ¯âš ÖàïEðT9[¢£žåEAl)êñÒ»­;=&”G#A!;Ia TÅaáÛoä=‡f9]ð«ØòÞOû¥·Éy×EG±uc@AåÊÔô`Îo(Y¯žQ°Sðz«À/ÅꛋVäfðÇ™áa}…ˆÿ6J¾”Zïa³Ãë¦A;¥RðœÞ‡:s‚É“Zú¼ã[Ö $|42áòä?~æ‚1øƒÎKë¥/’3®KØ¿@åÄ île]¡Îü2ÆQëz¶3©• }â´®\ÑY¸ ‰úþ4/ ÐÃ4ÔSø>¼lÊHíDHúlʨPödóŠT­)€ÐAÃd…G¾î~gjœåfæçdí¥/äqÏÓшªWÎàì©nˆó—ÅúûÕôhú_Ò­P†Äê·–×Gy’}è0¿Ö eŒ Páq¦ø[4nÉÙn‹úïT)D÷Ofe}ÆŽþda#1²éOôìß:Úøè©*¾â>td?½ü÷U]Ö£p‡2TNîAí©õµüeœTðÕö0Äß›u)l«ŸÖ鬣ZÅè(RŽ <ýù–­À†ýî¶ÉvEÊx~?ËŸxö:v‘L\ÿbÛÌDaÂø9Y¢â ø'>Ï*Ó~å hášCø2k\Œ•ãó8³µŒå/ŠÂ’¯!ÁŸÿ~òšÜþó¿¿©W"¶ØÉ–dÛº.iñ±¸+³üVŸwÂ0áÑ¡d»Ã ƒ¨Ž½ÍX¯u\ ‘”ÄeÉC,Õ‰ãK¤ Rð·…Æ!ÀA¨"Ô@»ˆr¨\y gÏø@ð­ÇÍ÷ê,KpQ]ò[ù×›2ò¹8íÕ¨U¡Öë~r™LD{Fõ  Xa‚Ï[×™>ÏE{Ø´Úje„óŸUñVGæm}'™B“Y ˆ¿ãÄ ‘d£GˆBpã â2vâ6œ=S˜°<WXÎÌà^Ã.d±Ÿ7ý Ä'´­^=lü­Åxqûˆû1À$¿K§bþ?ê·7U¶s(¢>e'—ILo”UÿÓ͇˜x倱”ÀŽ*I¼Œ2YN(=ǾêÜþ¾+¹ŠÀowoXåïWÞ°i—8n’ú‘ÕÚqk#‰S üýêûÉ^~36kyöì7m”¶ZòqZð¹äu4Q抇,%£‰?Zu£b)F÷ÙÏco`zß 2ýiò€,³üýª:Gÿ¦Kü ¢:~³ ü€?¡PÍ…ò~Ÿƒ$J~ý܉Î`.Y'x˜Q²Óض˜øÝü’R¢$ûÛu‹%“#î*@ü€é€ŒqèøO2·ï«¢t½uös_/ZÌR¶tqXðoÚ¥—îê+¿Ñ£7|°B ÃeyËÄs¼‹ BH•ƒÇ~œ}ó¯ˆÖ§]r4ÃXþah¶öiÿF¿×#r<œ¡ß]|¢7 ø•7hýÑûü Ìñ¿1=fg˜ood”Je+  ìýN¬ˆ Ù>ƒNg€(3œ-G‘¢H«ßE¸1Qtèà±·±wþõ€¿,½6Èò÷ëÔœ÷#ÊÎ<ÿzX‰¾´cà·6‡êd~CE·0j\®¸­Ÿ`–ß Êtjýí³4 [BvªÔ¦=ö¶¢Œ`5“Fz¢ˆ@ˆF¥J|[Å3D4$à?pìÌÍ3aÎó*ŠÃ—9¼£‚?ŸÝµŸó¯‡éfo;ÍèöIAgóqŸÕ¨6hÃÎ3>süFœµÙêØ×вNp>±Uþ¢ï%Ïùœkv‡¼IY´‰‰R\Ô†ƒ1m ûý7öîÿÎB‡7‘a£=ë-jËÿæ(—>¥9AdéƒV6œ¹™ÀOcü‹ÐYÉþþzES¼ŸIîMÓšÃË–† ¶cD…ŠN.Ÿ.*¢Úýh›ÊüšÆ˜MÀü±w³ïÀwghOQÍMžþ”qþ~Åmý8ÿ¨àO@gLwuUàÇŽ-› úä7UäD77Æ÷óbðÙÇ{bë_düú%½¶t–Øn ÔÏú›’ÏB/¬¢}¨ÔöKÅ ÆDZ羋½û¿·4ÔYÄùSp—„:‡ÿ0s[‘$õ8&ŒÀ´" êøb›níÓŽF´³Ýy~CYâœh`–÷2Í]„¥…oe#µåi·¬ë7 ”£+”P¡tU˜é  Ù{ì1·ÿ{£NÄÖ·Ê,þ0qþQÞ"‹_x–èàëÔê¤:;èVHâ\ê6®ÝT_‚öñî´¤=+ƒÊ¶\6¢åëx‹Oh˜ÐW>qVäBöú‘C¬M­„¹#¿ÊÜühàïgù×Ó§3¦ék Ì:¶ô¡oYzô[…zº‰­ÎZT½Ñ(O‘„Àïešû {¸ÿŽD}ò²^’më=èa)QòœUƒAL€!döÈ»Ø{àuðç÷RìZ3°oÏ ÚSXÞ Öè˜(¦×Ú¤ôl;@ŸüP²æ·½'¶Øüß²¾³Ÿ_ÎÔÜè ¹}Ëd³)ÐF”ÀÞG"ƒ*ñ šé#oeïþ7ö€¿,Î?üÃX~ÈÑðDýȺøÉMŽ·Ê™-YG{-*ŒÛœðf±hà¿Àg 2ÖWXþDÖ£ƒF¬Ÿ“Û¯nPPÑJ2F£Å0yôìÝÿc¥+¹ŠÀŸ¾ü…ÝÚ$çÖ›®uã» ‡~¼R*9êí°òù‘RQô¨½9ºÉâ•­Ï0ϯÇQŸad[œÞ¼Œª›=bÂ?yÿ\óŸ<òóÌÅà/J­m˜öHü&Î$Ž«;±ñßI#Ø-Ü !)Ï×í™u„óoñcî?ŒÕß‘¤Ø( 0jmÿ0¼>¿ßaªÕ&¼•¹o†Àßcù¥ø&^ä®c Ç`Ãä:~dŽÚJaìî3n{5ò;eÜj }¼•Yî-p|‹dÇÀÃ+ÀÖÌmº˜ôÿ©#?ÇžCoÑ‘ì'²ÛÐXà&x £Æ A=Œo"‡º—û—.mØIÀ­uüÎZüä8·ã05Pã¯8Ì/£Fýv¼ vG`´ 006ýf¦g~aºôÄ~$@×1˜Ã0~ŽAîu>Þoˆ)P‚вÛ*Ò.…(Žï·"ß#‰*mïá¬r„7ÆÔ'¿Ø½LvT†U€üA;²£–½ÞO(õ&ª+oeõÿ‚ªƒñeŸ^JcWufÒåúEëÕølÆŽ2O®Id)á÷Ia'|Ž8È™áÞüýdGi-ëúEfÊ>·Þî.Œ7€#?Á$ob¾ f ¤ªR^K2œ^†ïØV–è‚m¶üvò,?Î!Ín„i'm Lónò‹úI¿œøŽH‘ŒbµËB›ö¾6r²öòFPò“LðKÝ£³8¸ŽAa¨€Ô¢g* ãû[h¾s[™Õ6™¿!‘.…1:Nžu"+´¬äÙ‚> TøÇøI’û)Ë®½-‰uXð–)A¾ôyT:Ô] fPò™ä-™¥/ÉÞÒ˜|® ; Û1¥qÀxуjô¬UW’™¡(Ã[þM·üØ“&V>ìÄywXú¼DWõ ŽñÏ©ÒèC}våO$?ltdóÊ %ï3‰åPòLÄà/ uf€+–µÃ—a3¢?ÆÚ¼èaÜø;ñw“LmâTÛ¿eÿöÐbGa,;`èÁ…ø:Œ_ïpòlÑ49Æ¿`š‡ÿ®—Á†‘a¨=ƒ” Yí"òcLðöÌ üùlnêà&ûq,?ùžãDJ6 pAÜ(ƒ*ÊŠåž =žœ–¢ ㌠Ȏžu@Oo·Þá ?È>>5ü›§ 'p‰ª€7aˆ¶" j[yU°=yÝÿ4£eŽ?Â$ïXø-®P¬©cìƒñ hݰ:YŽhz¤Æ:@c¨‰*6ºïçÎv»âô›%!°‡Ÿæ0ÿoA¡[râöóÆ$™¬h4àÇáŽàÌÒh»ùOßÑ»MÌÄ6ke¸yS©rÛ°ÍÿF÷»Q—‡ÔüwÎæJËJy¿uˆ£Ø3)ýãâ–¨Äù9Æ›¬†c‰lðQ CÃcÏÂß<¿õW†/=ž+¶%ƒÄ|ɦ@°ñ¨MÐE>A7¬Ù{Êç^ϤüêæZ~Š£?e±þÌ![Û.!,,!0˯qœ7€cÀw‰@¬,À€ÿù øÃÏÖÚ[sJ[Ý%‘|Í€½ÚËŽö´Pò:&xOy´‡,ø3…lC‚?ß­yÇãüD–ÿW8Æ¿ » Û~^âˆuÒäà¡ÇàÏÿ¾ü4üÁç OœßúÓ²g€ÍºÌeS¢­E¯5޼žqÞÕ7Úc+A êàñç’gÛê÷‹ó_VðÏ€ý£äU ŸºÛðéûáýŸ6,¬mï©Ù3@>n¿^)Š%µ½J»ï§Asˆ:?ÂhùˬþzîÉ•/p» þ¬D´ç¿pŒÿäÁ?üÅg€§Ÿ†?ù;øÝÏ>þ¥=½¼0ÚI•K>â“D„Š’m ðP,Ñ4¯ùU<æ{–æÕAàïÇù‹€—ÁoK4F†ƒ¼CüZ¼Í6ŽƒGÈ!²ôî{>ýÜõüŸ» çWwú#Ù ”HÞmL`g;ÂöoV0|š¦yFþW6«J(Í0à/£=EÑž¢#¾Jtþ‹\Áë9ÈãñêûªV£ç3ÏÀÇîƒ?½>üC;üÓÛ-ù`+.½M«’™À^·˜ÌÂ8ðyšæ_Ê;𸹧š3 .a¦ìyÚc(uæÏúù|HˆêW8Î?aŽ»ã«½´Ø!Ø`€Z-XjÂÞ ÷? ¿‡á™Å>¡þ’Ÿ¶rKÞ‚„Ÿ¨"‹kìz3ŒäK!¶C Y…(²ÕU„shó´ù{Œü(©§à·_ä Jp]vx³]‰~–ãü<ãñxxÑŠÊÿû´Bøø}°w:~ñ#†gwúÀ7.IŒ¦¶^g=’Ÿ tÁß ¼+qžñ]æ.Úü"mNZt=àù2øË¥A“‡/¸üPkš}ª.<½ïÿKøÂ#†¯<»Ó¹u²YÅp£HQ„(ï¢&®n”ôø4ã|>?Â߸=Mûþ2ø{%нñ—ð?ßôióæ×8·Ö†µ8*Zý|1IºÑõäÑ/FÒC7©Q$ÉýÚ“ÁŒ -SNí#R†äÞ^’æ ÀÁàÆßÒ@‡e¾‰³ü nì!TvéDè/ƒ*ÐhñÀ;>ÏÏýÜ_›ßò£ë3¨}É ÑÚõ£YT $æ2E’£)€ _y~ûozßšƒ—]§Ž@mPÐY†{Ÿ€©:\sLR'±ùB~€´T"J)”Ž€ÒEÓ´ØS=Ïë«+ü&¾]†^++ë{¾ƒ?šïýß¿ßy÷/ü­ûK_|Ö<í:Œ)‰À!I:­5Æ#"Ék“<[{íC+åýAÅp£S JþŸý£òó¹ê¼øJ¡VÏ}%ªæ«¸pÓQ˜¨Â‰}¹#Su˜1&jÆLÖ„}“FW£ÏW=1Æu¨iX1Èžir÷­5~vÌ3§RµµïV:;1ü;(ñíÂïyZ>óÖÏ9ïøÐê“Æ˜ZÕãŠx4´Dè7"b£”2Éß1êÅH?“(H ,mŒAk~.mëûP´e[¯NY1Üp¡ÀÐúèÁgáÁg³»ëÈçŽ^òﻫMê#õ Òh‹LÔ}SŽ¯ŠªUDUDU=TÅÅQbª¡–qßðñ ÇÜõªúµßy¥þ‘±*‡SÊõ|y‘ÄÀø9|ÛçÔû>x¿úÔj›–ëéëcR g>ŠäC Ý1ñ{Æ-":V h¥”q'ý| üô;ZkmŒ1ZëDA’߆,™Ý¶¹ZÌdš.²ƒŒýX #ð¿ ½{¨cìYV%"JDD)¥¢ÿ•c”‰.ò\GPžƒ£Ä8®ÂqcPJpƒ 4ã“æÐÿsþæï»^ïÔóßãù&qlï™EÎþòäàKò™ç, ¨»‚˜dñglÉKK@œ¯ÆJÞ+»alæ9ù\¼¯0Q"@[bŒ1ÚžuØ$…ưë&ûÿPÞô~øÅ<ž4I®bqG‰HääÆcŒk ÉÚDp 8bŒ«ˆ«Ä8"¸’|ã$¯Û!Úé\7kÿø‹Ìk^{­ùÚé1¦3ù‚KY’¥‚¾üϾçnþòƒ÷óÅg×XªJ‰]‹e_ßÂ8Z¬E–Œ$`& þ0{²=‘Г¼—ÚôYDìíQ¿>­ÃX)B­õ –R¦“Ö¹(ì¶ÿ”áÞ'ú^å8ŽS©T\q# /~Tè.xóŒ1IÍ  ¸Æ·ûGĸ±Cl+ŠžÂ :!CxÕ¬™ÿÁÓ¼ô[¯â–kæ8ˆC”E¾Ô!¦9a›ð®³<ýî/ò·¼Ÿ/5Ú€'’‰îô{6%÷(ˆE‘2JaÍ=í†cpÛÀ¬m>à‹ˆO7×ß‘@)Õßø¾AÏ -e 0ž;èÁ;¯Ãï~¾ûÝ}_ªZ­º®ëVµÖU­u¨cjƘ*Qµ+‚­¶dÀn@‰1‚’n9µÿmõ k.µ¯?Ɖ7ÜÄm¯:ÁÕª—['ª~1JN;ÏŸ>Êÿù%ó¥Ï>ÅÓD×ÑNpæÅ ùwϳE™’í)”EÒÙ€î ‘(B|Ÿø -"i-¥TKDZƘN«ÕòãÙd$%(‹å&Ò7ìõ_K&€Äà¯i­'´ÖƘIc̸1¦nŒ©+Yà;ðUÁC ˆÑÆ®?ÄzYyÖ 0}ˆÇ>úOÜ~Cßzµ\ýòÃ~ùaŽQE.e°@¿¸BãÓOòÄÇá¡?xÀ¤ã©péI‚‡žmɨæÂµ›*v_ ä‡W"û3þWð©çãÊq§^¯WÃ0œ0ÆÌj­g­u²Ý< *z@±‚Œ¢4ýš¸Ú¹goñ™*Õ ïš=2sx‚ñ¹µ›æ™Ÿ®R¬PÙWg,ÐG¡ên3$ðªà7|üvHøð"‹«>þC‹,<ºÌòÓ«¦ñä*«Ëm:Ï®Ñ0NŽ-!A;-£€®ŸCÝó°¢Iù©Ÿ8ÂD>Àªˆ,ŠÈ‚RjQ)µÜétZ¾ï”í)‹mlx\øÀ_Á‡þ>òÅôP•JÅó<¯¦µà‰2'˜ÈN¬²cŒÉ(¹h1¦Ÿb”)É *U&v€À~­å*Ô„‡Œ¤êà´CBO¡Za;$ F›L 6ìÊ:Žô@†-ˆ+šÿzœ` ’jX ´˜ó'¹€4DÖ nˆÈŠˆ¬Æq³ÕjùŒè­mŒƒ^…wþ¬ðÉ{áÓ÷œJ¥âzžWÑZ§áÏüqø³( ZJfJD’¢0 š{ ÑŒåÊÑo¶£ì{ÛQr>JEç0Qä9¡)eŽlO´'.•ÐEI±øï4òC7 ê‹H;ž:"ÒŠmii­;­V+É'lJtã²Ê÷`&?õyt§ÓñÃ0 =ÏóãIa±ƒãžˆäg$F‰Œ1N’A¶2É e* ›¦Ê+M¥4ƒ”*H¼Ï¼D?EìAß´d— *3‰ßTÿ:j¬¨>¨È¢÷$°çâþ)øé*@;¾‰$TÈ’DXÁðÕÊCÈæu†ÓDþ¹†¯È ¦„ahÂ0Ô"â;‘¤õþŽã8ƘŒ¥‘¸4Â8"â&+¥”EÒ\¥ 𘥑Ì,‘ó3†ñ9zþ¶@žÉAHŒÒøýŒ_U¢4…b9Ùm?PÛ ’©ÆÌ)EßL/ÙRˆ Ó ú¢šŸn;bëocL>––B(¥ìÏ…ZëУƒ Ðù‚½Í‚í–´Ft³®[ªÆ‚ ÐA@ :¥”ÄŒõŽÿNã1x" ;1x“¿‹>«¬ÏØÊ Öçlàç•#¸ÄÇ.…®Š” ’Ï[ï§3A2ÄE…Sr"VT$u>RÒýYS)éáâv3½ ï±î¹¸BkoQœ¤™’ˆ¼‚ˆH&ÜŽŠD³q¹óȌǦau3w–^Õ-{'ó,DU€h­ -«RQ±(]©d[xcæéOþ3Êú¾XŠïXG²Ïø÷“ã€ÞÙÀp&ûM^'À•0-qi^)…U{Ÿ¼_”¸,NÆÒ+¥òN}ªÖla—=§ÏXüžHleÉ+”6‘¤¼H ÎaËß3Æ›âgö¾ÀOÿ.üÒàJÑÜ7ûnKgÑ ‰-£J¨FæD°¬¸½KYR°ÚÖž€lÏûb›ý~T|ƒ7^8Òw€x9\œï‰ÏÓŽ—kù˜9äÀ_âÀæó1ùéžÏZuü)xãHÇçi ZQFw¶É¯‹þÞTÙº(P^â×ïûsøáß0´6Þl” dÏvÛj[²­9DåÚ©R%4ÅÞ ô˜’€9·tÿ6øµÖ„ah~ð‰‹à8Žãd”ÀXK-à{æ=zJ -K—>&€Ö˜œƒmƒ=Ù”;Ô²T }>·­²} @|ú5øü—á_½w`¶x3u½Ÿæ»¶ö¤m§6i23¥`Äùþp¼¿ä‘ì+æËöGÓY"¿}ÀO˜·¯÷s;&Eãÿÿ÷¦e£ì0œ%tEXtdate:create2016-10-22T17:14:52+02:00cÄÿ%tEXtdate:modify2016-10-21T19:32:38+02:00ö[NIEND®B`‚Slic3r-version_1.39.1/resources/icons/Slic3r_192px_transparent.png000066400000000000000000000512051324354444700251040ustar00rootroot00000000000000‰PNG  IHDRÀÀRÜlgAMA± üa cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFbKGDÿÿÿ ½§“ pHYs  šœQ‡IDATxÚí½iŒ,KvöˆÈÌZzïÛ}—¾}·÷î»÷¾ufÈ!=$‡")’ÇÆ0C°!‹[ eÊ(þ¡?þaØ? H–MB‹ Ð&@K²I #ÒcrÀ!Gœ!gÞ›áÌ[æ-wßúöZU™qü#"²"³²ª«»«·÷Þª«:«*+—ïœøÎ‰sNÿ©ºáκ€€ÀÚ¶À—ߎðhKâáºÄßTx°!°•*Ì45˜ Bôz=äy!¶··Ñjµ$ Œ1`f(¥Ì "BžçȲ QA­5ˆišZ­V±¨=¥”Èó¼Ø!`Œ)~?Žc03²,ƒD)%Œ1”¦©BH)¥ ˆHåyi­I)Œ1Ä̤”ÊŒ1dŒa)e™ ˲Œ£(ʤ”š™ÁÌRçæö)¥½´îÎáçüñi­ÑëõÐl6Ñív‹óSJ•öÇÌBòÜ Ís|úªÀ¯þÜÌKÌ„qñ÷¶Cu´¨ýXj„±{L1s+Š¢v£Ñh:RJ’RBQŠ™Zkäyc Œ1ÐZûm¬µæ8Ž@ÊÌ;z¶l¹×¬¢|$åc8z!€)-³Fcjvv6I’D4 $I¥TöpT _{ñŠà­;3ûQ†ºÝ.u»]dYÖìv»Í^¯­5Œ1 "#„èx «]÷œàã¾PG!+Àá ¹GÀ €¹f³9×jµ’f³‰v»F£Qº1v½ñŠEZ­V±]kN§ƒt:loo‹^¯×dæOû`•à1€ ô•bÒDåÄÈÇ p8âÍt›™ÏXh4S333bzzív»ð3¼ßáA? G„ÒA¡Ýncjj evvv°±±ÍÍMôz=h!. !$‰aæ5<Á‡pdøX&+žÞœ1Æ, !¦äÜܦ¦¦E<ý¨¥:šH)1;;‹ÙÙYdY†ÍÍM¬­­a{{yžCJ)ˆhÀbE=cÌ3ßð vT8õÊð±\¼µŸpŽ™ÏÄqœÌÎÎbaa­V«ˆŠèG 3Ç$¥Ä°µµ…µµ5<{ö¬ˆnH„K–¬x;*d8ÅŠð±ì_<·Ÿ'¢‹Bˆ¹(ŠhaagΜ)ª“¤6‡)^ˆSSS˜žžÆòò2?~Œ§OŸ†ŠóRÊùf³™ !¸ ƒS¨+ÀÞ…cÌB’$¥”³RJ,--aqq±~žçÇ}œû’pTˆã+++8sæL¡~þˆ(fæ•f³y.MÓ÷1`C«§F>V€½‰0¯”ºLD3D„ÙÙYœ={ívyžŸ8šsñ£WǸxñ"æææðàÁlll”&¸È$I®c.c>p§„}¬ã‰0%„¸¢”Z0Æ ÕjáìÙ³˜`#*VñT®ÕjáÚµkX[[ÃJ3Àn¶;’R^cæ Æ˜;è+‰•`´3'RÊ‹J© Ì,˜KKK8{ö,¢(:µTg?â&ϰ¸¸ˆééiÜ»wOž<±ªwî.^¼ˆ8Ž@̈Ј€fÄ ˜m0¾u/ZøKÿÛ™WÿÏßo/nkrè*¡ÈGZˆHDQt¶Ùl¾,¥œìl4¸té‹Üz ²N03«$I.6›Í+>ILk$I°²²‚ÙÙY¤iZ€½ÝncqqQIc¾Šë£.Dö! ħXXœƒwï݆É2H!Üûö³q“ÑNÿÓïÌ^ëå"ú?¾qg¾ÁÚ•æ s|'î $€ˆ!ÄU":çîiÏ… 077‡4M‘ç9’$Ák¯½†û±Ã7Eîܹƒßþí߯ïþîïbkk A.ýGFˆ\Nˆ³ÙR8SM–Vè<Ãâââˆpïîmè,C$ ‚!œ"( kü³¯L¯>ÚRÿæskß=3£S¡ °`÷€?´(ÐGíÎ37£(z.I’ŰðDÅÅEÌÎÎBk,Ë$ ~ê§~ ?ó3?ƒéééb'çÏŸÇ•+We¾ð…/@k]ÔÓ~˜%8‰²ø×䬼 Àè ó ¤ñðî€5¤$WÃí?qNã‹o%Kæÿ^?ÿgÖÞ\×=˜ÐŠ|”Æo‚MY¾ ~"ÂÜÜæççADE¨ó³Ÿýìø½œ9s?øƒ?ˆ™™™}h´µ.à ßóï+( Ä`bùÌ".^¼€$ˆ#Q°¨ÿ|~†ñÕÛÉüwÆ-Öÿã`BB¦¼«úàŸššÂüü|Ñ¡!MS\»v ŸÿüçkÁïeqq333E:À‡I¼µ/ €iÝQ»úf£ÑÀôôtQ­åÓ}_|ñEܸqcäŽ}„È+Ò^kwOšP𢂩€?¤:þY#Dn7b(ÒX9õ°½ùRZ§˜hCú?ûõwò[ #îìo(v S$pˆÚ!>™I’`zzq#ŽcÖn6›¸téR¡ÃäÑ£GX[[;õN0 ýÇ¼àø¢28‹/œÕ—! rJ  …`Ä’±ráÜéBg0Œ!óŸ~ßú{?ùêÖd$÷?2KrºïÜ.bm!p½cÚTÀ¯”ÂÔÔ”RE _ .¥D’$#÷½¹¹‰¯~õ«X__G»Ý>µÖ¿üÈQ¦;åp§}–²~)Ü6§á{Š"¸cåâ Ý}YžéÏ¿ºqûßmë4á¨Á|HÀÞ8nn§òÚf*gf› ?wå#>ív»°ú¡¥—R¢ÛíâÞ½{0ÆÔÆùó<Ç—¿üe|éK_‚oVu€^T@²£Kè\„ —àJÚ÷•t¯E_ TáähÌÎ"ë-êX~ëöŸûäæ{0ø\>tQ "@3'wž©+¶ÔÂL£~/F­V« ”R€¥”HÓ_ûÚ×ðÍo~sؽ^_þò—ñK¿ôKxüøq±ŸÓ&Uð¹5|ß?B`+"iqðH¢rt§ðu$@0æs·6oÿ¹Ol…à?rë|ÈF•P_y7Yý—_k½¸”—Àïßn·Ký+«l’$xë­·ðË¿üËø™Ÿù¬®®666ðÅ/~¿ök¿†`zzútƒ¿æú•"<«/C^ïhŽy* DákÙçÿªˆû "ý=soÝýSßzÌ“^“:µ=íëC¡T„Þ˜~ÿÝøü¿ü£æÅ‹KzüžúDQTt9ðÍ\C ! ”ÂW¾ò|ç;ßÁåË—!„ÀÝ»wqÿþ}!033súÁ_ sŠa\?˜¸RPÊ€HÙm~4ˆ•DHü6÷æÏœó»ÖÞOü{–S­þŒwR‚è÷¿/üÓ/·¯,ÍšZpÆqŒf³Y¼ç^'B4ÂÙ¬¯Ðl6Omȳü태¿mÊ`‚Kô­»’Vâø±,6 NÀ@˜W§ß¾ûýgß| Ó$CûN˜;Õ  „Mbû½7#üÞ;©ß{'¹F²¡ºõ‘R¢ÙlÑÀ‚Ü·ð&q¡Óz/µàù>ú#@Éò;Kï©Lhéc§á¶p°þ‚„!2/M½sïûÎ|û椀8å ØI$·ŸÉËßz Úç òšlå$IÐh4¨N]·åªœfàCBC&·ŠÐf¨¢ ìHZ'7ò#€ê+Dü–öÜœ~ëÞ÷.~û-`¨å߯B8UúT+€3ľŸýÍ×[gVòð{Ç×S—Z.m´§ÎÙ¥`R‹úÑž*ðci}€XI@¼(I}Ë2·ÚoßÿÄüwÞÁ ø¬ð}”œZ`v2З¿ÏþâïL¯.Í /WŒ¢¨Èóù( Õü_übR+´ú!åQËï@ŸÖß~Æß9¼×›ß½ÿÉ…?y€A{ö{SÂl}'ÍN  Û#üæ7Éÿø[Ó—ŠÊ”x?ÐçþFã#eýGÎîÒ`&g|Qk†NnÐ8P€ø±¤Ð,ÍÅøÝ¯Î}«ü“°ü«; €˜¾õ@ùý·’¹•3ùø½DQ4Àý?2R—Í9$Ì©*a˨Bo…@ý°§"0KsA½ûðSsß|G‘10Dî ª`=è ™H©ä©S¨gÀðÝdæýÖôêüt=õñqÿ$IŠ…í> RÍèô–¡õ‡íÿâܪbù£€Ó{À‡t')” ¿`fY¾÷èS³ß|+&“#uàöw.#N±nÙ—B®T±ÿá[qüÿÍôÊ“m‘L5ÃŒ»Rj¤õ÷ Ɖ©KgFüáÄ–”º#û–½HeP6…Áo³é „DQA‰”é2Kâö£OÍ|ó͘MŽrØX“k 7ñuªFÃà­TÐëw£ùßx£¹|~NC×Ðzø(Šü’£µûóùü»¥=Ÿ©F{lI=ßiOhù‹œ5œöÄŠ‚ˆsxIððèµæoƹΑ“ƒ!aן¬=ý™¨œž€@iFøÜhüÂÿÛ^i%Æv "ã¤4ûÎf§]¨òZ`0­!tvUå‘>ºã©MdITIlS@âÁ_(€HšEsûÑ'ã×ßl˜y5ãÍ(7ÆvøqGãí»AÈÀ…¬`bí>9þ©V#?¡HNò`OXÝ.áW¾ÒlþÊ´WÚŒÝ˜Š”Qíjýý‘¦)ÆqŸï/ Jïп¯Ò ¢>QÅÙM*´Ç&¹Qÿ3í1BòB÷ÎãWÒ×ߊ‘å$ÃK`0´? ô;;x‹O°#‚ƸȫÿDyêIŠ6¼ÞôtGÎ?Ü”f2žŒKi¼#|ZÚVCàCÁ_¤$Wª·T%Ü#‚çûø¥$°<×½ÿìåîëï4M¦RŽæ¾}ÎÑ_R[‚ @Îf)€ƒMËL¬_ÐIÒŒðo¿•4þÅ×›ç¦[fä){gÖçøëÔj­ÑëõŠá“(à Y°øEÕ—e‹ï!ŽìÌn8I@‚Šç:wÖ^é¾ñv›2ËùÃÞ=ÆyeU"œCL@ìÊab¤C<Ìñh³¬“fòJÃ3¸›ÝY“³ß¾§¦fÆ´þ{²çþ½^ïÄŽ# YÂ0'Ê|JÃ@| ¼éÿ/[þHØ”fŠ;·×^Ýyã»mNs0E¶ð–aG»†“Í?¾­ff[»[`ü"—ÒO¹Ïw»ÝÒÒŸ'AJÉlR¶ˆ²Õ—Á×øe¥/gþ¸~‚‘ Ýû[¯l¿þAÃ8ðóˆ¨ #‚?xr>Bß9àFÿÝNýP¨—“¤%1 ~÷‘’_øVcÑ’v?o"*`¯ܧKïìì”VC?NUÌæDÒDz Ú#Ê–¿÷ÁNp©Íʦ4³PXÈïm¿²óú–Ns€”¸ƒ` êCRø¸¿ýßSŸdPVŸ'Dõ§#hŽ{üwyð¤Mø?‰“ßy+žŸmíÞ5)ìës ževvv ‡úØ/ȇ·ÎÙõ#@-ÝQ#R‚ð¨U|Eº×{Eýa= ¦ß–^T <2„ ô}„Êz娹¥•çêë‰ÈIP€ýO3âžJÚ艙ïܦ¦Æp~kÅ2£ëßét°³³sl Pgù}§eoõýWÑ–\Öp~7Á9®ß(q(h'A0PXŽîg¯ÄßXkÈ”AdY:—/Ší±ì¿ü¿wúÜ °ý•?Q3xêÁ·ê^OLNžLÀf‡ðëÔß¼¯f¥ÿ̪~ÌŒííí]ó‰éôKZ¨R cµ¿èsøR¨³n¢+px}b f‰sÍ{úVó8K ‚"Hy \ãÜ[Kf8€¼?t.\áp*Ì¿oÿ7 ·_+9&1:¶÷PçHaqÚ๥<ù7šó³Í£/côK¢nmm!˲# ªáÎð¢?Ù%0X¾:°ø£JÈ3¿ýà?Ûz o;Þã.,øYB²€,Öó­Rë±ÊQúŽ.P{¨B=’Åñ¼§PÝ–á_¿Ñ”_y/š‘ÒwÏÛÃN'¨^ 666ŠI²ÃP„ºô†"¶Ï}Þ/e¹+s$l”'ìÏGå8)Ú#û­ ¥£=†#œoß37^Ï#Ù˜`qSaé¥Ì]žG3I Yð%7ç듟ûyú\¬HÀMˆ•?í% ‹Ê¡ŽÇ¥õ(b`~Úà3Ïõ¢/¾™ÌL%ÇÛÅ¡ª‡‘5ZÚc M¡}ð« Î_êÌ\ïûÌÎðGa´‡%–[Íù׳FÔ±¿ÎLˆš /71½¢0w¡‰¹ hÅ û™]3ªÑoùeËFömD(¤Ic]¢IËq)À*’qC6>X“SÍx|úô;I§Õï3Ë2¬¯¯e“P„jxSø¨¢> u‘U±ü¡Å—Cª¸„¿ÂÙöCskñëyuãÀ¯„¹´ÎIȈ2`v¹‰¹•4¢’ËÔ‡ ðöGƒòÕN|y0¶Shñ™Á[÷¡êg èfį¿Ñ[e3Í ò„x(~‚-Ïslll Óéu¹¥a?‚LÏ çFDz’رË`†7l^+lb›f…¥öCssñy#êØŽmÌ€læ^ŠÐº Hb’˜[ncîÂbArÿ>–ù¨ ƒaOq!úaÔ¡—ê0ää„A Øé~óë ùµÛÑÔ ˜ˆßXw}}Y–¡Ýna×½Œ:azCõy(øçZÿ$rŽ®s‚#ÙóËÀò3Kœi=曋¯çIÔñ–ß‚æ¦Bó‚k€= %€ˆ3禑åúÞCc*à…{_¼„®pßÝžPq´÷ôx>f ~üV/úÒÛÉìTr“ÑЧD;;;X__G¯×€±Fƒ’© n|©€]‚_t§Ôº$äûç'ÄQ†7rŠ#Á°Âbû±yqùëY#Þ¶Gà ¨˜¹¥Ð¸Ø?) ˆ k`fe­3sd¿’žðï° €Ê' Æ© ;459Q#$ðxK&¶dkº±7þ¤‡¤Pk­Ñh4Ðl6GÖ!PÍ?Ë÷þìn©oOçÔzË_(A0ëËòìnäBJ†#œi?6·–_Ï›ñ6#'‚a@%„©›Ò‚ß8Ë_9V?ùK‰¦.."ëuÑYß@±é{\³}ðQ{‘ŽFŽzzŠyž $€õŽà_ýƒ}÷‰ˆsMEDd¯rÔyüÞAöQ¢ª“LTFq¥YWÔR¥eI¥'¿ö„½{âH$•¬¾6·Gs„3SOÌ‹¾‘·š[ ®kphßT}ð×…œÙâ;ÌÝQ°‘¡dªÖÙ3RØøm…•}*½*5 `@ F¤H~ë^Ô>Hôç¸ Y<Ø»Ýn¡½^Œ>Æ_P Àò‹üÕ8þËïzt“\¾€FGX˜~Ê7W¾ž·Ú›ìÒxDBhÝPh^¶Ñžü¥¨ Û÷Âk*àJÏ2 ¹ê¢µ>f¼Fv—„5Fgèî¤ÈS‰Z9ÞÞíì” ˜K£øPŽ©3åñ+áõ"¤9„íGÍC t"Ÿnf×Ï‘YîOnÅu†ÉSd&a„QV¤‚"X $±Sß¶¤ð¸èñ)ˆ`òósÏø…¯gSsël§`9ðãZüu}ª,µÖÉ[ÿª© jù,øƒ÷@FPŸ2ý¡Ò q}MÀÐËŒ ŽG¡»žZ®{‚lJO8À)c`Œ9òf·E:ƒûSÜóüåJ.%‘ D ™&K¡ È!aXÚ~ƒRA)EÊ~Y"% ’t&1?÷Œo¾ü|zî£GÖ2SDàë |u|ðwÆØô ~þ£: Ø»ˆ3‹à'@›Q ØÃ¢rǼóS‚1ä†ðÁš¢'ÛBî5ÿ¿*ÆdY†(ŠŽlY¤jFLAá2;Ñ_ŽHqúÈ5œÝŠ+vv—+ a¤°-—Y‚°k·ÆYž`na“o¼òz>½ø, = HŸSÐW¥=@çífùý6F÷£@uÎp©Ï Ðâˆí-«Aî„ NþóÅ÷ö æ‰(Áñ+l˜9Õ ­YÑiËQ/zQâüT1t cü¾-yÝ’D‰"먈ùÇŠ¡(³u%àœ¡µ@–GXXzÂ7^yOÏ,´ ¤×²ç¤=’!:Úc`)OÖ³CsØÑ­: Œ9À‹‹ÀÚcˆõõÒäX](¤@ÁeÄxÀþŒ2~÷‘½œâHîðŽpžçE²âaú/ËOýÔŸÛ_Zq½®IUÔÖßæÛú3½D“JÌ.tøÅOWϯltn(ºÏ)ôø)]N~A{Ðn¬hgý«À¦dŽ$ÌùÈÎDš©léwŸø»Â>;T{`,4kCx¼-°Þ$ÅÁG¿èÅ¡žXM´'Ìñ Á_ZqÝQŸøq¥~·Èî$ þJb›¿Âüb‡_þÌûzáâ–AîÂ$kÝàÿ/…<ýƒ€<í[ÿaÀר‡e€™¦g RI\¾h@BƵ€®æ¸ÎˆLÄsÕZê¢ÃÀ_TpUsøƒ\~UªÓ-'·yë߈¨´(u8Ãk2…¹Å¿ô™÷ôâ¥u—ÕiR„ík;»€í) äý ­µ¹m¹¤±¼oÈ΀”°þúܰ.⳿Û4‘›{"€íÌ£M›ÀicÐívåX«àU‰JV§çû‘[a¥Ú¹QÉçO¢>åñ)Îýí&—˜[èðË?ðž^¸´ÁHÉ]<l='°ý‚­& !>Юà'úL³²Å¯F~ªÏÕRFÒ€ž7ðåó"¸lÂ=ŠÀ£åD(€E¾Kf>àÀú“ngRux=·§àWüQÇXü¦{$ hø 0Wêè¿+!Ï$fç;üâ÷½¯.®ÛP§aغ"°Z~w ÕÈÎn–ß0ÐëZ׃?|h þF!`)Ï/`ßy½cɉ÷Æ; &2Lë_ûkRMnÀï·UòùK àÁ$µ–¾Ò«³Q©Î×WÁôfç:üâ÷¿¯Ï\~fÓ Ûëæ­ëƒ–¯´‡öœó‹á¡Ïû8ÐáÓ@¶xÜhÖ¦;”­ÿ~Iчcˆ$èêmÎN›,›P*1¦(W<¨Ôr~?×4Ìò;Hdßé-ó|kùí)8¿{("èTaz¶Ë/~ú}æ’Ko0Ìø7^`åÀX~/cY~²œ?ݱÖ߃X´§ÚÁ¡6óĺ#›™qÀ/§@œäîaŒ­Å‚íØ æ 8~ɲìÀ£@-ø¹ÏÿÃTfYÃùCð×YþÄÑž$¿rÔJg ³s;üÒ§ßË—VŸigùm¬`óªÀæ ¥öèªtd,ðþ»d9 ©>Þïÿ¯s~Q󛀴Èç%måÏomݯHSÆ>8Ä”ÐËah¥Ãįú²_©Òžp–wWίúœ`ýÝfÈû#*ßG{$&S˜™îð­O}/­>3ÈÈ]Vææ‰õ9ÿž€üHSÇýM=õ©.n1î¦ȧf‘7[¥íéϞoÑ¿h °§Â…H2Vç´97mR=¡DN? Öëõöå ‡¹=á ïnÑžpÙÑ"‹3®8»Qßò€_Z+ÌÌôøÅO¾Ÿ-]p–_3À‚Ëœ?µY¿®†+ë,¿Ö@oËÆýë8¿©ì§ú;Øm› ¤ó‹¶ {àÓy.äø™˜Ú ã‡oö°4­õ$(P(Y–íyÙ£a‰m»YþR £_tÚsýpâ+àüE›rþ<ÂT+å›/-­<ÓÈÐÌ`¶/KìÜTÖò;οøF€üºÛ6ôn«F€Â}Žú:Èfçí:ó:ö¾‡¥{:±fƒñý7R^š2Z ݛԸQ@k=žTf+‹¾à»€¿nE–FþRœ_¢¦]!`r…©Vo¾øAvöÂÓ)¹ç²D熲oÀù«RµØÃ8¿ÐÛÒN?¢úÝa^{ºÃ0Q&Šj&ÁŽÄÊ’ã€“Ó ç±4˜T=Kè w»Ý]áêBtE—æüE[òÐò‡=:ër{Š”rmÊûÖßïËä íVo½ø~zþâ“ yþîe‰îÍ2øk®á@x²Îòk² ÐëZêãó}r NxÕY÷}•.Jt~ÞÍ ûö±(ÃIQ"€¥Äî‹íCŒ1èt:{Zü.Ì× cüBІXþ0ÌWÛ(¨å-/l—k…v3å›/Üî;÷4CF@æÀŸ^VèyÚS™äê_¿Á•G?íÝüuÖ>¤>À þÎ8Ò[‚ò°ž’œ €µ²Ë3:o™4›`Ec8 t:ZT¬¼è_‡íÉ믆О0Å¡á›Ô†à¯›är£‰Ñ íFÊ7¯ßî?WX~—’|I!»©€ˆ€´þë,ÿ0ÚÃüÍr¼¿.Å!T¨ƒÞb C¦È>ž¤µ?оN†ØTZž1éÅy½ÝÍÇë‘1®øˆP§ÓX ~ ·ç­¾ôˆ2݉à—’Úª“\²Æòç ­FÊ·ž¿Ý»pþqMäÀÏЗôÈ‚¿2ÉUGuüëRüÞWr9 {ðç•É®º /`pT¸gãÞÒL¢§bØ8÷$‡íë0à{>(b>7£{Y&…fY†­­-xÎU›Ø&Úò~ÏýU?܆<« S”&»d=íi%ß¼v»wþÜãÔ.'Ê®WþÅ|3üýkTzm*¯KÔ‡Ë) ÝÐÙ¨Ÿé­Ž»Ü§=CŽ]yåvs~OÐ#“1SM£_]É;úzÄô—@í¢ÛíÙŽ~þ=ô-=PH{J+²FÜŸðJÂ…¨«ÛBð;ÚsáÂãÔ/ÚZ€[±íBОqÀ_L\“\šm¨³»e ]vKs¨þ^ݶ=‰° €èÅsÐ$'|wO$Úû ¢vÄüÚŬ)žh(´t`¬±½½<Ë!•">@™ó—Ò|+’øøK׫³Hm}¿!ךIÆ7Ÿ»Ý»¸ò(ƒ\f%ƒ.F·PÒ·üu£À_ôð¶ãpwÓ9¼CfyC?ì…Õ©-6"¶UÑ“½¥ùòq%í–¦t f} !ÍRloo[Ë/iüuIm>¿' ݨoù‘SéÚ—DèשsiiÏówÒ••ÇY1ñb%†¸•ð{©›„*¥+p¹„1íÝ ëãüÕD¶aÜ~à÷·8ïÙrÉ\FHe|’"AÇ­e!ÂÒ¼é]˜5;Ýl²Žpñ®%M·³ƒn§)¨ïìÖYþ!Ñž¤’ÙÙˆ}·¾Ó«T_‰L®Ðˆ5¿pývzñÒà ÀA¾”€š»ƒ¿âdî[}ÖÁín[¾Ÿ¥ø4œï›!¿ƒš×û­-ÿ',zqsÂ7öDR }9ÁãüþÄÍî“íÞäáÂá-šfƒíí-d©õ Î/û”% R™‹H§=ÁÃOrŪŸâàG)“I$qÎ/ܸ“]¼üÈ¢ÁÀõF_‰ o%Öá­äö +h)ßÑ ëZÊÓÛê;»ºbù‡¥7WãþÕ×û!ly¥Ñ}Hä2q¯'5 œj T9¢)eÌ÷]Ív˜È 1¥¥ˆÂ—FçØÞÞH‰~“ZQïìVÞp’+üŒïØfs{$’Xó 7îd«×f$0 è¼åüh ‚耺òC†?yfßÙz½r^Ϩ~>×Èë}ßOXúÞH­švŽ“!'Ç c‹@ÎNë4’ m6„Im¥fÅÝÉÓv¶7!È Š¨Ïû«é Áìn2ü¾óCaùs‰82üü‹wóÕçæDLÐdÁó x©üÕÔ/EL_Xj£µ-bélØ|þ\»íc‚噸I:³â&²x¢¸;‘h\áÊ0.ŸË;/]È6w@ƒêútåõw•°-³´ƒîö$Ùm¥HOÌîzî_¢=C²:3‰82¸þò½úÂÜ$Œ?ŸS0/޶üÕ(qjÌd£;½mëäv7mC€ãUqU÷]7(d;(-»D#&ìŸH t ­<¿¨Ó½Þ[Ûìío7‹wú“•nmÂ6œR‚Ñëm#ën!RTÌÞ6üDWduVÒ|{X~Iıáë/ßÉ/ß|Ki¼ågðY }+ÞÕòáNçܲ£:ÙÐ[·ÀO}ý®nñG•2Nšò„7Àä–þ„"ô²še Bö{ÂÚqw†«<ÃM)æO\Ì7IìݨZ~Oy< ’R||^$1òî&òaj¶e;2K\”.¤=à{‡7Ž4^xùž¹ôâ-Àd{ô³AoY!})O¶ü¾Òžaãçyj‹Õ³ÔFT´q4G ŸÔ–Î<,ü9Q@¾í¢?á 1@§#…B<,¹)ÀÂÈa)c¿ši@2bóê•tûêBÞy²-šSc.™Z*dAP–æãû5‰m…Ó«‘0н p4mÄ1!¦\Ïë*¹ü"üJ:˯×_½§/½ô@ 0ø½e…ÞK fŠŠî Å9QùÙ¸üð<µÏYfi­ ëG}v{ŒþIÃŒàÜÖ”¶Ã*³Îy-ó•*=JN$÷ÀxÈVº´œõ~òV÷ÉzW`¬:– í!¸ÎÌ¢Ò9ÈçD¹9m#ˆ•I7ÀùŠÑJD©OgXîX(€¤ϽrO_zùʹãüÙ’DçE=Ý)õ¥ß² ݰt'ݱ–ÔÇù]VÁÈfµ£W&øá.zÖqÎoxS„Ufm9â1Žæ#RY'†h&îåbïÒæ³Žó)ÍèwYdÁñ~¿ä/Wl&‰2àlÈ6‘(ƒf,úþ@M´‡s‰H<÷Ú]}åµûF*c-¿b ;#±õ²‚™#Ý?Xf€3Àt-ÐCÐg;.‚ÂŽß+9önù½:øÉwºSÿ~Ö[…^ÒìYÓØ¿@Æ+—³O]Ê6¿ú~4wfÊÔæ tn@¥„1°üaR[˜Øæ37›E>@C1D¾N ¢dI!’‘àÒd™Î”2xþµ{æÊ«Œ”H‰ ™ÑYظ%‘· ´m'„XϦÿlk '¶ŠŒN”¹þ8ïQÒžð–§î<«Ã6k@÷¼ŸCB†Ë‘(ÈqS áŸ3 ¥Y“ÿØ ÝgÝ´>-¢4ÉÖï Ôæó‡‹HûÂuŸÒÐ ’Ùš1ÐJl~¡wÀé:$zÖOPT€ßdRϽüÀ\~õvà·3¼Y'—%:DÐO€|È7| Èw€¼ ˜ÔZDOoÂ|ü»Sž:ð×]á•6ê“uêßÓ™S|RìsÄC8ìã>€¡¢‰ZÒðŸ}¹»¶º÷6{Tkír÷IPò|ßgu–r÷+=:CEhĘ ¹ “®ƒôŽKqй„R×_¹§¯¼vO+©Ì{†ðø’DWxÃÒ“[+ÈÎÒ“°¿)Tp ÿ8ÀU¿{Xà'Øóêm¡vÅy†›(Þ›`ÍßÁää*[˜¼´šíüäÞãg1Ðfµ:ÃëyQ¼"ËY‘¨/^)Uqùüžˆ\ÞÍñQœÂô6aÒMèÔ®ó{íŇúÊ«÷Œ’n].Fgšðä’D¯M> èÚ!°{x©rvŸÇ_ÿ8Ÿ*Ø«\‡I(HÚ¬Ó¼®%YËŸ—”톻#óN¢pqÃ4ÑtlÌOªód®it§Úõ;¨ß-¢=¢Òª0X”"ª´' ­‘ÜÙ.Í çÛ‘îìÀ¤ÏpíÆ}å•{Z c;5 ft¦_’HøÃûXååav¦†KgF=øG)A]”ç(í«§>év?æ\zŸÝ3á{G21Öáâ¾÷ï”@ðÉ«éög®ôÖ×vdÝ(*A¥oO± ¤2;¾_?šÎòûˆ &»ŒP‚ñü‹ôÕWîi%s +,øw¦_‘èMÙhÏ0ð»ó*?\‚´nAŠqBžÃ¬ü¡Z~²´¦·icÿÃÚµä_`¹©®ùÔ~äÀgwØNðÞ°ú M´Ø6ú?úD÷‘a gQšÙÚ#*+³ÈJ>ÿ.¼¿Y¬Ç„:Ý(ÂÚ¦L\i[?ÿ‰­<ŠØ-Gj€Îáé%‰Þô ø«çRþº¥ˆöúa<ÿPÁïþø\¤:ð“t×½ˆrGvðúàÏœ TN(çã'^î¬îÆÎÓ§ÛŒHIDJBJ*Op…Št…ôUÚÓt»&W¿£s~“ ®ßÚÖ×?±‘G’\«‰)ÂÓËÊ‚?ÀåéH©‚ £-]¾>×<øËûÓËý‡È&íqÅà«°Òÿ@ý&Nô°—ƒ¤Ï‡ßÒ$Î/èì¯výnºýT?z²‰,ë‚Àˆ"‰8’vvçüzÚ•fy+?î?'ŠŠÆµ–ß1®¿°¥¯¿²‘GŠ.0£;%ðìŠB:M5œ?|=d´ôUÎï·U¿“\¡as“z[#îhhý«~ÁÚLä,OÞÀÁàßa@0á3Ïëí?ÿJçñÖæ:ž­­ac} ;MäY‘‘D#’ˆÝ"su}ù›Ž÷ûç°c[8ë„?ðüõ-ýÂ+ëy¤˜Ýr¤Œ^›°uI!k׃¿Žó¼Ÿ+0Æ‹ú £#þqÜÃÒŽüÒF|º6¥aägá&Å‚Z`Œ 9Tmò^dbg{’F†¡.N•ä ©æ?ÿQ<úâ›4û‡Ü\™·9þ¶o„å9L0È¡ ¡RR!‰Íõ‰_){fK{ã¹Õ}ã…<Ž £GÖò›–€¾¬ÀÓdÕîþ’p=àw[Šh·0'p4–߃?ÛÁÈõOHÚ¸]J„!…üx&¸N¨¸®öfŽ Bzõ2:ÿó¸;Û„éå­‰VhÅö‘D‘H!Ío€Óupï ¸û¦·η ‘B †R³±½vaG¿p}=•zÎòë¦@~Yg„MÉü!…Ù øw+`?Òh»!Ýuþ‘wÐöímº´‡%‘ȇ(ó±LФ`Pê.’†PðÓ߃gr‡ýƒßâ³KÓ}g7Œî´âÀá€Dj(hcWñD* „Ì%zBáúèÏwt¢\bšâŠfDyÁ,Œaù1îÜÿ*_<Ê9Toù{øG„ÞIÚ°g6„"1€;nœ?rRF þ–·y ©P‘Ä0é³xüCÏQg«´’ Ì÷GV¡Tp[ÏKÂ@"CÞëBg;¸v~Óܺ¾©“(ï/DÍM: ø÷:«;l¢ë(m¤¯Gínùý4úó&¬ÿi`ƒeñÇ–q” 0îÌp_hèž9Ä•%ôþæàá±a§qå•'¹Â~‘$0˜Ï_楗' )È.DÝ ¨Ë b‚à5ñ5¬Š 8bÚ#¬[¢=£&]ÝÍêmÙè I‰0h¡ƒ$'eœüª‹v‡è üø‹XÿËß§™±oV¿áÀïéQõ£=‘ j0Ï_&óÊKÈ 1R·˜€h:ðÏ ÍØø«¥‹»…<‡%·õ ¯Ém¨sËØ„·l'Hˆ"²œÃÄóZOŠØ“£â$¼êÿ_½¸9¨Cÿô÷àñgŸ£N7ÚIH}È¥6—Ãø pí"Ì«/!o6¤l^Ñ Ä—äÜÁÀ_µöûù ¢m“‘÷¬å/Øv¿íõã¨þyc˜8֌Г¤ÖƇ¶~°i Üö>TrÐ…9tÿ⿇‡×–H§Æ[*’ßâÊDWþU˜×^¥¼Ù²dC ¹¬ `ùC‡‹Ï q«äª;÷Ãr UYè¬÷óúwíFàR»–úŒR  …u¨¢ r?g5ñBù“¥Àèáž‚1!è›Iôò9lþ…OâÑB‹˜AhÆ.Töû÷Hi“7Ù×.Á|â5Ê[-¸5¹ !¹,÷mùu øëݺ¨Ï¨(Ïaƒl×è¬Æ/Ç4Ö„Ò-K}v;H0…uØ…÷ ä‰_‰£ƒÒ®ï 7!fÿçàbyسëpÎð™C ï*Ö  ~ëmœ¬ìk¤[—Äàê*Ìk¯‘£=n×2&$WFƒ¿úØÍòŠïé94ð»j"“Yç5ëX#0nßÚ‚.mº“Ùå{ -„ ÇJ}ú‡ut2Þ• ‡G®„G9Pj`ßJÀ|zOsBô‡`¨€_—/:ËßtÑ@Ą貄˜6„7ÂòÐÇ-b)ºÂ¿uhwÂQ—¼g¹»—òøï {1»ë ø  Áv1 ¶?9”+rÜaƒ1í=}'œ7&04(’È¿ÿ"+ ñÆ=Lû/渼B擯!kµô`ý¢KjÁ?@á8àëx}vç¸y=GÝñ—ÐWq¥›6Êc|Fç¸àf†óîxßÓ¦ð$H‚ÛëÙÚÕ9Nûîç‚”ÁÏîÙ@$ Ù÷\Äc!A¯ßÁ”Ê++¤?õ*²vp–Ÿ!"‚º¤ Çÿ0ÎZù*ç¶ühø[¼Õש¥€o/¬¿› 3CZŸ K¶Ñ†Ÿ(ØËA U:îæ¸Õ‹bÇlÆ.Ùápn ôú4È#:j 4R6J ±ÚB´ÆN„ãJÅ÷Ïa‡q,¿²_ ~˜Ä¥&asñ³Û›?O‹Ë1ð°ŒPHKó¿'HNÂPgìÿžÌ”?þGÔSRœ5”L0iÓg¶7g íþq²:ÇÿÄÅÜïΦ}Ãݽ:¹ánÉ&ÅeÛû¿=<Æ,Ö8û{¨r Pø0êïG]ä)Ê`e·ª20¬!UŒ³«g1¦ öí—1øÃÆUãVpÕísâÖ?¾î9‹ß+Gw~¸‡î>h `ÐDg;8²HÑqÃNFnñÙ¢\¸ÁÕ=K¦0l eŒs«°¸< (Ô¨mùwsx«ûÏtà§À*螥;Y¯lñ²¯o´Úsà•Ý9J|äë  ‚Þ£ó{¤aÒ“@FŸx9Î;Èåùò˜b Ù@ˆçW/aai0d›³¿4*Ö?ÌòJwèÿúè[¸ŸÛ:·yÇ?Ï&| ?¢tÖ]77³ÿ}2€66ckàîœ09náP¶çá;öÞ”ÁßO“_š5„ˆpvõ—çÁÜÿ^hO]”g«_N­žÏ¸Bn]WºcÙzÂÀR6§§³îŠ`à?vRðô¤r/‡©û {•ãýþµOÚÎQ¶ûTQÍ9„ˆ°´z‹gÏüãTq… `09šãëquæZŒôçÉ® ïös{ö’T'@ÛˆðÔÕþž¸Ðg(Ç=„RMvó¯­ØõBþ_VÃH(œ½t Ëü¼ðãü£&¸êܺ[¸Ûmõ3¶D6Ë2stfœ¹ËŸðá(«ãímî?ÒS• À9Üà ÆÌ”û*жŒ²<â³õCÈË80r!"œY} ËK‹]Á¦)Œ uîÖ«Óè+P'Žâ°¶q{ÝsϹ=¸o‘'F$ü“ÆÈçG €6¶á1䞬ÿ±å¤(Ðî‹Üëâ³I…3—žÇÂÒ¸þ:7ο›ÃË»¼®ž%€tÞŽéSÝ´FQ‹PÌÜN˜>{Ÿ"Ûq}ý÷X°›h‹¸‹YôöpðÇšw p´">YN3Û›RX\±–ŸÍøàeùG¶T÷=¬ˆÅ¿ÌÚ&¦éÔ=rô—*4á;à©“Î]õ×ÎþrzF‰ÐÄð1Âü­-'É–ëS3”²mè™, ÈøÏ-¿º×jNÏ83¼Ã8?*û«­çò›na´[ô:uy9n‡¯¾3q &ÌzÛ®o[ÄcÒ¿Ç–ñôöðc—“¤Àð‹RØS€¡ g *¾Šù¥sàÅõ EÓòïçÇçsëTæ¡•wRý!^IïäjWö˜uQò)&)€Y<Ä<ž§Çú'OF‰¿d$g¢Æs˜›?b±ë$×@fçàK‹ý`gWZhr rÿìÏܧ7èôŠõ‡uê:6tÐ_¤â~œaW|9‹Û®éÕ¸I'BN‘0@Š‘,¢ä*f΃°;øÇ±üVŸP¬ÚNÔ³1}ëÎîu ð\®c;Z3D|›Î€l£_ï{¤¸q$pï¡­1a}bÀœ`@(FcY@&W1»pbðW¹{ üÔ.žÝgØÒ—ÜñwcÊÐq­r÷òȤ~ ¤¶½‰Ÿ4;4ßÂI …5,áÔXŸ' üÀiQ’@4%5¯`fñIŸ¨\óº’;€¶dƆs÷ÐÚ5¶2Áƒû–Ý‹Að›Tÿ›‡~=üy“~VüÃÖB7÷޼‹&z»äûŸ8à{9 @A5/aznIJˆd0€r`åMÿÙ¹°ì(€Ûž³ŸP+‡;«@þÞVƒLGz[©O«˜mR\ÚuiÐG|/€óxÓØ<¹ðÞ]N€sÑ“Ut¶%¨ûö]c-|‰î@S¿m¡FùOÂg`0ÕÁÉ’Ûöuö~¶ØX §ø&Œ.!ð þy<Ä2îQìr¢Õã  PWØ2 qù>D \F«àL"MÚX¢PPì²É) x>÷ù¾¦ŠÓK¨ yú³ðÂ[þ0ÒÄýpjÞq)ÐaXêˆwý؆Â{Õ÷DßˤG€I0âü—ÐÂEHw³™­%Ì·ÞvÊ€`ÕQ2Ê–XAK]b[uîÐ,¿§6.åÕÀ¤ìy7˜< 2A‹ËtÄb¯‹Á¼ƒiì{õŸ<rè J‚ÿ ¦pTYS)ˆíûÈL˜-~pdˆ-ûÖÞ;µšË௳þÕ×Àn«Ÿp•WÔ&·€×©µø&³£YU 2¬Z')€¼ƒ¹R—·ªœðW€a£³:ëÅ)޳ümoù+¿VJf£rdÇø”rÔGˆã”ÊþÏ2a’í¦àhÈ¿z´Ì.È݇YS x?0pYOÀÄjÀ>ÀÒHÞ?ð3€Ù£9í“äÛ»NXE— ð×åßTãüžÓk¸¾qѬ¯LÁ(!á‹ ejŸ>PJdŠ;QëñT´¤ˆLi72éÊH•Û×uÅÇIoFI›êp·‘¸„ôúûw0a3šÀgÿ ÆÓ=´õòÆç÷öùI(À¨¼ïñGA`S¸ á²ÉÇÙ £<°Š`(÷'±t$³Qêt P¤-ˆJ¨š“ ʺ ߧС8a~˜dÚx†KCãý¾=h?ô·O¶?¹s4§w2FkQ.bŠv5¥¹ þa Ó…‘ŸïwiL(ª¯Šã¢1o/W0\òRÚ |(€l€ð$v*×bÿÀgÓ°ö¦üðÏ2lØÿ¿s÷hOqR 0jêGïðàLø« ÚˆO5æò¿£…ÆÜvŠ$\¤çMœÃb¼/ß®/øÓÀþ-ÆÝg8Ї2É`·q=|ß‚_\D›®€ F:¼þu)‘mLð‡+Us†Âß«{ýQ›Þ¼àÛ8‡ML8Ht/þŸþ9ÆkÀ›÷Žû$­Lšãx‘ œCW  ´jõÍàûýþ¹Ú¦,ÇN¢UQ|•þØ ›w±ÃŒ&]*” ÎòçØüuÎ.POêŽð£*üK¸…w±€.Z#>ÛFßÊÏÿÁße¼~Çnûà‘›À>ErØá€á˯èu;œ!¡+nò+,GÌáR˜QÚ ¹]Ì(ËÊnÖÄÅ—|îbš¼V-X+? ü‡ñÕ÷Ü{¸ýØÎ9žV9Êy€*Ü ú+Ø£¯ã¬m/þìî½´üþ/°l4ºb0Rç[ñÝž v¹k&ž"žâ<ž`€()WŠQ|?|®¾þ(ˆÐ_z‹ïýÝ/Fïÿñ#Ñíf×tÑ«ëÊ÷¡OÆ;idÿå1t…ìpîõæà·ÿ>áoü¯Œ_ø;lÈAyn3€A$\9ŒOÏ761Ýx‚Kè`¾8R<Ɖò|dn©;W  |ðPlý÷ÿN¾û½Ek÷6­y• ÆÌ0Æ€ˆØ¿ã¼,?hÒ§¾G 4¾8ëÿÂßc<ÞÜ}Çu"ÈŽ ?ðpn˜NˆØÓMÆtƒ°Øf4"P¤@±$€@RšadNÑJŠ•ç¸°@6x|ïʡ^ó“%@È:Èÿ—?–wÿáWåý÷Ö!Dé21UÒ»½xÑZŸaæÒû¡¢„_ª`˜~è²_`,úÓN°oðv’äÝÇ {Ïìþ¦F$^F˜Ÿ-ÍH´b¢f4#A±""°Ê 0>¸Ôä§?½ª/|ï /ƒa+•>oZà7dàöUzô¾*½ñ:=6Bš‚`° ° àµc¥ÂV90³¢xvß+³xÅw1~4¡PijäXïÜxQ a;žýYÆ“­½ÿ†Û?‘kè$‘„”f&ÃDR±) Š Û×Ò§órnIˆï?k¦ÿê‹fé³Wx€­Wý(*€îhà_}‡ÖÿÁ¿£'_¹‹mm;™ *.~õê %Up×¼Wçi•ÌbùçPAŒ1lŒ3ƒ­ ;®=Ë~)Ððo°±\ù;ŒµñKÔÈ],H)IrRü.³u?Ÿ b&)@ "ALDý†ƒ & ”iðTñ“—yêç?ÍËß³ÂmXE8¥!¼=‰+o6]ð¯}[ÿócý+÷ÐÙÊÀDb÷è[±­¢ÃÂ\- ÐW•ÂQ%¯‰èDÄÕïcXk]Œ#Ž{¤Š¬osmì“”’â8&²WÌ,,à‹ÚC *ÿ—Þw”³¤æLÆØÓ1įr믽†ÙYE«Ñ†€†U†“€Ø>ooÁüÊ·±ý _ÃÆ·Ÿ íjÀ ï1nÔÀ(QúaÛK lãºmîïm"b!cLžçœçù°ã*Ç©DDˆã˜”RÂ#Œ1Ø‚™}̆ÜëЗÂ93¹F µ7šI@C+Óˆþ ´ÿê+˜^=ëê r`Ài>-B(R¸ þÊ=¤ÿü[¼ýëo¢óhº[©âÜ£ ýf e ?_ë3ÔY{”•…‰¨4­éÁY;À! c §ijœ³=ÖY›à—ÆÉÌþQRT¬¿{í~­x.³à3•£”X‚?uÍÏ=Gº€äÜ¢søOƒ2 Šƒ xë²ýº¿ú'¼óÕ‡H{98ÝŒv²’JïC£FŒ¦~MDZ¡1¦×ëÙûQ€]>¤ü•‘;&¥5 ¡µ–̬˜9 •ý‘ dáÝv`ˆußeÛHQ¬°Ø€ü©kÔø‰ËhüÈE$ËóPnÅÉ~véqм‚5=àî¢÷ï¡ûoßãÞ=@º•Ád¶çïI-6ÞR…a×:À+€&¢œˆr!D.„ȳ,3išŽ뛬8øâÏ16»#>æ¬ERk­Œ1Q Ò›A«~h7×+ÃL ù™Ä?v‰’OŸEüò"¢éiHH”Ó«Ã[5 ÏLÀ¦û11n?Cþ;·ÑûýûHÿð§oOêLâò Ôõ¨VZH Z "7à†²Ç¥"lç0™nCß߆yÔ~ëò6YßÙ„~ÒyØÙLaŒËÈáàyÔö!TR ”Kž6Dzh=±¾@ZkdYf”RdŒÑÎâ"’°N¯WˆÚêS¥½PÅ (c}N³Í=€Tƒ7{ÖM¦ÇÀÞçb] ?±ÔR X‚4±´¿al¡?:8µërØþÔÜ_{ƒûϧ¡CÖ8\"ŒU¿3à3s­8Ä…ì`ÿÐZó¸ü?²«Äcª3s𦠕R kíur€/a" _×E„Èå ŒTŽÊ¡xÿ¢zCÂh×HàwŽj\-Èó±oÆqGjÆÝÇ@$°â°û½Ò¼€Ïªl6fܶ0Zz03w»ÝC-Å ï`ùoŽ™½^µÖF)åOŽü$ØÀ‡!Q_rí¿ã?Ô;еJáo†(ÈÛ†cígä±ðãû¾3£÷¾¿—d³½L~UßÛ-]"´î~{ݘ+£A ÌÌœeçyÎCŽqb²«}ïåãì¦|‘òÝóÀu©^³aˆ*/5ÊQÚ‡ÏÁñç\ÇÒ¶ðs{¹ç'ANÆú}©7ƒ7ŸF}'@Iª¯= óÀáhû Á‚4¤(^¡Œ1ÐZåx „€RÊ+‡ÇZT/᱇‘¯ºó)G¸Ÿ…붸gã:á'NNšŒ+£MM©B¯Öñ{_Å—@U© ‘§,«¸g1Æ MÓØ˜Ú-J4Éëzß;vùÿË8JiÓ5Ç%tEXtdate:create2016-10-22T17:14:53+02:00ÅkÏK%tEXtdate:modify2016-10-21T19:35:47+02:00èS9ÇIEND®B`‚Slic3r-version_1.39.1/resources/icons/add.png000066400000000000000000000015511324354444700211100ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<°PLTEÿÿÿ‹»tˆ¹q„¶n€³j|±gw®cs«`n¨\‹»s‡¸qi¥Xe¡T‡¸p_žP[›LŠºs‡¸pV˜HQ”D‡¸pL‘@ƒµmGŽ<~²iB‹8z¯e=ˆ4u¬a9…0p©]4‚,k¦Yf£V4‚,0)aŸQ]œM4,0)X™IS–E4,N’AI=DŒ:@‰6<‡27„/3,0(¤Î”ËæÃÔëÏÎéɸݯ¹m»Ü¯àòܽ䶥ۛ Ù–¯ß¥ÅèÀŠÂy·ÚªÜñØØ’“͈ŽÌ„–Ô‹•ÔŠŽÒƒ·à¯}¹k—Ç…×îÒ Ù•”ÓŠ‰Èÿÿÿ…Íy|Ãk~Çoµß¬WžE¶Ý¬¯ß¦ŒË†É|y½nj¬[w¼`sº\™Ñ‹’Å€¾â¶—Õ‹†Ézp¸Y~ÁiªÔ³Ý©ŠÏ}Ìu}¾g«Ôœ˜ÌŠ–Ó‰zÆkpÁce«Ur¹[—Ì…¾{h«S´ÝªyÁdq¾_nµXt·\´Ú¦C8}¹j²Û¨oµYoµXt·[³Ù¥lªZx´dµÛ¨˜Ì‡}¼f|ºd˜Ë†´Ù¥kªXP™AÂ}¦Ð–¦Ï–‰¾xB6nAsK3tRNS#}ÛóóÛ}#SææSôôS"åå"~~ÛÛööööÛÛ~~"åå"SôôSSææ#}ÛóóÛ}#w¤öcbKGDˆHÇIDATÓc`À™˜YXÙØ9`|N.cS3s n(Ÿ×ÒÊÚÆÖÎÞÄttrvqus÷ðô ˆxûøúù‡„ŠÄÂÂ#"£¢cbãâÅ ‰I`œ’* JKÏ€ dfId²sróòÜä‚Â"Y €\qIiY P ¼¢²J(  X]›\[WßШ¤ rˆŠjSsKk[{G§š:Ä©š]Ý=½}ýZê0Ïhëèêéaõ9#Æ1]{hotEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/application_view_tile.png000066400000000000000000000007211324354444700247300ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<cIDAT8Ë¥“ÝKÂ`Æý%)‰èƒÂ‚¤•CaS )ˆBDZÚ‡QRÌ4ZaQvÑI’•÷A7]x§óôžóæfí"š<¼0öüÎÃÙ^8:±Ã9¥Ž/hÕZpç ämæô H›ÏØ(ƒý ĵø’E˜L<€°tÞø ÈÉ«æÉVõXþþk!z©'Û±gþ §_ÁކgÏ8@ڪЃpÁ4JÐ<†QËEÓ¨ÁÈÉ7€-¬H•~Å(Íf“‚¹Š èsnû¯º®[ôMq~ªß‰-c¸ÝFƒÞé p€¸úh™ˆAP\†1øY5ŒrËû (÷ URŒÊΈtâT ~¬—?Ëñ[KªÙÖ ^¯[t‰ðÆ®mýNŸ ­» Ž-èÏ™;‡¡È) ÌäÙ–iQî =Òtr¬ö.›¬Ë Ut|;| .’ûc^IEND®B`‚Slic3r-version_1.39.1/resources/icons/arrow_down.png000066400000000000000000000005731324354444700225440ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe< IDAT8Ë¥“?KQÄçÄi‰œµ¢‚ ÁH*Ñ.â÷ˆEºøAÅï!XØXØ vc!v¦¿{;»û,®Ó»„\¶}ÙÝ7IŒóÌbÕÃà¥cŒ0u˜®Žï“2ÝBÀÝÑ^êàh¥ gw`4¨+Ü@jˆÉ¥ s‡ä5"H h†PÇðhõ"H.E„ºBFЛâ ôœÝœ¶›&è 5E˜°Ä€îõɃäòÔZn¦ +ÒÕ;ýÍl«·þ8r9¿~Æc¨§$ ý¦h÷¯>©*ÓÁåîkš®í›>†£·áíh¯L—LjãöÅÆ³RïwŸ‡UšdÞ:ÿ<$·ÇhݸIEND®B`‚Slic3r-version_1.39.1/resources/icons/arrow_left.png000066400000000000000000000005311324354444700225210ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<ëIDAT8Ëcüÿÿ?%€‰B0È (=‘ÉÄ»I1€õ2¤À‹ ÍÅÙ$ôþüùÃððýC†_?3üú¿~1˜þŽ`ƒä.ô_edj>)Æ*®§À­Äðçßn90ýûï_ þÍðûÿýÑ¿V¯\‹ðÐÙ¿ÿ0þaøõ÷XÑ ¢ß`Å¿4ÔÜŸÿ@±_P½¹3î¼0›ˆ3'Ã×^¿ƒÕÙ ¯4ÿùý‡áþÂGŒŒÈ)1vUÈM B9 Ç i;N@ͺ@͉Ռ⅜㡻¼zZÓÉIEND®B`‚Slic3r-version_1.39.1/resources/icons/arrow_out.png000066400000000000000000000013131324354444700223750ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<JPLTEÿÿÿd´[nÀcd³Zb±Yk¼`a±YoÁdm¿ci¹__®WY§Rl½aj»`h¹^[©S_®W]¬UPœJN™HL—GY§RO›JJ•EH“DW¤PM™HW¤PU¢NI”ET¡NtÇhsÆgrÅfqÃef¶\d´[b²Y`¯X^­V©Ø¡§Öžg´_”ȕɎ\ªT¥Ö›Ñ’‡Ê~~½v†À~ÅŠZ¨RrÄfuÅkŒÌƒ¤Ó›„Ç{}½u“ÇŒs¶mY§RW¥PpÃe‰É€‚Äyu¸nsµlU¢Nc³Zs¶lq´jd§_g¨bG‘Ca°X_®WŠÀ„tµna¥]y³uc¤_L”GEA_­VŠÀƒz·ss´l^ Zd¦^p­lD@\«T‰¿ƒƒ»}[¦TFBq­ms®nBŒ?Z¨SX¦QV£OT¡NRŸLEŽAC@B‹?AŠ>ÿÿÿJ¡{YtRNS®ñ®ìñ¸³®쳸®¸³ì³¸ñì®ñ®`XçôbKGDˆH£IDATÓc—WPTbeU5uy M(ŸQK[G—AAO߀ Ìgf1426a053·°d °YYÛØÚ1سs88r‚¸œœ¹\€ ^>ˆüÜ Ô‚ZHXHº2¸¹‹‚øbžâ^ Þ>n¾~’ )ÿ€À `†Ð°pi°وȨh†˜Ø¸x9¨a ‰IÉ )©iéP~fVvÅ•@;~DtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/arrow_refresh.png000066400000000000000000000012551324354444700232310ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<?IDAT8Ë“ÛKÓaÆû3bvÐ’Bº°BÖ9Ëd-Od­6_×tä¡eÎÅšþÜÜ¡ÜIͱaIF%üÀh„eµ ×aù#èÚ;¼ ïž¾]8ýa»xoÞÃó<ïóá+ ùߺñ¼q°eZ!;“”"Ð4U/(Æ/Dvèû¬•Ý]ÔtoUqÍ|ÛúÍ͸U¢!\ýËvt®¸êP¡ÝØñõëÖ¸#i)8:S.¹Î¬žåjÜ[/Uv– åloL´Äë3 kÔò&µü§Ö~jã´å·ýÒ~M™©LµGJfŒ2BÊ”‹ŒÌ˜„Zf—=çEf™QdÖŸî’wLÛ…ÈŒ#³f›d¶&ÊÆ©°uºÉlÕ“¶;îåû‰ïxLHÖŒ ɇ È È Èè~£…+É:Ò#h›hÎ#5=&¤N.eÄÜïhiL˜(B:’* ÕÎÞÆ‘îC„8i¹^fµ[‡È|¡Ó–ªØ §Öž™ÎxºpŒÊ‚«Ø¡¢Ô™¦Õœ¤Ô››Ð’žÑ–y¸q…Ç{©×  Ñ—Àw~¼u—ÌŽ›Ï“˜Îi«aÑ” Ò–dž”ËŒ„À}f¶\¤Ôœ¢Ó™~¾vJ’BF?B‰;>„8c±X¡Ó˜{ºrI‘B’ɉ“Ë‹’Ê‹EŒ>A‡:rµi–̎ʈe§]D‹=^ XZT9}3q²iɇ„Ä}‰Æ‚‡Ä„Ã~‚Â{S–M1s,c¥[{»u…Ã~‚Â|€Áy~¿wO’J-n);57{1Q”LN‘I-n(/q+,m(,l'ÿÿÿSï1tRNSkØØkËËËËkkØý‚‚ýØ‚w‚Øý¦kËËkÓÔ"„|bKGDˆHžIDATÓc` 02É+0³À¹¬lŠJÊ*ªjìPNu M-m]=.Ÿ[ß@ÅÐÈØÄÔÌœ,Àka©ËÇ/ hem#°µ³wf`qtrv ¸ºéº‹20xÀ-óôò—`ðñ… Húù{‡„ÂD¤ÂÂ#"£¢cbã Ò2ñ ‰IÉ)©ipcdåÒ32³²Qý““‹æÁ<¢Oò“ttEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/arrow_rotate_clockwise.png000066400000000000000000000013021324354444700251250ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<GPLTEÿÿÿnÀcl¼`c±X_­UpÂdn¿bX£OTŸKm¿bP™Gm¾bG@jº_]ªSY¦PV¡MRœJC‰6y02t-1s,-n)3v.0r+i¹^fµ[‡È|¡Ó–ªØ §Öž™ÎxºpŒÊ‚«Ø¡¢Ô™¦Õœ¤Ô››Ð’žÑ–y¸q…Ç{©×  Ñ—Àw~¼u—ÌŽ›Ï“˜Îi«aÑ”¦ÕɉŒÈ…„À}f¶\c²Y`®V\©So¯gɈ>„8U Lj«cŠÇƒˆÅ‚:~4TŸKPšH„Á|~Áww·qSžKl®chª`H@a£Z‡Å…Ä~‚Â|T–NSJj­cɇ‹Ç…‰Æ‚‡Ä|¿u‚Â{[UN˜Ff©_‹Æƒ…Ã~tµmR•MF?^¡WZžU;57{1>ƒ76y0ÿÿÿù6¼tRNSkØØkËËËËkkØý‚‚ýØw‚‚¦ýØkËÓk’Æ/bKGDˆHžIDATÓc` 02ÉÉ3³À¹¬l ŠJÊ*ªìPN5u M-m].Ÿ[O_ÙÀÐÈØÄÔŒ,ÀknaÂÇ/ hie-°±µ³f`qp´r‚›ëÌÀ êâêæã{x2ˆ‰{yûH@ù¾~þAÁ!¡’~XxDdTtLlœTA|BbPRHrŠ”4̈ԴôŒLY$¿d¹3 l8 ]U+Ý:ÝtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/arrow_up.png000066400000000000000000000007671324354444700222260ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<ÃPLTEÿÿÿsÆgpÂerÅfh¸]tÇhrÄf^¬UY¦PqÃen¿bX¤OSžJRœIˆÊ~…È{‡Ê|§Öž¥Ôœ}ÀsƒÆy£Ôš¡Ó˜†Ã}e´[ŸÒ–œÐ”e¬]_­VšÏ“˜ÎPšHY¦P•ÌŽ“Ë‹J’BSžJ‘ÊŠŽÉ‡CŠÐz<Ît8Ìq7Ën4Û›v{Oå·ƒßUÎv:Ír7Ën5Êk3Éh2Ú˜uzN㲀ܔQÌp6Êm4Éi2Èg1Èe0Ú˜tyMà­~ä¯â«ߦ|Þ¢zÝ yÜžwÚ™uxMˆX†V…UƒT€S}PyNÿÿÿ)<™tRNS ¤ñóóóóóóóóç|îƒÛ¯ððððððððð·ÀÂ#7bKGDˆHÞIDATÓc` ™˜YXÙØ98¹¸yx|>~9yE%eU5u MA-m]=}C#cS3s!a K+k[;{G'gW7wO/o_?ÿ€À àPѰðˆÈ¨è˜Ø¸ø„Ĥä±Ô´ôŒÌ¬ìœÜ¼ü‚¢bñ’Ò²òŠÊªêšÚºú†Æ&‰æˆêÖ¶öŽÎ®nÉž^ˆê¾þ 'MžÂ 5uDõô3gÍž3—AzÞüÉ -^Òµt,cY¾bå*·Õ)Åkº§¬»Lï[Im` ŽtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/brick.png000066400000000000000000000010151324354444700214450ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿eIDAT(Ïm‘1H[Q†¿ûòbhÒPˆÔª[¡…ºfJÇN.ŽR©«d¯àâÞBQ,¡ÉÔ½ƒâT–RR%†T(ãC£é{¹÷w0Q¿³}œs8‡ßˆ>ÛËÁ|k ÒÇ©|ñF«W•L)TR¸9Ö÷Fl/s­I¼YrˆŸ€K7RŸóED©s=WWYUUõEõþ¦Žˆ½X‹ÍˆGˆ_t©ó—$–Ç8~ø;9SŽžûm 1j¼îÝU!KDËA×Sû ã¤!C ‡£Æ(´I£¶/`8gÄ Ø"Nèï}è Ž\àøÇ%#ø,áÛ„‡ˆ°D]bpØAO[» Ã!À!Ä’x ]Ͻj¾ùö{C°„ 1Lý£“wzi¬g´¨ÂÔçXšŽiœë£Vþ€é‡õ陊š›6§49¯èýÛï×Þܦ fµ¤­¾Þº; ÷qÓ¹%4£vÆtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/brick_add.png000066400000000000000000000015031324354444700222570ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<kPLTEÿÿÿ³³³¢¢¢­­­©©©¦¦¦³³³¢¢¢©©©®®®¬¬¬›››«««šššªªª™™™©©©˜˜˜¦¦¦•••¥¥¥[{H£££¢¢¢¡¡¡7kŸŸŸžžžœœœ›››ššš˜˜˜———–––Lt47k7k7kÿÿÿ¢¢¢±±±ÖÖÖýýýüüüÓÓÓààବ¬ÑÑÑ«««®®®ÕÕÕÇÇǵµµ£££ûûûÒÒÒØØØÍÍÍÎÎÎÌÌÌÁÁÁ¾¾¾óóóéééÝÝÝÞÞÞÂÂÂÚÚÚÃÃÃ×××»»»ÏÏÏñññîîîáááËËËÀÀÀðððìììêêêäääÄÄÄõõõòòòçççßßßÛÛÛš¬r–]fŽO^‡F‹¢~ãã㔨‰«a´Ô•Ðæº»jW-èèèëëë`‰H±Ó±Ö’Œ¼e¼gÅÅÅL{1¶Ôœ²Ò•7k½½½~;{G'gCW7]w €€‡§—­“·¯Ÿ¿.HE@`PpˆKhXx„®¤ 0CTtLl\|‚ŽUbRrJªCZzFfVvNn^~AaQq‰(ƒXiYyEeUuMmqqQ]}ƒ¸DcSspKk[{GgW}wƒ¤”to_Cÿ„‰“&×Oén»TFVnê´é3fvO™%u¼‚âì9sçÍ_ ¤ 󰤊êÂEjê aE> ]ïîÒtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/building.png000066400000000000000000000013301324354444700221500ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<\PLTEÿÿÿtÇhrÅfL˜GqÄfH’DEAnÀck¼aDŽ@B‹?ç99šššÖÖÖÓÓÓÏÏÏÌÌÌÈÈÈÄÄ᝝tttŽŽŽ±±±ÑÑÑóóóòòòñññððð©©©ÍÍͲÌõ¯Ëõ¬ÉõªÈó§Åóïï¢ÇÇÇäääãããâââáááàààßßß›››ÁÁÁ¬ÈõªÇó¦Åó£ÃóŸÀó¾òîîî“““»»»ÞÞÞÝÝÝÜÜÜíí파Œ´´´¢ÁóŸÀò›¾ò™»ò–ºò•¸ñììì„„„¥±¤ÛÛÛÚÚÚÙÙÙëëëxƒwzÁpƒƒƒ†††×××ÜäÛZœT|ÆsÅßÁ………µµµ···ŠŠŠÕÕÕËÛÊ`§ZrÂgˇ€Ãx‰‰‰¹¹¹‹‹‹ÌÑËTžN»yj¶`u°n’§‡Ž‡¼¼¼½½½ƒš‚X–Tm­ekkkjljZyY`s_gmgÿÿÿ±+p tRNS ARÞ-wþø±hD…7bKGDˆH²IDATÓc`€n4Àgñòñ  ‹ˆŠAÄ%$$%%¥¤¤e ²rrò ŠJÊ*PUI5u M-me¨€®”ž¾¡‘±‰)TÀL (mnaie °‘¶µ³wptrv ¸*¥ÝÜ=<½ |FoK_?ÿ€@ˆSP°[HhXxDd3ˆÏ럘”ÌÊ`çHIMKÏÈÌÊÎÉåä äÁA~Aa;â!†é tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/bullet_arrow_down.png000066400000000000000000000003111324354444700241010ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<[IDAT(ÏcøÏ€2 )þïú¿ùÿêÿ‹þÏøß÷ ‹‚í_wýßÿ'PÑœÿõç±(X-±èËŠÿÿÏü_õ­P«&Kt~Ÿò¿ì[®NGVI¾Î”‚! Ž §QƒP:IEND®B`‚Slic3r-version_1.39.1/resources/icons/bullet_arrow_up.png000066400000000000000000000003111324354444700235560ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<[IDAT(ÏcøÏ€2 9›%V¿^$‡SÁz‰eßWÿŸü­S«‚Õ‹¾¬ø¿ñÿÌÿUß ±(˜ûuéÿõÿWþ_ô¿íêy, úþ·ü¯ú_ø?óÒÿ˜oC,$Ó Шi¶^IEND®B`‚Slic3r-version_1.39.1/resources/icons/bullet_black.png000066400000000000000000000004371324354444700230050ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿wIDAT(ÏcüÏ€01 ¼SIŠ!í_è_†ß«Íz÷ ‹†4Ïz†? wë—304`±âo¨Ã]†ë r ?C±ºá7Ã?†ß ?þ2üÅîÈŸ«o0(0h0Üd`X¤â?rK±50\e¸ÊÐÀ …e A ð÷*’|ˆÝtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/bullet_blue.png000066400000000000000000000004411324354444700226530ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<³IDAT8ËíÒ= AqÅñó:îd’R2 _ %Ãe±H2-¢ \ƒ§õORÊÓHêún:Þ€ÁM6ÃY?uê ’øføïTi JAÅQ(j…üIÛn‘Å“­ÃúæÁÈðB˜Kù(ßUsë0Ú¿ÑgifæšHÔç@á¢jëý–¦§sebb±¶ w”ðàLs¦Û uD°äâBve =${ ÒPäÞ¸ñ/ñGÀ ¼_퀢B…IEND®B`‚Slic3r-version_1.39.1/resources/icons/bullet_green.png000066400000000000000000000004471324354444700230320ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<¹IDAT8Ëcøÿÿ?%˜aÔì8ì²’âûí–Wí7[\µ]kÖ`³ÌDŠf_›þÿУƒÿwßßõê™Iÿ-æ6m€ý6Ë«ø?õÒÔÿg:þ/¹²è¿ù½«D`·ÁüêÖ;[þwŸéúßzªùÿÜ ³þ›tio€õr“†©§'þ_|eáÿÙfþŸp¨ç¿~™*ñ^°œg(e1]¿Á´OçªQ«æUÝ"å­D9©Ñ”H#"⫚¦üIEND®B`‚Slic3r-version_1.39.1/resources/icons/bullet_red.png000066400000000000000000000004371324354444700225030ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<±IDAT8ËíÒ- ÂP…áãÝGY°ïZD ‚ ›\ÃÆ¢¢Ñb,EaÍf°]ĬFe0/L‡0Û©xA¿ %ð°àÒ‚ÌM(m@]ä p¿ä«/™Ç>— &¾Ï*²0ð0¡òxÍ,ìQ·Ôã!w0Ta PÙ|FÝnò^¯1Bư‹yó<êÑ€iðÜér §ø…#„»‡[Xj[MàÈU·,ñOÀñ|Þ‹ëds)IEND®B`‚Slic3r-version_1.39.1/resources/icons/bullet_white.png000066400000000000000000000004241324354444700230450ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿lIDAT(ÏÕ± €0DŸ¢!8VVpçÈ ™ÊBkµ AÌÙˆ±¶âýæxÜñ ñ®’ïU²sKr²Ý|cÝ7 ›¼¼VC¢Ï S³¨‘ÉnÑårhh.;2Úq¸h-þðêz>2µénpÇtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/cog.png000066400000000000000000000011171324354444700211260ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿§IDAT(ÏM‘?hQÆïqi4i/˜ÜÕ€±ƒC&¡8(vPpvîd+8‰“І:ÚIzèÁ.. ‘ÚAK ¡ÿBB jî’»÷ÞCƒíoüø†ï'RŽù4§g ª1óì8G…fÕ˜¾nŸ/¦lvsžÎ«òí€húfWï)W™ˆˆ1£Üx/Þmø£ÂNÍž¯HÇÓûÚ2 qÕ~Å›‘gç×À‚X¶SD„kKP‘ñ÷éPŽ6l,/1üæ;¦°P„4WÖ@l~Ö%m{N@Á£,NýòÕ›õž tG|0å!ÉÐâëÍÚ;ðoÞz„äqy’HE„Æ’g…&ÁbýÜÞ.9ö%g@™Ãú* UŸ$FÒèõÕ)°¶<¹¨±üÀpÀ€õ•§5ðj)^˜ÀbŒ.`“åSdî-Qâ‰Óìð%¹ë"áE*Â+v‘‚h&£êþ¯;A§0}fë–§ñÛÃéd+,©çoîþ—õ ª&úÙöµbÀËnêÉüŸòëÖ ›GÜŸëÎFˆÆÚ Ýÿ”óºªià ¤tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/cog_go.png000066400000000000000000000020321324354444700216100ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<PLTEÿÿÿËËËÇÇÇÄÄÄ¿¿¿ÑÑÑÎÎÎËËËÇÇǬ¬¬¶¶¶²²²­­­ÑÑÑÎÎÎÊÊÊÆÆÆÂ²²²±±±¬¬¬§§§£££ÐÐо¾¾°°°¬¬¬‹‹‹ÊÊÊÄÄÄyyyqqqÅÅʼn‰‰ÈÈÈÄÄÄÀÀÀ‚‚‚www~~~ŒŒŒ‡‡‡ƒƒƒÄÄÄ„„„ššškkk¿¿¿“““•••aaa£££   m£7g¡3b0XXXSSS¬¬¬5‡-ƒ«««¦¦¦%¤¤¤ƒƒƒ%yyooodddvvv$w yooojjjzzz[[[+ƒ!yymmmXXXQQQRRR!}yåååäääÞÞÞÝÝÝÜÜÜíííÛÛÛÖÖÖÔÔÔËËËççç···ÙÙÙÒÒÒÈÈÈÌÌÌÉÉɺººœœœ¡¡¡ÂÂÂÆÆÆÁÁÁØØØÍÍͼ¼¼ÃÃÃéééÎÎÎ¥¥¥p¤@eŸ1âââ±±±h :³ZW˜(ÀÀÀÏÏÏo¥=|¯UªË‘t«OEŽÅÅÅk£7°Î–­Í”¨Ë¼t¡ÇŠh¥FÕÕÕcŸ1Žºo‰¸k…¶f€³a‚µg˜Âƒ\ <¦¦¦[›)©Ê¥È¡ÆŠžÅˆ…¶j—‚f¥HQ—"J‘D;‹^Ÿ:–Á€b£F½½½²²²_¡C{ÿÿÿ“RsÆ[tRNSc¿¿c)êê)›ýççççý›{ôþþô{}þþ}……ÍãîÆDDÆîãÍýDDýýDDýÍãîüüþãÍ…û}þù{ôê ›ýçí)êêëÔc¿¿cë²×GbKGDˆHùIDATÓc`F&f8`ec爎áäâæðyùøcã„„EDÅ@|ñø„D ɤd)é”Ô4 €¬\|rzbRrzFJ¦¼H‰brVvNn^~Aa𫬢ZT\¢¦®¡Yš_¬¥­Ã [–”]^¡ÇÀ _Y•SgÀ`XQP\cÄÀ`\[WßaÂ`jfÞXÕdaiÕÜÒÚfm4Ŷ½°½£³«¥»§·ÏΞÁÁ±¿=eB×ÄI“§L6݉Á¹$v†ËÌY³çÌ7«P‹»‡§×ÂE‹—,]¶ÜÛì_?ÿ€+ƒV‡À=¹: È;wF'Šœ„ÄtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/control_pause.png000066400000000000000000000011261324354444700232330ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<èIDAT8Ë¥S=kAƒ(Š(r‚6‚…ÕXha©½mêt^¥þ Mig¡!¥…`k'¢Ý¨• j!‚wøýµ™Y½Í™&YöfoæÍ{] c þ³¬_F£QAÓIŒ ¸^¯*î-ÜÑh´g®·˜`³‚yN’$Ùét‚ÝnççûýÖë5Ìf³>‚”b±XY4Åp8Tƒ†ÅŒíº®3MÓØn·ãg›Í†u:­Ýn+Fgp§ý …dšºZ­°yˆÏçƒív Ýn—˜¼¤R©Þ`’&ÚFs¡P€J¥*š‹ÅW¨VßI ƒ2²I É0úAZ nƒÙý›ño ÁëõRm’Î š~8(„aœ€Ëå‚óùf­Ôtû~4Ù\s<át:™JæØl¶;>ó[3I].—  €ËåÒ"ƒ/’$߃rß`A5Óé”$´„‰¨½1ŸQ›ì÷ûAQ2âžÐÊdn¹Ûí†ù|ªªö1m‰D¢‡®–ð’èDÏãñ€Õjå¦ÒNAÍx¡ÙlêèA A{ß®r½^WP[.Ë@€¡˜/;'yóå¼ÏyÏw™€‰qsI“E“UÓÔHÖÑšéÏúqãMæ»/%[àã1ÙO3‰C!Î&…ݽób'GOûë6¬ÁÚß#óY_¨lf9¾&¬ô@=5Z](UêÞc¹…ͬkuˆ0£™M}•ÛðSº}¨Ë=45;}²&+]ØÆdÏúBÌ€sal쬛«R*?ºÀ‹C•Ŕ겹æn¯ð8Ž –óÆØºùâƒÜr' óäì\w%HüR¹ï@ù#4z` 9bpfŒŠ¦ ÷ ]‹B[l@­Ñ©¥Â‰¨Àâêk½˜Š$Ü,,@v¼¡Žkm¢Iû6\›gI:ü盛°â]ÐKûq–°»ê‘±¸PmÃù{A¸Jø/µz°¸ôÜX?|N2¢Ü†¢;’È,äOÈU ðžpcñdÒE(×Áâ fèB©ƒ•å"4_¦X2^¯?€­OI°»ß›hºã«Ú‚¡('Hmã$t¥Ë-ã8ќѺ;Ÿ,sWÜéá1ê‰z·¯½ É|½I’ˆM•¤Á1ðøÐœ.ÖÁáñÉ3Ôú¯‹4~•ç¼Qû²‹Ä2Pà%²'8s2/ÀF(sÔ7í|ó÷UL7Ÿåm³O7èG®%†šw ´ðÐáafúú÷cúŸçü? sq"õ–IEND®B`‚Slic3r-version_1.39.1/resources/icons/control_play.png000066400000000000000000000011201324354444700230550ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<âIDAT8Ë¥“˪â@†ÏCåò8«<ˆ‚"èÆ¢ ®‘ ¢*ÞÚËÂ…÷kÔxAQ±¦ÿæ$"s8Ã0 EBº¿¿ªþê|ÑÇÿÄúý¾Ôëõ´n·«w:ƒ1f´Z-½Ùljõz]úV€Ã2‡Ùz½¦óùLÇCÄét¢ÅbAµZU*ùKOؼ^¯„…çáp Ó4ér¹ˆo-—Ëf©T’ß>Ëf¼Ûíh»ÝR>Ÿ'Ã0h³Ùª²Dt]gÙlV²x¿šuÀ‚:?)—Ë x¹\ŠÀ ”J¥4[€›¥C¥´²:N<£€áü@‚D"¡ÛívÛ€Yûý^ÀȸZ­8ìárý¢jµJ³ÙLÝn7ŠF£†-ÀÇ$P¾ã `Uu‘¢ü{ÓéÔ®"‰¼†ÇцÕ+ªªúO&1™ápHápøÕŸ¯†Íçó)Àù|.ÊUEÀØÇ4è~¿S±X¤`0ø2‘ÏU* `!°"+àÃŒ‡ôv‘2™ŒœN§M¸ƒÇãÑ®— ™!ÂAÓçóÉ_^åd2)ÇãqÆ[-ÀôŒ¹c”dGþögŠÅbR(Ò€î÷û ¯×kpHw»Ý—þú7þküFF?ÔE·E IEND®B`‚Slic3r-version_1.39.1/resources/icons/control_play_blue.png000066400000000000000000000013151324354444700240720ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<_IDAT8Ë¥“MoQ†ûüüXºtîš.º3qcÂÆÄESW%5ÕEc a,QL›Ö*kª¶©‘ZéŠi,¥ èP˜Áa``ÊqÎ…©ºó&ïfæ<ïù¸çŽÀبn,‚A•I•YÕäPæá7ßñ£à5UÆo‹ïàŒ §éØÉ)'¸ý£$ýj/GMyy Æ`ìƒ!|Ý([£§Y†­ J]P.ûD– ¥ Á£8³´“µb¬f¢Ž'¿‹íŽx$¹¼ØNU³Ó#ßDI†ýpDtm¥ÐÄH °/,3kpU¡òS†·á °uÊõ”øŽn²³d؎ L>ú…eÂÞz€Ã Ïjm"<ÉÜ98¼! Y40¿¥hìKEPËzsvèuˆ%p¡Ú¡¥ÀE]‚åõw4²h0‰p8¬Z£K`ÌXäÚ*ü™hâQü1r‰ÉÊ%¬¹×9d‰Áq4N °| Æ@ÏÀøŒŸü˲ҰŠ.,¯¼Ô Ì_¾&èºØ†¤è½bàÄ}Μ·Èͤ‹up¸Üz &·?CJ5è÷€ù ‰”;>ó‰À¦Ë-H•ZÐíõa÷0Vç}ˆ†ûžªÅ3œ0˜2fC!¬ÉR“$ȨÙíÏV™;ÎôàµErlD­›"ËëME¯— 3§‹<Ø\qÚ±õ{‘FWyÞ¶®z7™P$V 3Ážy¶Q˜w,1Sö÷¯òècºû"o™}¾M=Y\¡OÜ‚âÛ\ô´ÍGݦ’ÿ~LÿóœxWÌž%IEND®B`‚Slic3r-version_1.39.1/resources/icons/control_stop.png000066400000000000000000000006231324354444700231040ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<%IDAT(Ï}‘MKAÆýPóæ”}…Iï! ÅB]ºuèÐ)ˆ˜C(AHeãËÁÚ–µŽ:Ö’”ôô쬔lÏeØßïÿ²39äþÏ÷¡#Úª¥›Ö؆¾U×"#tdÛDˆ±`^1DÝ\ÉØÍÌ1ƒÃO1.]M.67 ž` ËŒyE›sá…–Š2ø‰º8Q^hê˜m¸ÎäQ$r“1Ž´îìSâˆ0ï…G>Y‘4xx>PE.YfÊ­`È‚ˆåEÉS7âÇßDZ:Dà*råªå¡©sDÌÙ]hTLIfë@ì¨m½e˶¤7UYüzÍ¿ò(Þyv-|úHIEND®B`‚Slic3r-version_1.39.1/resources/icons/control_stop_blue.png000066400000000000000000000012671324354444700241200ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<IIDAT8Ë¥“MoQ†ûüüXºtC¢û&FMܘ°1qÑ´º!n\4¦KÓ¦µJ‚&Ô¶©I¥#ˆi,SJ>,С03ÃÇPŽsî0#jãÆ›¼››ó¼çãž;S“º¶6]]N]Óc9Çw¶?ã'Á+ºìsodWøÓ)?Ì1GÇ'›J ûæõ‡"=]ƒ±¿Œá«ÁhÍÍž8¾)ƒ¢@»µ:}¨ÖEˆ¤¸¥‚cMÓÀŽp*óMéö4À£ö‡ *tµ{Cr§¨}ØO$ÿVMìÄû²1³ 7ä>Ôô— Õ¤TÅže²³ãî¯ñØŽ aæ;eÿ >mv‰ðdŠg@…â4²hàŒ}Í2Ø3–j‚7mz¸·½,Ë.È Î%–×7dÑ`:yt,à°š­1#‚¦Ð t®B±®£¾vkuYbpȦˆ–pEèZðõ¹Üò°PàU"£Š,¯¼² œ¿¤IéAKÕ¬^1+‚¨› †Aþ¬C^&W‘€ò¬Ý<]®6a4’Á,×ÌŠ`®Ölµƒáö>§ÁíÛ´†h{l¸v£ N)_fªm’ ¯g÷>_åîúrÆ3š‹D½eÝ‘÷Q…@©­YÕà2aæ\E?¨ÌR[¿ir•ç ÷j(ÂÅ“y(ó2™ öœ. °eažZâf¼ïþ^åÉÏtïeÉõøÅ6ýtq…¡žù„/-<ñø™YO˜¾Cg.ÿLÿóë8öÇu‚éIEND®B`‚Slic3r-version_1.39.1/resources/icons/cross.png000066400000000000000000000015161324354444700215120ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<•PLTEÿÿÿø`cÿtwÿtwø_b÷[]õVYýorø^aóQTòMOûil÷]`ðGJîBEÿsvþqtùac÷\_ðFIîADýnqüknïEHîACûgiùcfïDGî@Bø^aöZ]ïCFí?AöY\ë9<öX[è02õWZîACì<>å'*õVYî@Bì;>ë79é25ã "óQTí?Aì:=ç-0æ*,âðGJí>@ë9<å%'ä"$í=?ë8;ã!âÿwzþvyøadùhjÿ~þ~ýqtø_bûmoÿ|~þz}ÿ‡Šýy|ûilø^aúlnÿz}÷_aýtwÿ‚†üsvøbd÷]`újmÿy{ûmpÿ~€ÿ{~ÿy|ÿwy÷\^ÿy}ÿ[^ÿX[ÿtv÷[]ÿvyÿVYÿTWÿprðFHöZ\údgÿrtÿpsÿnpÿln÷UWî=?öY[úcfÿqtöXZôMPÿghõNPë46öX[úbeÿpqöVYòEGÿbcôHJé-/õVXúacöUXñ?AöJLê/1òLOÿÿÿÞ5¥>tRNS ++o>++¨)++oý++üüüüüýý+++++üý++üü+AüüAAýüAAh,1|bKGDˆH¼IDATÓc`@ŒPš‰B³°²Aøvöì šÃÁ‘“ Ìwrváf`àquswååc`àððôòòñõó)   —€&)#-³NV.6.>A^n¿bbRrJjšŒ¯œž‘™•“›§á«æ©©—”–i€øšå•UZÚ:ºÕ5µuz@ýú†FC#ã¦æÓV3sma á30XYCh[,%—$cŸÌøÑtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/delete.png000066400000000000000000000015141324354444700216210ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<›PLTEÿÿÿâmmájkÝjhÜdcÚdaÙc]Öc[ÔbXãlmßjjÑ^SÎ]PàijÌ[LÊXHállàijÇWEÅVAàijÂT>ÞjgÀS:Ûid¾O6Úc_¼P2×b\ºK.Ô`X¸J*Ò_TÏaS¸J*µK)ÍZMËYI·J+µK)ÈXFÆUB·J+ÃT?ÂP:¾S8½Q5¼K0¸N.·L+µH'퓎øÃ¾ùÓÌùËÄô´ªâpfò­ªýÜØúº®ú£‘ú‹û©œüǺé{pó§£ýÛÔúš‡ð‘ñŽzø”ù’~øŒvø¶¨ãncêƒ}üÔÍ÷“~îŠuö„lóycø¯¤ÐO>õ¯¥ú«ðŒwì_TòwcôŽéxù¹¯ú“ð…pÿÿÿéYMîj^ñ–÷«¡ø†pö‚hêf\ñœ–îŒ÷‘~ós]é[Oðƒ{â{uÛVKö«¢ðdVîfRæXLæZRõ£ŸÄP4ãkaõ¬¡ê\PæYNæVLæVPô¢žÖ`Tàe\õ¦¡ï†~éc[ç]Yî„}ô ž×]QÌR<èzuî’äxqÁM3&¯‘3tRNS#}ÛóóÛ}#SææSôôS"åå"~~ÛÛööööÛÛ~~"åå"SôôSSææ#}ÛóóÛ}#w¤öcbKGDˆH¿IDATÓc`À™˜YXÙØ9`|N.cS3s n(Ÿ×ÒÊÚÆÖÎÞÄttrvqus÷ðô ˆxûØúúùA@`(P@,8$4 Â#"ÅQÑ1±`Ÿ JLJ† ¤¤JdÒÒ3âÀ 3+[( —“›—âËKJÃÊâÊ+*«ª•”AQQ­©­«ohljVS‡8UC³¥µ­­½CKæm]=}C#¬>ìÏ0°§—ÃtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/disk.png000066400000000000000000000017161324354444700213150ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<ûPLTEÿÿÿ6k¼6k¼6k¼6k¼6k¼6k»8k»>p»5i¶6k¼ßéø½Ðì^‰É5iµ6k¼>p»6k¼5i·7kº1`§5jº1`§6jº5i¹5i¸1`¦1a¨6k»6j»9l¼;n½:m»ÑàöÑà÷øûþ÷ûþöùýðõüêðúíòû÷úýëñûÑßö€ªéöúþöúýdŒÈîóûêñûòöüñöüâìùÛçøºÐî½ÐìÐßö~¨èéñúîôûèðúÝèøÛæ÷z£áÃÕïÌÝõ~¨çfÉéðúóøýøúþïôüßéùÛç÷Ùå÷x¢à©Âç5h¶ÉÜô}§çáìùãíùîôüó÷ýåíúØåöw Þ¤¾ä4g´ÇÙô}¦æeÉgŽÉl’Ëm’ËiÊeŒÈtœÚŸºá4f³ÅØò{¤ãz£ãz¤ã{¤â{£â{£áy¢áw ßvŸÞtžÝrœÛtÜšµÝ4e±ÂÕòx¡àužÞs›Ús›Ù•°Ú3d¯¾Òðz£âwŸÞvŸÝr›Ùq™Øp™ÖŽ«Õ3c­6jº»Ðïz¢âm–ÓŠ§Ò2b«8k»¸Îï÷úþˆÀbj“Ï„£Î2aª8l»¶Ìîz¢áÂÜ¿hÍžÌ2a¨³Êíz¢àeÊ|›É­Æë­Åê|šÈy˜Ç5h·5hµ4f²3e°3d®2c¬2bª2a©1`¨ÿÿÿ€\WtRNSqÌîúþïËT›þûìcØøðþþþÞíµîñÄéÑî3bKGDˆHøIDATÓc`dbfae“‘••“W`çàd`àRTRVQUS×PÕÔâæáåcà×ÖÑÕÓ704R66153`´°46Ö·²ÖTµ±µ³wb`ut²±qvqus÷ðôòöñe`óó Ò ñô `‰ŒÒމ‹OÐ××OLJfMIMKÏÈÌÊÎÉÍË/(,b-NKËÌÌÈ. +-(+¯¨d­ª®¶ÏÌ*©©-¨«ohlbhniU†ƒ¶ö†Î®ìî(Pííëg˜0qR÷d(P2uƒðô3g‰0ˆÎžƒ0cî<1q Éù ".Z¼dé²å"RÒ[QÌ‹´ tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/error.png000066400000000000000000000014501324354444700215070ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<ŒPLTEÿÿÿì¿=ë¼<êº:è¸9ë¼<çµ7ë¼<éº:æ²5ä¯3é¹:ã¬1é¹:è·8á©/à¦-è·8Þ£+è¶8Ûœ&è¶8æ´6Ù™$Ø–"æ³6ÔŽæ³6å±4ЇÍ€å°4Éyå°4Ãmã­1Áhâª/¿dñÔ…ðÑ‚ýúñúïÕòØ”þüóþúçðÓÿýùùé”úëžþúìôß©ýùíÖ£>üõÔñ×¢íÈqÿþùôÜ^Õ¢>ôÙ\þûíå»h÷êÈýúæôÚ]Õ¡=ò×WüóÇôãÀðÒŒþûêøæ”ôÚ\ݱGò×VõÛ\ýøÞçÀ}þûóûòÃöÜ\ößdëËWòÖUõÙTøç”ûôãñ×ýùçøå‹öÛZôÚ[òÖTõØRôÖPüöØå¿ˆè¿bþüôúïµõÚXóØWò×XòÖXôÙWõØQô×NöÚbÒ“Dõã¾þûïþûîþüïþûìþüòëΫà§-ߤ+Ý¡)Üž'Ú›%Ù˜#Ö“ ӌυÌ~Éw Æq ÃlÁhÿÿÿ àq'tRNSÔÕiòí´–0ýü0ÔÃZZ ìä ª‡$ûù$Ê´KKãØ¢¢ì½sabKGDˆH¶IDATÓc`À™˜YPXÕ5ØùìšZœ\HÜÚ:ºz<>/Ÿ¾¡¿\@ÐØÄÔÔÌ\ƶ°´²¶¶±µðEÅ윜]\Å%À’nîž^NÞ>¾~R ¾´Œ@ gPpHhX¸¬P@>"2*:ÆÉ)6.>!QA1)9%55-=#3+;Ç=W‰A9/9¿  °0¿ÀÖ¶¨¸D…Aµ´¬¼¢²ªº¦¶®¾¡±I Ãß7+&Nµ8M„tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/flag-green-icon.png000066400000000000000000000012401324354444700233100ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<2IDAT8Ë¥Ó]LRap×mÝuÑæE7Ýô1KO¹b]¸”YÐ*ËåÚrK3Gæ\³@IÇŠ€ üÈ $ñ!‘„A*â”ÔŒ\NmP8‰ÔZ[›[YÿÎy×Ψ-/äâ¿÷â}žßžsö¼2ÒÉ/ o–S§êO>ÛT¸ÎÓòÖ¹ŠâUÅå¿ü|&è¸èß8c P罈`ržÅ'L_C‘Š‹k_ðLz 8T!Û•Ëçx–ðíŸ$ êàmŒ-`ruCIü /n>øe躾. ûŠrë eÉ×@r€.zJ G–‡ñ|e Žøc ~ìGW´ú·Vhª),· ¥t'ì ÀÞªÎ]ôhß»cFˆÂå$móR˜#x—tú`Žj¡7Ã^Ga¡·šiûOÖ*j.´ü´½7¡>\[áJX¢:ô'Lã(óõ6ü pĆ‡Ý¯ Ð-´¢á•ÓUP½‘ÁÓ#´2 ß’›LÒjk¬©¡¶˜a,©ñ…#ÞáË2ÔN•¢yV‚Þ¸¾„¶˜7F+Qþ²»ìp4f€;,pD%üd‰èÐC3qÆmO ÀsQ(³ Ö´sž‘ß<¹3ú"tv3ÀQÈ»[ò.ßÌ·/\'K.ŽÉøßRw‚n8÷àê2Aê÷o¸Hÿ÷œNZÀü„†Ÿ@š•ùì o °‹s@´@ýç2ç¶Ô»ß/B¸R*£ø¦IEND®B`‚Slic3r-version_1.39.1/resources/icons/flag-red-icon.png000066400000000000000000000012311324354444700227620ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<+IDAT8Ë¥Ó_HSaÇqéQ‚.,-a[–¥”®œ¤‚ ÁÐj™aHAQFe[ƒAÚçT¦†Íajþ)´˜¸2-3c¥Ô…&Eÿ„,XcnoçœbXzñã…óò|Îó¾7ô?„ZS§Nøg4¼¹] ºÄ2# ^9Ý9Y{®]G=<[ï껪`¤´dÊeÚÎXgáßÀ`Â*ƒ`1ÃPyÙp,LhvÀøô܇{í ——óʾŸ¾ë;%À¾¬W´ŠÊ­gî‚¢sÐt Ü.‚7«~?”õïO¸òrd ù|Š…_lx¿`-…†Z1v„ŽVü: “k³}{³þ}~nÕæ¨‹U­ð;’bÍœ ±@Wy|ƒÜÁÌóÏ:HÿÅ-¶Í (KŸ ?0íðœçÙd 8=ÀòçJëò™{¿B%¶Ë„¯½ªIEND®B`‚Slic3r-version_1.39.1/resources/icons/funnel.png000066400000000000000000000012011324354444700216370ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<ÞPLTEÿÿÿŸŸŸ›››ÝÝÝÌÌÌ———ÖÖÖ“““ÐÐÐŽŽŽŠŠŠîî†‚‚‚äää|||yyywwwªªªªªªªªªªªªªªªªªªªªª¢¢¢ŸŸŸ———“““ŠŠŠyyywwwwww¨¨¨¸¸¸ÇÇÇÔÔÔßßßçççìì쥥¥ÜÜÜÁÁÁ¤¤¤‰‰‰www¢¢¢êêê°°°ää䪪ª´´´ÂÂÂÌÌÌ›››ÝÝÝÀÀÀ———ÖÖÖÐÐПŸŸŽŽŽ¼¼¼ŠŠŠ†††îîî‚‚‚|||yyy···£££ÿÿÿ}¬ôµ"tRNS#W†¯Ðêú@€@€@€¿€*‰$bKGDˆHÂIDATÓ=È×ÁPEÑÁè½÷è-Hp!DtÿÿEÎõà¼ÌìET,•+ÕZ½^«VÊ¥"õÃÑx2Nƣ᠘™æ|±´¬åbnš3@Ã^ýg7æzƒ©íNT3†öâ8à°ç i]×[Dc±¶}:©í¹ÍęٻˆRž8 A”4|Q¾@P ¼ÞäŠf¥™ûFî”D®<4 (à§ÈS‚ò^ïOG‚ zV·÷»ú‹ð6ƒbž_tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/hourglass.png000066400000000000000000000016131324354444700223660ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<ÅPLTEÿÿÿןqןqןqןqןqןq¾†N³{?¨p/žf!•]W ןqW Ö£z]ÅÒᔥ·¾Ûú”¶ß¹Öö¹Öõ—¹â“µß°Ïñ±Ïð–¹á’µÞ¦Æê•¸á¨Æé’´Þ¦Åé°Ëꈬ؂§Ô£Ãè¬Èé~¢ÑyŸÏ Âçq˜Ê§»Óq‰§Ñ™iÈ\žf!•]W W W Ñ™iÈ\à·‡æÆ•êÏìÓ¡ëÑ èÊ›å–âºÝ±‡Ô£yÄa®x>éÍ›éÍœçÇ™äÁ•ฎݰ‰×§Ï›sÂŒ]ÍÅÀÜ·šÕ¦}Ê•c¾‡O³{?¨p/Ÿg#˜cšk,§ƒSš”ˆÍãùìóû÷úþøûþíõüÕæ÷ÌáößìùÝëù´Îëàìùôøýæðû×çøÞëùÅÚòÉÝóâîú¶ÐíÎáôÌßóÐãôÍßóÒâóíôüçñûµÌéÞê÷ùüþò÷ýâíú·Ðë»ÓíåðúõùýúüþüýþôùýÏãöÓå÷Ñãöš¹Ý¥ÃæÀÖïêòûýþÿðöüÙèøÎâö­Èæ{ ÎÔ¹˜ÄÇÅÇØìãëöñõúÞçó¦¿ß€¤ÐtšËq—ÈŒ“£œbÿÿÿÛÖú¿4tRNSn³ÝôýýôݳnÄÄÑÑ**¸¸¯þþ®8ÝÛ8ÃÂÇÃ:áÜ8°þþ®¹¸**ÝôýôݳnK–RbKGDˆHÓIDATÓc``dbfae31eçàäâæa``à53·°´²¶±µ³wpä ð›™;9»¸º¹{xz9 ½}|ýüƒ‚CBÄ€ Âá‘QÑ1±qñ " ` *–˜”œ’š&.Á’Ré©™Ò2  ›•-Ç€ äsr¸ŠJyù…Ê*0¾ªZQdqIi™º„¯Y^QYU]S[Wß âk765WµT·¶Õ·wtê€üÒÕÝÓÛ×?aâ¤ÉS¦òA<‡ð Hè}]=ö€@}C#c‰z2œâ{ËtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/house.png000066400000000000000000000014461324354444700215060ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<¸IDAT8Ë“ëORaÇÏ«óGø®­Ëfá%Ë0ÍF¶…3`¬fe®QMBàaÌ€)ËËlކ#Š"Í ÓÀr^›Õ4Ñ6S»P¼2•3™ö"ýv8•­Òæ‹ï›g¿Ïç9Ï÷y€Ø*ÝEûI:q+ΣH$·›ÛÂEÂZŸ:iÍ[~0ìÕ"w$è)>@Ò`dØÄÅ„ó:^»Šá¿ ŸîHħg“ÿxä dŸ*)2bÉÁäC9&ÝJ;Ë1Ú"FŸ.-â©N#·x”‰dY25ÚÀCð‘þF¼µéxjÈÀp“ãî* XEp—±©ûŠTòA¿:™ô–§P~+ÁÇ*ø›„¨ã`zj’Io->‹{jtëù°IS¨æKÉŒ„  "é‚(ÿm‚eô§Šà5žÀËcX\\Äòò2æfgàTC‡–‡gwn ýæ)˜„,J:ž$|Õ©ÓÙŸçÌ£á,LŒ¿ÂÒÒÿÊìÛ´²qWv]æ h,Ì@9go€ ›åzõiëÃÍyèÑfn|x7·¹óßùôñ=jrXh¸xe™{ÖépXÆ}¢aXë-«««Ì°R©„J¥‚Z­Fii)³Fa1Ý‚š?ƒÿ¹F“É´BQ3[[[ÑÖÖ†ÊÊÊMAL*‘Hömù ÃJ$a†+**`³Ù`·ÛQSS³)Ëå …¿EÏ/'t;/¶óÄþ7ÇP;bF‡Ã§Ó Z̬=xcGA§bo:ÛÊѦkó þ,뽊ªÁõ Àšp8 N—Ë·Ûºº:„B!hT¨ÓC;¤D¾C„DY|” w¤m!Ú8.K§x×r¤Ré¼Ñhd@º˜Íf‚ùÜ’3 ÙµY_Ó5©c4üe÷•]ƒÄv¿éNó}¬‡îvýTIEND®B`‚Slic3r-version_1.39.1/resources/icons/infill.png000066400000000000000000000003621324354444700216340ustar00rootroot00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<”IDATxÚbyþÄê?€DHÊcD¼˜¤æq"TÌÖZ?u²:åLØL…*L„jĪ˜(X @óØ%P1ì K"k9Ù;XÔ10‚â}®!¡Ã°‚“Ïý@0À°,U‘`€a XxBBv6P!Ô$±tïÁÔq„§DFJ³3@€*o‰êAØIEND®B`‚Slic3r-version_1.39.1/resources/icons/joystick.png000066400000000000000000000010571324354444700222200ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<ÁIDAT8Ë•S=hÂPþž4At±¸”¢âà ´d)tµ n ¥àصÐEpº•ή"B77±[D§`‡HÑ‚à¿ñ/}÷ Al=8Ž—ûî{w—ï1Ó4qhÅb1´ÝnÕÕj^¯×X,?Óé4™Ëå´CìÌ0Œ@ öx< ]׃Nç§î±.'‚ÉdrÑh4Ðl6ÛíÆh4 9a 8Ùl–õz=A°\.…M ˲ˆŒ1A`‘MÀw€t:mú|>ðE §oNæ¸D¾q(ŠI’Ðjµà÷ûO뀃MºQÓ4(ó~ûPoo®ñúòÅýÙ±ƒr¹æAÛív2w10ĹÁÅ㘱d+½›ü.&ßR©ËR©”· ¨˜ J‹F£rµZÅ`0@¡PsŸ+WPj50¯R¯"‘H.CUÕ<#%r6#‹ÉœãñXrõÙ>ŸÏíèõzár¹ÉdÐn·™è€'e.]T*‘X˜UD>›ÍÄ™¾Ó9‘Hˆh@ ºu8Ú «p¿ Gº _kP’|6‘°ûq³Ùüs› Ûí¢^¯Û¢9tKTD£î¿`æôœO±?Òc¶ ™¶IEND®B`‚Slic3r-version_1.39.1/resources/icons/layers.png000066400000000000000000000021441324354444700216560ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<(PLTEÿÿÿtÇhsÆhqÃen¿bj»_f¶\b°X]ªSX¤OSžJN—FI‘AsÆgpÂem¾bi¹^e´[a¯W\©RW¢NRœIL•EG@Bˆ;oÁdl½atÇhsÆhqÂen¾bjº_eµ\a¯X\©SW£OM—FN—FI‘Ak¼`g·]sÆgm¿cjº`fµ\c±Y_«UZ¦PU LPšGB‰=G@Bˆ;f¶\b°XnÀci¹^sÆgrÅgpÁdl½ah¹]d´Z`®VY¦PV¢MPšGN—FI‘Aa¯W\©Rj»_d³Zfµ\d²Y`®W]«TZ§QV¢MP™GN—FGŽ@G@[§QV¡Meµ[_¬Ve´Za¯W^«TZ¦QV¡LQ›IJ“CG@A‡;A‡:TŸLO™G`®VX¥Pb°X^«TY¦QU LPšHK”CE‹=A‡:;5:4N—FI‘AX£NM•E[¨RW£NRJM—EHACŠ<>ƒ7;55x/4w/TŸLO™GY¥OTžKN˜FH‘BCŠ=>„89}34w//p).o)N—FI‘AQ›IK”DFŽ?<67z12u-.o))i%)i%O™GJ’BEŒ=?…9:45x00r+,m'(h$$c sÆgBˆ;oÁd;€5k¼`5x0f¶\/q*a¯W*j%[§Q%d!TŸL!_N—FI‘ACŠ<>ƒ89}34w/+k&'f##b ^[ÿÿÿâá‡ëžtRNS777777777777EEE ‚‚‚‚‚‚‚‚‚ ~~E ¢5:?BFJLPzJžE £=ØÙÚÛÜÝÝåÜëËËE £DzŠ–¦²¼ÑÆß±E£Jœ©¶ÀÇÌÛÐâ³E¤R¸ÂÊÐÓÖàÕâ±EE¸}ÖÚÝààáá×ᯞRÏÓÕÖØØÖÕ૞žãäâáààßÞ¦³²±¯®«¨¥¢¡¹çãßbKGDˆHßIDATÓc`@ŒLÌ,¬lìœ\Ü<`^>~A!aQ1q °€¤”´Œ¬œ¼‚¢’²ŠªP@]CSK[GWOßÀÐÈØ(`jfnaiemckgïàèäìÂàêæî1ÏÓËÛÇ×Ï? 0h>CpHhØ‚ðˆÈ¨è˜Ø¸ø„… ‰IÉ)‹RÓÒ32³²sró3ä-).)-+¯¨¬ª®Y 4¶¶nY}CcSsKk[{Çr @g׊îžÞø¾þ 'M^ vܪ)S§MŸ1sÖì9sWƒÖ¬]·~ÃÆ¥›6oÙº ’eBüÄÜGtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/lorry_add.png000066400000000000000000000012611324354444700223350ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<CIDAT8Ë¥“KHÔQÆÔR‡4J|…„fEeEb»ÊBªmЦˆˆ ÜD7AHÑ¢2z¬ŠB%m&PÙpÑ[\ØC!z‰(Þ{ν-æŸ  žÍw¿Åù8çûî ¼÷,¤²X`E†ïžSÕrçUAÕ"b1³h­AÄÄö_èÝû—€ª–¯ØÑ„÷ŠW‡w‚sŠWÁ«Å©àUxÕu{Ï?'±8'ÌL¤ð*8±8µƒCn´”›…mÜ8ßãUU±r2~i_GDÄPR{à¿»jo7çÕ¡Î3öu*ûjçË‹@Fàì“ §&Q㣨QdFgyMY>b#žu•El^]Ä•ûýÅkg8±<ÎϬÑpd‹†£ÿæK–Ur|lÑÅ£ŸÇ©®(Âˬ€³5é°ÑÎ6:1#ÅbÅŠ#96EÚèc¦Q1ä–d—Ðy'aŠKÙÒA:{1•§ï…!Z°8Bpp÷&ï½Ã9‡÷¢ó>ý'Z[BõÞ¶¬ÝJEñz†Ò?ØÇ§‘o—³<} ¦ìÇ¢ªzÊÖïdøk®¬k"ºj;#?rº»ïƒìªüéM5Ñ,eci#Xê74œŠ„)N$m@¸‹ÅšÃ÷I€ñ©ïy‹‚šÖàÌ®ºÚÇy€d2æÄ~oîø>ñez(õ<ïm*NKãZcGÉËΘž×19ï.÷¿{NºÚÉ "Äß<¸Ì÷œë›ËZÕ I ½ÿZªå<0€y=XöIEND®B`‚Slic3r-version_1.39.1/resources/icons/lorry_go.png000066400000000000000000000012731324354444700222150ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<MIDAT8Ë¥“KHÕaŵËèš©i))QjP‰QQ-ÒDE‹ U»‚hSë1° …‹P´‹­¢K“k¡µ 3{ˆ¦·H ­ìþï÷ÍÌ×⪛Š†Ã™ÅáÌ& !0—ÊbŽ•Óëôˆª–š)ª‚ªGÄ#âfÑ{‡ˆk;ÐÔ¹÷7U--ܶ” F0ÁL *õ˜ A…÷nîù£™žLT0ñ˜ú ŠÃı`ñr®åµpåü“ ¢¨¨Š—“]—ö_ÏqUüç®ÚÙAãÑZÔ#c?²[ïö^2çî ?ùu†8E"iåkK"^p¨)O°qU‚Ë·ŸçäxŸæÄ².~f M[öè´õž»´œÖüvîôLqlÞjÊŠñÎgÎè}óu)dºÕ¥P£.Æ|Œ‰'ÇŠ‚*Z;wÒóntV ǹÇü¼¢Lâ’Iþê·6B¶Ç™à&ú)Í_MUñf¾ÇS\ìØ„êY¢#»6„ 3##Lcéñ»kŽ¡ÁPSŒÀçÉ yöá1½CO‰5½$ëΣ—Ñ«¤LTn¡d]=ýcQª¼v±84CãoàýX©q†¾¾gÃʬ)©#–0…¨®®Þ´)àpªö [­?˜"¦h0>M“¿¨˜—»yôúáW§4D{¦ÃW+,vgŽX•5Ñ–Š=ô wÓ>ÐöÙ ÉæÐýï7®kÊšª+«ÏíhuFC²9¼øoŠÆhJ-;7eZñ¥9 ÎÌn¡½þÈèIEND®B`‚Slic3r-version_1.39.1/resources/icons/note.png000066400000000000000000000013441324354444700213250ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<tPLTEÿÿÿþß¶úÒ¢ùÑœøÍ—÷Ê’öÆŒõÂ…ô¿~ôºvñ¶nð²eð­]ýܲÿþúþýúÿýúî¨SöÌ—ÿýùþûðþüðýûîýûïýûðþý÷í£JõÈ“ý÷áùêªùè£øçøå–÷ã÷áˆö߀õÝwûñÈëž@ôÅŒý÷Ýøè¢øå•÷ã÷á‡ößôÛmûðÄê˜7ð¹yüöÛøçœ÷á†õß~õÝvôÙdûðÀé”,î¶qôÑžðÊxðÇrðÆní¾`ôÛlóÖ[ûï¾è#ë­^ðÃüóÓüóÑúí²í¼[ôÚlôÙbóÖZóÔQûîºåŠì¬]ë­Uë¯Gùé¬úê­ë»VóÔPóÒIùí·ä…ë«]øã´æŸ2é«;øé¨ëºNòÒHòÑBùí´ãë§Uùæ¶ñÑ•è§Kë¯ZîÂ}ûî»ùîºùí³ùí²â}ê£MèžFçš>æ•5âŒ⌠ä!ã‰ãƒá à}ßyÿÿÿ’í¡›tRNS@æØfbKGDˆH°IDATÓc` 021³°²±sprqó€xù@€_€O@, $,""*&..!!)–‘•“WPTRVQU ¨khÊkiëèªèé€ Œµ´MLÍôÌ-,ÁVÖ6¶vöfæŽNÎ`W7wO/o_?°@@`PpH¨·OXxD$X *:&6.Þ',!1),’š–ž‘™•‘“›È/(,*.)-+¯¨¬"Æ«ì »ÕnMátEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/package.png000066400000000000000000000021321324354444700217470ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<gPLTEÿÿÿØ´MزK×°JÖ®HÙ¶NØ´MײKÕ¬EÔªDÓ§BÚ·OÙ¶NØ´LѤ?Т=Ï ;Ù·OÙµNÎ9Íš6Ù·OÙµMË•2Ø´MÉ’/ײKÈ-Ö°IÇ+Õ­GÆŠ(ÔªDŇ&Ó¨BÄ…$Ò¥@Ã"Т=Ï ;Î9Ä#‚"€ Íš6̘4Ë•2Ć%Ä#‚!Ê“0É.ÈŽ,Ä…$Ã#Æ‹*ʼn(Ň&Ä…$êÕœç̈åÏ’üøæþöãòݯ÷ä²áÀràÆ~÷îÑÿÿöÿþðûíËÚ­P÷׋ùáªòÖ•Û²bíÙ¥õëÒíÞ¾ÞÉ•ÛÉָpݹiõ×õÏøÛúãªìʄզQþøáùëÍíÒ’ëÌ„å¿oÛ±VÈ”;úìÎÿõÙÿê¹þß•ÿÕwÿä¥ûÜ„ýøâÿóÔúä°ñφõÐõÐxä±Lÿé¶ÿá›þÖxõ½@éµ=ñÕýöâÿôÚÿóÕÿë½÷Õ‰õÉié´LûÚÿÜŒùÄHì¶8è¿Hè»OðÐŒüöâÿô×ÿòÎÿïÈÿëºûÛ’ñÁVùÂHð½;ìÅGé½EæµBæ±GîʈÿðÉÿí¾ÿê³ÿç­þÙ|ïÇHêÄCè¾Cæµ?ã«:á§@ìŃþûçÿöÝÿïÁÿë·ÿè«ÿä¤þá–ëÆHé½@æ´=â©8ÞŸ2æ¶kîăܽwöå¿ÿöÛÿîÁÿå¥ÿãŸþá”êÁFæµ:âª7å¯VíÆ‡ä³tÊJàÀ}üîÇÿòÌÿè¨þà”çºAä±EïÊŠëÁ‚ЗSåÄ‚ÿôÌÿïÄñÒ‹ðΊפ_ëÍâ·oÈŽ@ÿÿÿŸ™pÜ;tRNS+¯¼1„ïóŒeÛàl FÂÈMŸþ¦ûûððððððððùø»ÁeÛàm …ïó+¥þ¬1F¾ÄMñ*NbKGDˆHùIDATÓc`F&f`ec·¶áàä‚r¹yxmíìøø@|A!gW7wO/oaQ1_?ÿ€À àаðˆHq‰¨è˜Ø¸ø„Ĥä”Ô´tI©ŒÌ¬ìœÜ¼¬ü‚¢âi™Ò²òŠÊªêšÚºú†Æ&Y¹æ–Ö¶öŽÎ®îžÞ¾þ ò Í™'Mž2uÚô3gÍž£È 4wÞü -^²tÙò+W)3¨¬^³vÝú 7mÞ²uÛöª jê;wíÞ³wßþijiª£«wøÈÑcÇOèB}cdlròÔiS3$ÿš[XZAXç2R95uºÒtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/package_green.png000066400000000000000000000021351324354444700231320ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<jPLTEÿÿÿØ´MزK×°JÖ®HÙ¶NØ´MײKÕ¬EÔªDÓ§BÚ·OÙ¶NØ´LѤ?Т=Ï ;Ù·OÙµNÎ9Íš6Ù·OÙµMË•2Ø´MÉ’/ײKÈ-Ö°IÇ+Õ­GÔªDŇ&Ó¨BÄ…$Ò¥@Ã"Т=Ï ;Î9Ä#‚"€ Íš6̘4Ë•2Ć%Ä#‚!Ê“0É.ÈŽ,Ä…$Ã#Æ‹*ʼn(Ň&Ä…$êÕœç̈åÏ’üøæþöãòݯ÷ä²áÀràÆ~÷îÑÿÿöÿþðûíËÚ­P÷׋ùáªòÖ•Û²bíÙ¥õëÒíÞ¾ÞÉ•ÛÉָpÞºjõ×õÏøÛúãªìʄզQþøáùëÍíÒ’ëÌ„å¿oÛ±VÈ”;úìÎÿõÙÿê¹þß•ÿÕwÿä¥ûÜ„ýøâÿóÔúä°ñφõÐõÐxä±Lÿé¶ÿá›þÖxõ½@éµ=ñÕøöÛÿóÙÿóÕÿë½÷Õ‰õÉié´LûÚÿÜŒùÄHì¶8è¿Hç¸HñÓÔç’Øå€ýñÈÿïÈÿëºûÛ’ñÁVùÂHð½;ìÅGé½EæµCÔ­9Ì҈Ɗ(âí¨³ÖAºØIääŒÿê³ÿç­þÙ|ïÇHêÄCè¾Cܳ=©®-ˆ±)»Ó…þûçùóи×F®Ô9ÃÙPòâŽþá–ëÆHã¼?±¯0„¬$­$¿gìăܽwöå¿ÿöÛë螬Ó7¬Ò7ÑØU¼¸7…ª$|ª"©·MëÆ†ä³tÊJàÀ}üîÇþòËÐÝe¨Ñ3‘°5èˉëÁ‚ЗSåÄ‚ÿôÌúî¼äшðΊפ_ëÍâ·oÈŽ@ÿÿÿPñÜŒ:tRNS+¯¼1„ïóŒeÛàl FÂÈMŸþ¦ûûððøúû÷þùø»ÁeÛàm …ïó+¥þ¬1F¾ÄM?€7bKGDˆHúIDATÓc`F&f`ec·²æàä‚r¹yxmlíìùø@|A!'gW7wO/oaQ1_?ÿ€À àаðq‰È¨è˜Ø¸ø„Ĥä”Ô4I©ôŒÌ¬ìœÜ̼ü‚¢bi™’Ò²òŠÊªêšÚºú†FY¹¦æ–Ö¶öŽÎ®îžÞ¾þ ò'Mž2uÚô3gÍž3wžƒâü -^²tÙò+W­^£Ä ¼vÝú 7mÞ²uÛö;w©0¨ª©ïÞ³wßþm:¬¡©tª¶Žî‘£ÇŽŸ8©§oõ¡‘ñ©ÓgLL‘ükfna aRÎÂ/ VtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/page_white_go.png000066400000000000000000000012761324354444700231650ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<)PLTEÿÿÿøøøûûû”””•••™™™ 5‡*{% {wüüü :ùùùüüüÕäÑ!| 2üüüøøøôôôõõõñññïïïéééççç÷÷÷ùùùóóóðððêêêöööòòòëëëûûûìììæææíííp¦>eŸ1íïëúúúi£9³ZW˜(öøôo¥=n¤9h¢5bž1|¯UªË‘t«OFk£7°Î–­Í”¨Ë¼t¡ÇŠh¥FcŸ1Žºo‰¸k…¶f€³a‚µg˜Âƒ\ <[›)©Ê¥È¡ÆŠžÅˆ…¶j—‚f¥HR—"J‘D<‹^Ÿ:–Á€b£F4‰3ˆ _¡C6‰{ÿÿÿÆ"÷–tRNS36 ðý‘‘‘?ýüî1þW4õýõ÷PZ¤€bKGDˆH»IDATÓMÏÕÂ@EÑÒâî6Ð-ÐâîîîîüÿO0a?®äææ„ˆ¤pb "¥2ðÎ%ÿ%sÓ4Íx¼À§@¢¤Ë±¬?¼[%~Çq,/„Ð¥D 좙H0ŠEðÄþJ<‘üJg²¹ä ÅR¹R­©¿Po4+­v§«Ñbè5ûƒáh<™ê0Ìæ‹åj½Ùê ŒvûÃñt¾\Mp‹Ùbý¼¹Ý6‚ÝŒ×;žNÒN¼ ‹Û@;=Œ9:Š6€Íu‚Íx§Ùœ¢Ø˜¢Ø——ÒƒÐyÎv~Ët{Éq¡×—Õ“€Îv¦Ù›¤Ø™¢×˜¡×–žÕ•œÔ“˜Ñ•ÐŒ”ÏŠ^­W}Ës£Ø™‡ËywÃisÂeqÁdsÂgqÀf”Ï‹Z©SzÇpwÄm…É|rÁeoÀbl¾_i½]f»ZψmµfR¡LNHJ™Eo½f™Òm¿`j½^h¼[d»YaºVo¿e‹Ìƒ‰Ê‚ˆÊ€F–Ak¹c–Ñi¼]cºX`¹U]·Rk½bˆÊ…È~„È~B’>n¼ej¸bzÀr“Ï‹d»X_¸S[¶QYµO‡Ê€b¬\B‘=?Ž:•Ï‹’ÏŠrÁiŽÍ†ŒÌ„ŠËƒf»]E”@e´^‘ψa¹VÍ…lµeOžIc­]ƒÇ|Æ{A)0oBDQn¿Å—2½QϾ+Qn.‡š,T‚ Á…B˜§©¡ŠÂ2é=ˆÊÒˆ(ꊨRùù-f n‚а±¾…¶ {ùQÌsoðZ¶íÀɾã.¢Ô­ªC͈-áPúØoÃÉξggÓ>æ’C“èèŠkË/§ÛPœ‰ïã´×wðòÃ(êÆ»é×ÄAßç°{ó~æÂOMôO—åYn˜ »ÿ4AQŠˆ 5[3§f'©_³á÷ ¼éÿ’‡KAÍH¯ë@ÔÈ~Â¥è7/“X%akCšLóaž¼ë§oìî§DèÌvû«áÉØSDAD‹·NŒW¼z@+P†?<Œ£¸Šûc}£3Ûí£¿sð/Õ|!Ê«•­.˜6çº}bqþ ÒF•äóKQøIEND®B`‚Slic3r-version_1.39.1/resources/icons/printer_empty.png000066400000000000000000000006501324354444700232600ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<bKGDÿ‡Ì¿IDAT(ϥѽ+…qÆñÏÃ“Ž’”Éf9 %2˜°Éÿ`,þve‘ '³:«ÅnĤœ¤“³Îñòœ—ŸÁKOƒ{ºëú^÷}Õ¿OÛºøsÉ‚ %XRÀf§ÉA3ÊpÖ‰•Ùätÿ™(Ø\¶ÝßÕžrܹ¬&ë…|´17p<®÷‡ï%GÎçc[Súìü¬Yr³Ë`Qî›|!+«07;š¸–ýŠøóÊ*QÜPDÙ«j èr¯ˆ¢¸®„' I h¨)á^\— ã/jUT£ÔÅIë¡­[Y=ž,¸Õ-ï%Óõ±á”ûQÍ "áÌAôï6ßT(]¥rý:tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/reslice.png000066400000000000000000000012311324354444700220010ustar00rootroot00000000000000‰PNG  IHDR‘h6tRNSn¦‘ pHYsÄÄ•+9IDAT(‘eRÍjâ`½ILQÁŸRT´*¥$)Ôࢠ}q%n\)î|Šy‰élfúI»ÀA˜é¢Ã„QŠQ§à Vq š˜ïÎ"E2ÌYî=÷œÃe\ „ŒF£ù|ñx\„««+÷  ûý^–e7#˲an†ADBˆ®ë³Ù C¡(Š777ˆøúúªëúf³a&‘H\__¿¼¼€#s „(Š¢ªªªª²,BÜב+•JÛí/..q0GBÈr¹ ƒ¶mÇãqJi¿ß_¯×ËåÒ33™Ìn·SU•ã¸étZ.—O ý~¿eY»Ý.ëºÎrwyy™L&C¡P ˆF£ÃáRJ)‡±XììììííÍ4ÍóósŽãŸ¯×ëŽÝÛOßùÛ/¿±^¯çóy˲¢(¦R©~¿_.—%I*‹“É$N;Ù†ó‹Eð8^¯7—˵Z­R©ä0étº’2ŸÜ~üZÉ Ðn·5Mûçù8®E91ãñØñóþ|ð …‚$InFÓ´n· ʧâ­ýætIEND®B`‚Slic3r-version_1.39.1/resources/icons/shape_flip_horizontal.png000066400000000000000000000006231324354444700247420ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<%IDAT8ËÝÓ=JQà3ù1>4-ÙM .ÀÖÎz nÀf@D› ‚.ÂBA±ÁÂ4)Li)™Iî9×Â‰Š‘`ãYÀ÷Þ;÷¾ÈÝ1OJ˜3œÝÃémÞ€½‹a÷ü9ü =1!ÉcÊ“™ÞMÞ2y×ø^®$ê&½Ak&ÀèéúJ)L“cc­ŒJN®³v#DF- …PV—Ê£§ö·ÀñUŒžÖkrLÓ ¯±XÌ”n<†/£'Íz)®–£ƒ*ž`Âh,˜ÍF56*ù^ŽZFï./D˜§SÓ‰Ù˜åä ÕÝÚ¿ÿ(´R•Rà 耨Xq£0xÉ ”CîAòÀ&Dÿô/ü&o~ß°ŒÖÙ ÜIEND®B`‚Slic3r-version_1.39.1/resources/icons/shape_handles.png000066400000000000000000000010321324354444700231500ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<¬IDAT8Ë¥’1‹Q…¿‘QÐB\AØvI M~Â"ÿ"Xæ„ü„€d‹”I¿e‚v6ÊÚhé$M$³èÂÖ&˜÷îÙ"o& F ½ðàñÞ=‡sν‘$þ§b€Á`JªHJz½^ýo€n·û!Ïó—ιd2™Ôãð^i·ÛÌf³Ú›w72 Þ™ao°Ýnét:ŒF£Z©À{ÿu:6ãGOy~v è€äöÉ9ÃáçÜ'€¨È`ðö§ÎÏì’À¤’ÈL˜‰åMÆ ó"*äz$̰¸Û1Iîí8DyÛ5 Ì `%° tî30/,4íš‹»í•Ìéws†7•>uàYÁR¡Ä"p¼·Òÿ1É^‘ÎNYðïãz*Œò @IxRÁk`Áûá8²**’D«ÕJó<¯<|üŒW‹£Ä¥Kâòâ5ß“Ïxï“,Ëê÷²,«ôû}n,Y\¯Y\o˜§k«_ÌÓ5óÕ†dµ&I7,¿]1qÎÕvó—h6›_†U«Õ+…úÓ‰ãø}E>JÚ¯ò¿Öù5¼oqIRriYyE%D ªº¦¶.§¨¾¡± "À/ ¦…š…1=Ç|&‰P V¾tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/spool.png000066400000000000000000000010411324354444700215060ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA± üa cHRMz&€„ú€èu0ê`:˜pœºQ<ÏPLTE‘““€­­­ÂÂÂbccåääs}z=J'þþþ~‹‡¦Vv§Hnc7U.;ˆŽŒ¨ ¢Ú©Þc“®Swh*@G$…‹¢šœóËÙø¥Éù•»õ“´«Mr[,ššš¨y…ô´Èÿ²Õÿ§ÒÿÉßõ°Æ×\n3G«‹ävž÷y°ÿºÕÿÜêñ®Éð„¯A #r'Fé…§ÿÀÝŠ•‘µ´µqqq¦„ЍŸ¢‘™ÛÛÛ^_^¥ž Ÿ¤¢|||¬Š˜ ž£££nnnÿÿÿ±»‰tRNS@æØfbKGDDù´˜Á pHYs  šœ¢IDATÓUP‰‚P {9ñVåðoT¼þÿŸŒï¡Â’-i³¦Ý¡\ãs ‚˜% Hr±„ VÊj¥ZÃ×ÍV»£!ÕKÝžÞæí`$-{2ufæ\£î€ëùÁb¹2Ì5ÏÌFA0ÙXêv·¡K‡È·õã)># Ðs.W%a:ÜÛ]y¤¾Ÿ~‚‹“4“A|ý²³Ó¿xg N7ûêrIEND®B`‚Slic3r-version_1.39.1/resources/icons/tag_blue.png000066400000000000000000000013451324354444700221430ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<;PLTEÿÿÿËËËÈÈÈÄÄÄÊÊÊÀÀÀÊÊÊÈÈȽ½½¹¹¹ËË˵µµÉÉÉÇÇDZ±±ÅÅÅÃÃî®®ÁÁÁÀÀÀ¬¬¬½½½¼¼¼¬¬¬ººº¸¸¸«««¶¶¶´´´²²²±±±¯¯¯­­­ªªª«««ªªªÕÕÕÔÔÔÒÒÒÑÑÑÐÐÐÎÎÎÍÍÍÓÓÓýýýäääâââàààËËËóóóûûûÇÇÇ÷÷÷èóû½ãýÞÞÞÃÃÃüüüêõýŠÏþƒÌÿ¼âýÜÜÜÀÀÀëëëúúúçóû‰Îþ–ÔÿšÕÿ‹ÏÿÚÚÚ½½½éééÓëû˜Ôÿž×ÿ›ÖÿÙÙÙ¹¹¹èèèÖìûˆÍý˜Õÿ‰Îÿºáý×××µµµçççœÖÿŽÐÿÌ÷ûüý···ååå†ÍýŒÏÿ—Ñúó÷ùÕìû—Òý÷ûý¶¶¶ØØØÿÿÿ]´$tRNSÍùâö=ö;ö;ö;ö!;ö!;ö8ö8ö!8µ4Í‹bKGDˆHÇIDATÓcPQUS×ÐÔbd€mÐÕb‚ ¨éèiêëèè0C4t´X tôY!š:úlì&¦f:æ`-K+k[;{N—ƒŽ£“³‹«›­Ž»7P€‡×SÇËÆÛÇ(âçÏáÐ öñ Õ Š G€D"£¢cübE@ƈŠÅéÆ'$&ùÅŠC¬–ÔÕINIõ‹•‚9WZFOGÇ/ Îg`•ÓOGæ30È+¤)2 %e 'ˆ!oA¿TXtEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/textfield.png000066400000000000000000000002311324354444700223420ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<+IDAT(ÏcøÏ€2ÐAÁÉÿø!PÁ<ðDÁÉA ¯#_üÇA\+Šv*³Îæ“IEND®B`‚Slic3r-version_1.39.1/resources/icons/time.png000066400000000000000000000016771324354444700213270ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<ûPLTEÿÿÿ¿¨ŠÆ«~ͳxи{Í´zÄ©q¹q°™zª†Òºx¸œg©lÀ¨…Ÿ…d¹£ˆÌ³t¤ƒX›†j¼¡w˜|Y¾¢m›{S½¡lž{T¸šgyS­Ž^•uP …^tT‡k¢‚W”qM‘{bšawZ•|]šxP”rMvY•}a‘uS“rN—tMtR‘z^ãÙÈêçÝññîððìäàÒÒĨÜÌ—îëáÙáߖ­Õ’ªÔ£·ÜÎÚèßÙÆ¶›hìéßÂÏæ®¿ßÜãñìñóëñôÙâñ£ºÜ¯ÁÞØÏ·ÛÏ·×àíª½ÞÿÿÿîððÚåç÷ÿÿóþÿ˜´ÜÄÑỤsæáÓ¤¹ÛÖãñõûþóõõ•žŸéððòøø­¹¾ÂÚð‹§ÑÓÊ©îì䊥Ñéöúîøýùÿÿy€¤®±wºÆÉÓðúr•ÉâßÉìêÝŸÏâóúéöýãðò„ŒÂÏÒëûÿÌìúmÇáÝÁÝÓ·ªÑÁÛðæ÷ÿãòú¶ÀÅÞíñèüÿßõÿ¯Ôï{›ÉÐÂ’À©xÆÑÚˆªÙëþÿîúÿòýÿòþÿïüÿëýÿz£Ù¹ÆË±”XØÌ¢¥·Í‰ªØÏßïóöúóõúÑßט¬Ã킦ˆUÖÈ–¿Ê̆ŸÈv’ÅsÄ€šÄ¹ÄÃͽ}˜wG±—Zν…ÞÖ¤Þՠ˹{«ŒOŽê/tRNSB½íúúè°9–ùô}‘u<úõ1¶£çß÷ö÷öÞ⟪0ö÷5x€}óô‚6§áö©7ôß6FbKGDˆHÖIDATÓc`À™˜YXÙØ9`|N.}C#cnŸ×ÔÌÜÂÒÊÚÆÖŽÄç°wptrvqus÷ {zyûøúùû‹DCBÃÂ#"£¢cbãâŀ⠉IÉ)©ié™YÙ@ɜܼü‚Âô¢â’Ò2) €tyEeUuMm]}Cc“ P@¶¹¥µ­½£³«»§·O( ¯Ð?aâ¤ÉS¦N›>CQ ä噳fÏ™;oþ‚…‹T NUU[¼dé²å+Ô5`žÑÔÒÖÑÑÖÕÃês«ù8‚¼Ç˜tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/variable_layer_height.png000066400000000000000000000003211324354444700246630ustar00rootroot00000000000000‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<cIDAT(ÏcüÏ€01Pª€D”ÿÿÇð ƒá/0þŰ—‘ñ?1&ü‡é…è„Á3D›úÙf¼E´ ‘ÿ¡é…˜÷œh¼ÿ£ë‡ÀÏD›`ûÿ'’Í  ×?Ę@0²ù]Uvx¿iIEND®B`‚Slic3r-version_1.39.1/resources/icons/variable_layer_height_reset.png000066400000000000000000000020321324354444700260660ustar00rootroot00000000000000‰PNG  IHDRF} êssRGB®ÎégAMA± üa pHYsÄÄ•+¯IDATXGí˜ËkQÆ¿Lb“¦I›>Z+ZI¬¦¢ˆ¸Ië\ÄVm|U m„taAwîDH„ü\¹iŠâ+ÙŸ+«Õ>ÔÚ¢µOšæzfr“4i’™Š Õ|ð‘Ü{æ™ß=÷Ü!*ŒœWšþ™Wšò`²è·Ñk4üÛ¿!E`V£®¢‚Êj*E»ÙŒ†å+øÌÒ—,Êû³çðªý êÊËùlRV“ Á–ø¶kN¯©A­ÁÈ#K[9ÁÄ¡@«t:¼l;kYr(Am(BUÔ1kvs#Ž$ÙŽæô§(oñRfCI ž66AM=‚RUE®”Àh)غ µ á$zŒÃG.¥5_3œþù[ÂC(¸°Ô»añÄb©["GÞ/JŒe_4El´SÔO¾>†á`} ++…¶´O6nB­x¬+U÷¸Ãvt¥< Þ˜“¶ 3ÀÃ"iÎU^²oËäý‚r‚‘ ØãPŠ050€‰¡!T?z€U'ëïÇÞwÐW”£Þc­[uË x¶œè¼:¯j·Ð³T6½Æ›0ÿª4/¥ÉË+'˜å`Ñ9jEQ‚2„ñOåñCéô]ó<ˆïý{Û A "W«û6­u¼o(K=zã=ˆ;äŠ=P‡w޼αö$“'^q«a³~šÿm§Òö²r6Öt ïØ™ñôѪT¬o­™}Ù¼…Ù´ºñ%쌓)n0™rÉ"œ Ëÿƒ—E‹hÿ—ò`²(&£€Ÿìy³o¨IEND®B`‚Slic3r-version_1.39.1/resources/icons/variable_layer_height_tooltip.png000066400000000000000000000234411324354444700264450ustar00rootroot00000000000000‰PNG  IHDRpü9ÞT IDATxœí]hgÚ÷/ÆR¬Ê+HÁ‘"ºr Er¡lƒÕ¨°K(I&ƒ´”°ØÔ‚ž…)ŒŸb6j)‹]r¤ƒ™Âæ…BÆ-ai`dºe!’¡RZUJ©LdTÉúšy”˹3ú°ü‘4qî{¤ù¸çéš{f®ÿõ×@{œÞ§P({„ú jš ååe#@ …’xhP P(H‚ù½[@¡Pž>‚ p™LÆ—Ïçý™LÆF=Ýæ×-P({šL&ãòȲ¼Æ0Ì~†a¸b±( \h· 1P({˜L&ã³Z­S Ãp²,¯õõõñ’$Ý0|¡P¸Ôn9(”=J0´ãH€eY{­V9Ž;ïF>™LzÕËÒÀ@¡ìQNž<É3 ÃáôÒÒÒØÕ«W}8b@†††xõ²ì³h …Byö †Ãäô[o½Å[,–¹|>ÿÄ|}}}OÌ@G Êž¥^¯Käôàà`ËÈ 40P({”\.#§kµÚÏ¥RégY–7‚Æêꪨ^– eâr¹Är¹Áép8ì°Ùló²,¯Ȳ,}õÕW‹êei…²‡ ƒösçÎͳ,k¯×ëi|Ÿa˜ýß}÷Ý·ÛQ/§Ë3l'…By†üóŸÿ”ªÕêÿs¹\Ò+¯¼2‚ï ……Ãáÿ9sæL¬Ýr;¾”( —E‰‹¢8²ùÜEq¤V«‰Š¢Äkµš(W*•¶ºžç‰x<Î+ŠÏårSÛY>—ËMe2_§éç‰d2éU%Þî³|>ï/•J ›­£—ùAàjµš íÝæ ƒvEQâÑhÔÓëö_€d³Ùæûúúx|™L&ÿÄÄDºÓ2¿Û=†·Þz‹gYÖžH$.ôõõñ333Ÿö÷÷wÍßÞËd2ŸÙlöuš~Yq:ûY–í&&&ÒÆÕnXLÙ:O=0är¹)EQ⊢Ä1ý’üÒ;ÎK™LÆg4y€S§N-Æãñ'«d2.ßh4nãÿ8Zi4·Ée’ɤç«Õj"~¦>£‹¢8¢n¶µÛ:K¥ÒB·3Ø+¯¼²1"Ï\•JeQQ”¸ À“£-«Õ:`µZ§J¥Ò9íí´_‚ pø^©TZÀÿ·3úÊd2>܆¢(ñ|>ïow,m6Û¾£=ì7ƒÁÐv»Ýæ ƒvìEQâ8Rño4õD£Q9_¥RY#G [ÝgJ+O50`H¥RþåååI£ÑÈçr¹)›Í6¿²²2H$.Øl¶ùb±(4³³\.WËãNwX«Õ+—Ë£ÑÈ×ëõ5Fã8räÈ@óÇïp8ü> k4×úúzÜét^ÚìGF=V«u*›ÍÎi4W½^O¿þúëŸâg‡Ã/IÒéééc}}}‡?øàƒ¹NëÒjµÜÌÌÌX*•ò÷÷÷{z¹´Èf³sø×`0L’Ó‹e®—ýbYÖ~çÎùD"qeYûŸþô§-]ÒƒA»Õjº{÷îEFãÊf³sƒƒƒ^QGâñ8o6›}ËËË“ÓÓÓÇÈå>þøc___ßáP(ÄóÍ7“ÎðÝæóz½~€ééécKKKcCCC¾d2é …B<@(âÝnwäü£_’¤ˆF£q---étº‘óçÏ·¤ôRvÆS “eY»ÝîHµZ8p`ÛqeeEøí·ßb÷îÝêõzï²þáðà#˜D"p»ÝcíÖ©ÆjµNåóyÿÊÊŠ¨Õj9r„ÇíiuuUÔét#‚ÍÊÊŠ¤ááá°,ËÒVK:ÑË~Õëõ´Û펄B¡@3@me8ÇÀüõ×_‹&“‰;tè§X,Šn·;$<ÍÄ™_ýuqbb"Íó|¬T*µÎwšO®¿¿ßóÃ?̉çù˜$I7,KËÙ¿¯¯·X,s<ÏÇÈ;í”Ý㩦D£¼S}“ ‡Ò;%ŸÏKê÷´Z-'˲„7V¾üòËØèè(°,»¿ÛºÜnw¤P(ˆF£‘ôB­V›™™ÃeGGGEÙXÆn·€¶wuI0hí„^ö‹LZéD©TZÀ{9år9b0&ÉϣѨçÈ‘#<˲ûñò@¯×Û+•ÊÆ0“ÉĬV+4G*•JecÛ’$ÅL&S˺Ó|N§s?@ó²’ì_ò< ígÏž¤#…§ÄS1Ȳ¼&˲¤Ñh\ä+´|×××w%ò7 ‰aï¼÷Þ{#õz} çaY–Øøao000pA£Ñ¸VVVæËår„eYûùó罸ìÒÒÒ¹.yÔ¨ÏjøCÐétÛ_[[{bõt/ûÕ ƒaÛß.(ŒŽŽ.4ÇÒÒÒÆhä·ß~‹‘AÈl6o\T«Õ˜^¯ßöêýÍæK$mûWÝ>A¸¿üå/‹z½Þм¼è%R¶ÎS «««"Ã0\2™ôƒA{£Ñ¸ÝIÿ½[Ü¿? pæÌ™1§Óé•eYº~ýúB4àM¯C‡mœÕðÆc&“ñY,–9¼\Éd2±DÛ+•Êb­Vë<8&—L&½ Ãp’$EJ¥RìÑz\Á`ÐÞËÝö^ö«÷ÞéŽÙl¶ã:¿øâ‹ùãÇûñ³T*1|4õ‚À‘7WVVăŽƒA»(Š#¤¬—¤Ó|@@*—Ë‘ãÇûñFb­VÉ{3‡âœNç~†a¸û÷ï‡M&“ÿìÙ³“¤z²»8wò* 7”6är¹ÿU^*•þ-ÂÛàÌårÿ«(ŠÇÿ NQßo4kŠ¢(Édò"¹L&óEQ”L&órYQßg¥R¹Óh4ÖÈùq]µZí'ܹ.EQ”|>ÿŠ¢(…Bá†ú3r{êÏ*•ÊÜ6ùŠÇãÅýÄí—J¥ãçÁ`ðT¥R¹ƒïãÿ¢(¾/ÂÛ8ÏçÿO=Ým¿AxÛÕnº×¹M<†Fc ûAÝJsÜïg©Tú·¢(J£ÑX«T*wÈý&_æ#ûFÝoø~4ýÎS*•þ](nƒÁSä<¶O_½½hJ4…BiЍ(J 40P(”h` P(-ÐÀ@¡PZØ5u¥úEªÆíJ¥ÒR BÍfêDQG^tÕê Ôz^P+P÷‚"õYS«ÕÄÍ«¨üÝl]¤Â´—õ¾HìÚˆLN)—Ë«Õ:…‚­V{L¯×÷”’Ü'N,èõú—öG V ¾ìŠÔ§Å‘#GzúŽ ‡Q«³×x*—ù|>`³ÙFž1 »J¥²ˆJ9ò¬§V' ‚À …K Ãp˜b­N«n4·ÆmR½—Éd|íTŽdB] ÐzF'Ÿš)Å ¡ì#U–픤°Ûí§Õ£+TI’£+Ü/µµ“"•l©ÅÑX©TZÀ>ǾÝʱE…'*0EQ!U©dŸäóy?&ƒáöDQÁeÉ3,9òÄvÅãq¾ÑhÜ&[£Ñ¸F=Ô˜jDQÁù*•Ê"™žF=Ø6\/ æX–µãh “â´[MЧ0OþÎ;-¹îx–[^^ž¼víÚT»ì¿¾¾¾Ã333cÙlv®¿¿ßsþüyïÀÀÀY–%L±n—VYqËËË“ ÃpCCC¾«W¯úp=h¬ñÙgŸ- ÊoiiiÌ`0xzÉÈÌd2>lûôôô1–eí¨ ì¤$í¶>FãZ]] [­Ö©Í.-Ô ÔvŠÔ\.7e6›}‰DâÂôôô1­VË;wn^Ý·ûÛß&WVVæ±o7Ûo5 Ãp¨À4™LœÕjJ¥R~Fã*•J1r›:näæÍ›S¡Pˆïïï÷?~ÜÿÉ'ŸœÊf³sV«uJ.ŸÏû÷íÛç …B<¶{ffæSL9Ǿ÷4u-Ô˜ê¶?~Üßh4$FãÊd2‹˜))wôèÑKÅb1¢Ñh\?þøãÜèèèB<ÿ9•JùëõzZ£Ñ¸º)N·Úo/»N:µ½µZ-—J¥ü<Ï·Œ ƒU€éJ¥Ò2Ïúúz<H¨Ðét=ÕdY–\.—øå—_Æ*•Jlbb"ëÑëõ\4õètº‘b±A•_¥R‰F~³*AÈÑ£G/}üñǾ»wï^Ä|þ­*IQ‰ Q2={»8pÀ[¯×Ó.—KD$˲vòS,#@@ ½ö­üÑ:OµZ ‡®_¿¾À²¬8år9Âó|lbb"]¯×Ó¨<Íd21§Ó¹Ÿã¸Ó¨º Ò?ü0o4yA8I’nØíöÓÍQ–$I7zUcƒA»N§¹uëÖ@sèÚŠññqÃ0ÜìììÆgõz=}òäÉ't7ÅévúíEaWï1$‰ ƒÌ+W®ÜPÏ#÷ÈCoãlßh4žª JýžÑhäš%´ñ=ÔE:t¨ëÁ¾|ùr¸Z­Æ†áÌf³Ïét^ÂKRIª(J\§Ó0 Ãuª£2ÿn¦þÜ ìÛõõõ¡-ñ=µ×€šh4ê!o"oVø„eYN§ÓàüXX¥Ó‡TW" ÃpäûÅbQhD ƒ' Ú ƒçÁƒ•3®(J|ppЫ–™»\®Ã<ØX7žˆöíÛ·`vvvã¡“vËÅ …K¸{]½”p¹\b±Xu:ÝÈÌÌ̧êÏ€$˲D _¶R3`7´÷ø¥#€X$“üá‡<£I¯×…B!~eee¾^¯§ûûû=¢(ŽlEI ðø‡ƒÉ`HwɾRLrûvß¾}7Ãöïßohÿcì„ÛíŽí߬TZ½^—ŠÅ¢¨Þï­”X“eY"ƒïD"±†gj,Œãr¹Ä^Õ˜8r$>º.­¯¯·=^6›í‰K¯nŠÓ½Ì®ßcøüóÏ/ʲ,F¾Ýus©Tа,kÇHÏú)óF£U~z½~duu5<11‘. ? à8n㌉7È\.×a‹Å2‡÷ú¨­J±> ³,kÇã¼ œÙlæÉaþÓU—x¹‚7"·rSS’¤¨º{íµ×|år9‚AU’¤:n={Qc’óa%«d2éÅQ³Á‰8RÂï,Þ¤ì¦8ÝËìz`Ò?þ8ðÚk¯µÜ)þüóÏ/–ËåÈèèèÂ|0·•QÀÊÊŠˆÃõ^ï´ãÚµkSµZíçññqñÔ©S‹¥R)b2™üÍkÍr¹1›Í¾Z­&V«ÕKŽ ÅbQ|T°%Ž7á,Ë\±X‡||\¬T*±Ï?ÿüb·và8›ÍÎá™ñÞ½{sÍ!®Åbñ—]ÿùÏDY–¥S§N-&“I¯zÚb±Ì­®®†Nç¥ÙÙÙÛFCºvíÚ¶*V÷ŠÛíŽd³Ù9‡ÃáW%>44ä[^^žì4Rj‡Édò¯¯¯ÇÇÇÇEl÷§Ÿ~ºqYвwü ðØW®\¹Q¯×ÓØ6I’"²,KøÄm¯òÌÕ•XAhiiiìÑ¿E–eíXFB¡üþ<ó”è[·nÍU«Õ>ŠϺ åù€Öc P(-P…Bi …Ò  ¥…] ¤ˆF-›VÛ²‘ EiÁF UvCf­ÞF7òù¼·ŸL&½ÉdÒKZ´½ˆtëÿÍP÷ÿ^½«) —Úi,z•^“ì%›¼†x<Î[­Ö©•••yFã*‹¢Ùln+hQÃó|L£Ñ¸ðù3–$Ÿžž>f2™üÏRf-788è­V«1Fãºwï^ì‘ÊnÇf1/*êþß‹²w2S”¤Wéõ^eÇÁb±Œ4ÓŠƒÁ M[ÔÙv333Ÿ’gc€'Ïæ…Bá’N§h&­”J¥…n2k¤×"0)Ã%aÿþ÷¿/4Õ€Fãö‰'ŒF#ßný[‘y“ÛTK„»ݪ¥Åݤã› Áþ'Íz»™üªeîíŽG/òõ|>ïGÉòvF_ù|ÞOJ£É>R÷ 9r%M};I´K¥Ò˲v‡Ãá'û¯ôZ-Ñîvâ3›ÍÛ#ç#G¤ägÚÝm_Ÿ6; ÕjUèïï÷Œ‹…B¡­¬$I14‹}õÕW[†õªÕj  9b0 “›É¬·Š Ü»ï¾;Д<×jµŸ_ýõOAà>ùä“Sö'¦Õj}óÍ7“ÅbQìTd¦™· ܉'Pú‹rã^ªýx½^?˲vFãJ$úûû=¸ÜÅ‹/éõú‘P(Äã:»I·ÉþÇ}î¶mµÌ½ÝñèE¾®×ëíZ­öXµZ z·3Ìff(âµZí±L&ã3 ž¥¥¥1”hçóy¿ OÈ¿×××㘾ÜI¢m0&ëõz:•JùI„Û펨¥×£££ ¿þúë"J´‡¿“ôzhhˆ×ëõc©TÊïp8üÁ`ОL&½Ç…B<ެ_}õÕ©níî´¯[í¿í°ãÀ€ŠCœ6|;+{¬Í€"ªøWbAL"Õ}ÝÖûÑGfYÖŽ†¬÷ïß3 Ãm×-¹™÷ùóç½ Ãp¿üò‹Ь« ˲tðàÁžÄ8”‡‹‹e$ ñ6›m> Úûûû=¸½Ë—/‡š²äNíÄ/>¦•oU‡¡¦Wù:î7ºq¡@j+à~˜ÍfþáÇažçcäÛ‹¤óÕÐПL&½ôzýØV s;qòäIžìÃááápµZ½õÖ[m1)ó®V«±“'OòÃÃÃa­V{ ÷#—ËEHœºÝ½ìëÓdLJhk ;3róæÍ]s%ª8Y–%¼/°ÙÈ|V«uJQ”¸Óé¼°ýšjÚɼqÝ÷îÝÛž¤;w7nݺ5'˲¤ÓéFÌf³o||\Ìd2>T ö÷÷{E‰ÏÎÎÞ~´­¶ž‘í ՌۡWùúúúzWoMõëͶ‹’wœßápø†á€´¼¼<©Õj9Ômär¹©^%Ú›!ËòûÑh4$ô@UCªtÉïD&“ñaU+‡Ãáh~—Ûµ»Û¾n¥ÝÛe×WZ,–94„غ{;vÛâåÇÙlvŽ”Ú¶ßìV ÃK-òf˲võ­Ñ-Ïó1­V{,‘H\À~òáO-wÞJ]MÒ4·“ɯºÿÉé^åë›a³ÙæÉ}Øl~Y–%õñÃåÜnwD¯×á÷Ðl6oÑÍ$Ú›¡äZ­–ëTÛ‚ Œø;Èd2¾¡¡!@sÔ–Íf7¾síÚýHÊßq_Ÿ6; ê›,([Æ¡ãÓf+…f¯\¹rC–eÉl6óo=Í2]—/_˲, mÔd†K§Ó ÝnkµšX«ÕÄP(ùâ‹/æeY–pX]­VcXÀóvºoÁ0 ‡Ÿ™Íf^–e) Eº™ünÆfòõíö×f 1.3¬a‰ ñ{ˆªnÞ¼™ÞL¢Ýiô„Áà믿É>L&“^N7’J¥Ú~ÇI™·N§ùúë¯EƒÁp¸^¯§M&“ÿÎ;¼”ìÖîNûºýØ ;2¿ ƒ§Ð¨AV IÑÌ–œEñ}Eyl€«ž—4Q ƒ§¶Ó>õ6âñø_kµÚOŠÒ4VEÝvF°hÈ[«Õ~R¯·Ñh¬¡‘..‹æ¸hn‹f°ñxü¯¤a+i–ÛÍèVÝ·¥RéߨêÏpÿÔ/rݸÏÛ1ù ƒ§ÔÓê6àr­¦Åêé^_í jI£äJ¥rûDmJŒÇ¶›a.¶‹<&¸ €3~Øî{£^÷–q>Qß'—'™;µ»Û¾>íQQ(”hJ4…Bi …Ò  ¥(J {^]Ù­½½Ën¦NÌår=¥7?¯´³ÅÛ jê^P¤ö©¾ì¦ª|Qmì^Ju%&Õ`eæí‚¶t»Õ® QGHªzz/ó2ßîèy'>%Ÿ±“/|~L>%Ÿíâ3xòsò¹;‚¹ êW£ÑX#sÔ/Ì)Àõ …øÜŸ©'“É‹ä³xò™|§öcÛun£P(ÜÀçá…Bá®GOL&/â|µZí'ü¬]~™CÇÿŠË©ŸÇ‹¢ø>¶Ÿ\'ùÂõ×jµŸ°ÈçåêÜl¿(Šï“ÛÅçöä´º dûÚm·V«ý$Šâû[ý’yä:Èuã¶ÉcF?„6yä±l—¿F?$=æ/´ÛOÜÙ\™OF?ÄþÃ6cû~×K£®ÔjµœF£q}ùå—O¤?ãY®\.Gµ¯mn¿ºý<ÏÇ0­5›ÍεK«h¦8kµÚcår9b4ùz½¾†g#GŽL4G]‡ÃÿðáÃ0*ìœNgÛ~Tóúë¯Z©Tb˜NkµZ§¢Ñ¨§›’´Ýz†ÙëÖ­¹P(ij,kGç§n¨¨êé^”¥,ËÚïܹ3ŸH$.°,kǬÁ^é¦Nh:OMOOK¥Rþ¡¡!ŸÉdráÈöÍ7ßœ8wîÜ<¶qiiiìÀÞL&ã뤪Ìçó©¾Äm©÷³]_÷õõ¾zõªoiii¬¿¿ßƒæ@G½ôË/¿Ìk4W¥Ryj™£½òÒ¨+1E[®ûÆox«ÑdVÍvÛJNáúI! ξúê«E€Ç†·n·»§Tïþþ~Þ‹A{¸­*I+•J Íg+•JL§ÓìÄÔ e)£3Ôv56íÔ‰¿þúëb ¾ýöÛÃ0\4]hz†a8QGX–µ£"’çùØÃ‡Ãf³™ßªªòþýûa€ÇÆ·ï½÷^K`Gó^žçcõz=½ÿ~;*]q;h´û{òÒ¨+qd£Õ¨D“Y5»Õþvë×jµœ,Ë-”q÷r­þË/¿Ì4ƒ*ñ¢Ñ¨g«JRRˆÿofò»½(KI§­NÅ`Ô7£»©Û¡x¡w(ù~µZÝðWÝŠª…e[E¯×sävv£öÈNy©Ô•íÀ€&¶[±7'Š;¡ÑhH Ãpx†Æ3 yYÓÉèo¤f³Ù¹b±(8NïV”¤O/üŸ †íÔŸê`©žîUYºƒa²›*²“:±—uc &ƒ N§Ûpdߊªr»T*‰ÜÎNFÓ»ÅK¥®l^" úMfŸ%8=sæÌ@ó‡FªÝŒnñqo©TZ°Ùlóétú@s˜¼U%i¿‡4®V«±@ uSnÆfÊÒÝ ›:±—åqH÷6DQ9pà€weeEÜLUÙKM^¸råÊ €fÿLOO?U¿Ñ^Øq`¸víÚT¹\Žà0ÎápøWWWÃÎL[a·Ll»Áó|,•Jù±èÉVµ…B¡HµZY­Ö©<»w¹\b6›;pà€WQ”ø¾}û\wïÞ½ˆ—Œnyž%‰ }}}‡ñr¡X,Š6›m>HwïÞ½ˆýÇqÜéT*åçy>Ö® õz=ýæ›oN‹õz=f¸×¯__¨V«1§ÓyÉëõúÉQ\ ŠÅ¢h4ùZ­&¶›¾{÷îE4•µZ­SÙlvn7Ý·'&&Ò¤©®Óé¼”J¥ü[Ž_½zÕ‡mRÄgˊʶìyf§òãvlÅP—d/™£î”U¶ü¢³«#†?ÿùÏò/e{LLL¤Qóð{·…òr²kA–eÉd2¹L&“«]:3b¶Ž0º™«>îh¦cn¾Ú`”l?9ÚªÁìf#t."xqÕÛÅì?Ü_lg'SWÜì?²_ÔË \äñ@O‡vû¡žû²Q- ÙÏX£édòú¼÷ŋƮ†z½ž6£ÑèY__bø‡¹‰Dâtž;wnÓ¢h4êÁŒ9Ò”?s8~I’nLOOëëë;ÜN.\,#,ËÚƒÁ Ýf³`L&“§^¯§Q݇2॥¥1RŒEYR©”yyyÒh4òȶj0»Ut:ÝÈÿûß¹ééécS§ÏŸ?ï%·ëp8ü˜grõ L]ƒÁ ÝjµNݽ{÷"ê-½¢(Žœ9sfŒa¥ÈƒƒƒÞx<Îãñ@™s©Tе;ŽÉdÒkµZ§–——'q¾wß}wÛGÕ’ËÅãqÞl6û–——'q‘n&¯Ïs_¼ˆìZ`$)²¬=zX–µK’ôDêí¼õz=ír¹Ä@ ­¬¬ˆ,ËÚ{©ôÐT æóyÿÊÊŠˆ_&Ô5Ü»wO ÒêꪨÓéFÔz€Dš—8&“É#˲„B¯×‹Å!»,Ë’ÛíŽ`®= Š<8&˲4ï¿yóæÆ|‡ÃS­Vc˜Ö|ýúõ–eíê>·X,žb±(âåÐììì˲v™‘Fµ$‡ÚX¿'øY7“×ç¹/^DØÝZQ.—‹™Ífxã7|Mq’Õj€ÇŠÀR©´ñ\[[K[­VÐëõ\7ãS·Û) ¢Ñh佃ƒƒP«ÕÆfffÆP×0::ºÐ,’Óä‘ãF`r¹\b£ÑL&“K¯×H’tcppÐ{öìÙI†a¸|>¿1ºé¤èdf?êÈ÷Õ³øþf³ä¼ÛM¿~Ô§?w›Geêºñ~¹\Ž4G]GŽáY–Ýo47F9‹e.—ËÙlöÍÎÎz?ûì³Ø¿þõ/?˲õ/Èí¨U©Z­–# ŽivvvCÅÚ ½^o'—Ëd21ü&¯-—jÏs_¼ˆìÚˆ! EdY–úûû=õz=MªÛ€$˲´oß¾j7û÷ï·<6šèl®:00p%µår9²¬ýüùó^”%« KÛÕr¬T*1ŽãN3 ÃÝ»wO¬×ëi<Û  ¦²,¯á”|m×`–œw»š Y–%ÒT¶‰D¢m †IÔ‰¬¯¯§—––žh3JéñýwÞyÇW¯×%õ¾¶»Qú¨nA‹”x3÷ëß~û-F.g6›7®Ù»™¼>Ï}ñ"²«O%*•J  yM¯þìáÇa–eíXÊÊl6ó8 ëf®Š7w2™ŒÏb±Ìa%¤L&ÃK¬tT©Tñ&£š|>a†ÃK…b±a†«×ëé^LXWWWE†a8¼m4· …Â¥­Ìî&+++"Çq§ƒÁ oઇ±@@êdêj6›í(ïþâ‹/æÉ’hxÓS™ëëëéT*1<^âñQ×Èår£ÑÈã#×ééé)4Óí¶O¸~,OGÞ|ìfòú<÷ŋʎŠFbqPAÞÆ¢œÉdò¢Úè iPŠÅ2ÕÅc{5WU”' ž’ŸU*•;ЉbñV,À‰E:ÕÅI±ˆi7“[\Híd0«6Ôíõ…ÅE£Ñè‡jSWrZ„·ÕÅNáQÑÑNƹd‚ð¶ºj£ÑXËd2ÿP/S©Tî´3n4kŠ–v:6íŒj»-§4Çý-Ç€,Zû¼÷Å‹ö¢"* …ÒM‰¦P(-ÐÀ@¡PZ B¡´@…Biá™2ÿŸTÌe2ßv ©Öj5ñE6“¥Pžg~—iô¹YR …Byöì80tS«‘ÊGRÜD&=Jsö¢R®“ZMÅÜN¥RYìTÓ? ÚI•*$qÄ‚J»L&ã‹F£RåGÖ?@|õªé Pö; Ôj‚ p‡ÃFh\Bb³ÙæWWWë««aLKí¤V;~ü¸MC3™Ì"éÆDröìÙI4°EuÞ;ï¼³¬VWWEFãº|ùrøèÑ£—ŠÅb KGGG0{‘ã¸Ó¡PˆGÔvF¼Ê^eG¡›Zí£>:M‚¢™h7º©Õt:݇;yšL&?êPHÚ¯¡‹Ðøø¸‡a D‡‡‡Ãõz=}òäI~xx8¬Õjaªt.—‹t DÊ^dGêÊnj5µQ'Ïó1rž¶ÙD­F¢.£¹\nŠeYÎh4zX–µ£zŽU~³³³·ggg[ÖñHûØ`0Œ`½ åeaG#†njµµµµ'\{©lÓI­†îϤñh___Û›–¥Riã8À÷ßïG£W5ëëëmÕ’6›m€šâœl6û»Û’S(Ï’†nj5TÑáÍȳg϶¸#(³í¤VhŽB°šR2™ôv2Wíëë;,IRÄd2ùÍf³ÔÕ“`ûðQ)–|‹Çã¼Á`8\¯×Ó&“ÉçÎÈÁƒ·mšK¡¼ˆìøæc8ö4‡äãããb­VûÙb±Ìu2êTóàÁƒˆÑhäK¥Ò‚Û펥CCC¾åååÉ@ …Ãa?Úl¶±N÷Òéô£bq›Í6¶ººÖëõ-ÛÆöF¢(ñÑÑÑ…l6;çr¹Äh4ºˆEYNœ8±°ºº*4ŸŒì´¿(”ª®¤P(-Дh …Ò  ¥(J 40P(”h` P(-ÐÀ@¡PZ B¡´@…Bi …Ò  ¥Í£¿4-šB¡ :b P(-hˆÿé¨B¡$ž 48P(/3 üG (”—‡„úÿ£é¸,ûL)IEND®B`‚Slic3r-version_1.39.1/resources/icons/wand.png000066400000000000000000000010721324354444700213070ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<ÌIDAT8Ë•’ÁK"aÆ¿SÔ!bteY¥ÃË!!º°¨)ê‚:ÖMìžE—]ö´†èÑ[Hç žZ©eˆÝA¦è TÆÊ¶,¸þ 5}Œ3‡ðÙo&Ú‹Žåá/óñ{Þ—‡!ÈS(gÎ1åln¥ßÛiÞÎX˜g£ÊéÛ†1KÓÓTzµòdÀƒ8÷“‰‚"Í©—‹P$Ç'ZµÝR‘K=/àôMV½JAû½­õ‹(¨8Ù¡ß'ÆMع/ØVQ‘^C½ä¡ýÍãdG£²ŒVuúä£ÉÎà$G¶ÓXÂÉ~š¦¡Ûíââk êõdüb8L¨ô’£U«‹ÅP¯×¡ª*šÍ&ª»Qh>BþF(ÃÓ7€•d§âT%‘H N£X,¢\.ãhgÉŸuùžq.’-Ó xž7dǃ@ €x<å<Š;ц;ar…ØMK|”Ýn7ü~?,‹¾u›m¼g¼crC> ™¾Éd…BÁ}>8Žƒþ‰ï™(èóÍa|è ˆD"Èçó=ò#L ™þÊÁ`¹\.— ^¯·GÉd2h·Ûp:…BCÉÿjµJ¥’QØ0²‡7õ³­VëвÎ?P ³µ¯vIEND®B`‚Slic3r-version_1.39.1/resources/icons/wrench.png000066400000000000000000000013461324354444700216500ustar00rootroot00000000000000‰PNG  IHDR(-SgAMA¯È7Šé cHRMz&€„ú€èu0ê`:˜pœºQ<YPLTEÿÿÿžžžœœœ›››™™™››››››˜˜˜’’’ŠŠŠ–––‡‡‡•••“““………‹­ãˆ§Ú’’’„„„‚‚‚‰©á‰‰‰………ƒƒƒ‡©áˆˆˆ†††…§á]‰Òƒ¥ßQƒÕ¥ßOÓMÓI}Ñe‘ÙG{ÑW‡ÕEyÑäääîîî–––âââççç¹¹¹“““ÏÏÏŽŽŽŒŒŒÝÝÝÅÅŬ¬¬×××ÔÔÔÈÈȼ¼¼ºººÂÂÂÄÄÄÇÇÇÌÌÌÆÆÆÃÃÃÀÀÀœ¹ç˜¶æŒ©ØÒÒÒµµµœ·æ¼ÏïºÎâ{›Ò™·æ»Îî§À飽è³Èì¤ß˜´æºÍ閴å¹ç®ÆëyŸÞ”³æ¸Ì锳囷æ­ÄêxžÞyŸÝ·Ì풱䚶æ¬ÃêtœÞo—Û³È훸ç«ÃêsœÞ«Âê¨Àêq™ÜQƒÕK}Ñÿÿÿ ”(+tRNSpÖûûpõÖûÂûüÁÃûHþÖ’Ïûõp’þûÖp’÷#’É’ª’¬¬¯Ì¯Õ¯ÅXtYbKGDˆH£IDATÓc`€F&f$Àʦ­£‹,À®§o`ˆ,À¡mÄÉ`lÂà65ãá5·àƒñù,­¬mlí!\!a{{G'gQ10_ÜÅÕÍÝC‚ORJÌ—ñôòöñ••ƒi—÷ó V€ñCBÃÂ#"£” |åè˜Ø¸ø„D˜‚¤äÀ”Ô´tU¸ƒ22³B²sÔNVÎÍË×@ö„fA¡ETšoÇ”“tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚Slic3r-version_1.39.1/resources/icons/zoom.png000066400000000000000000000012641324354444700213450ustar00rootroot00000000000000‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<FIDAT8ËÒKHÔAÀñï̪-–ŠK¾C=Ø ¡CmÒfuîP–‰)D—êB§"ÂK»$A„ e¤¦t¶¨¤ð ô‚JŒÍZÿyuÙmüÁ0ø}f~3#œsLÞ²|!!|g-V©9­¼Që鎺³ýc¬`²ïT«™C8*¬—èô£5Þb¬F'âÎR!DÆÐ‹› ­kbâ^sPÈÌ!«½»²¾«¨ÊLocÂgÀע≣‡.=^uéíÖ˜ˆ¬ïê[ €¨ê65ôÞ¶FG´§• !§Ô P ä%wN5•\«Òq=(¡t@ȀѶ(t2)Õ§ "«L?Bþ2¬uXcY‘äV$¦ÆÚƒÔ&ý´çÍa]còÂt²¥€Ô<â *¡æÒã©QçÄ1àð}E )h˜’Y—­§§Ò„ê°Ž’…æ6` øDXr<:=UR½Íï÷ÌžáóûO®úÎ9žÝ8Ò*}òªV&b==h”é·ÖqÙÜ+;¶ËœâJT<ÆÔÓçüúò­øø­—³ËÀÈõÃA !CÎ:”§£¾Ì¼Êú}änAHÉ~0bM”ž¸36» ü+†/ç——ö”ƒøsóIĢ̾™àÉÀ«ÏEkÿ ž …óJ {ÊvíÄ!ùùé=_ßMÏyKªb]Àö½aµ¤{6W°03ñ1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 25 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 4 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 2 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 1 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 30 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL SOLUBLE INTERFACE] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 25 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 3 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 30 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm 100mms Linear Advance] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 50 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 100 interface_shells = 0 layer_height = 0.2 max_print_speed = 150 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 60 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 30 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 100 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 60 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 70 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm FAST MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 30 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 35 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = grid first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 3500 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 200 interface_shells = 0 layer_height = 0.2 max_print_speed = 250 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 45 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 200 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 50 top_solid_layers = 5 travel_speed = 250 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 50 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 40 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.61 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.6 top_solid_infill_speed = 40 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL SOLUBLE FULL] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 4 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 2 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 1 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 30 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL SOLUBLE INTERFACE] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 3 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 30 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST] avoid_crossing_perimeters = 0 bottom_solid_layers = 3 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.7 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.43 perimeter_speed = 50 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.7 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.43 top_solid_infill_speed = 50 top_solid_layers = 4 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 7 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.61 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.6 top_solid_infill_speed = 50 top_solid_layers = 9 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST sol full 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 3 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 4 support_material_extrusion_width = 0.55 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 3 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.57 top_solid_infill_speed = 50 top_solid_layers = 4 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST sol int 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 3 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.55 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.57 top_solid_infill_speed = 50 top_solid_layers = 4 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [filament:ColorFabb Brass Bronze] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.3 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 210 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 210 [filament:ColorFabb HT] bed_temperature = 105 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 10 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 105 first_layer_temperature = 270 max_fan_speed = 20 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 270 [filament:ColorFabb PLA-PHA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #FF3232 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 15 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 210 [filament:ColorFabb Woodfil] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 200 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 200 [filament:ColorFabb XT] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 90 first_layer_temperature = 260 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 270 [filament:ColorFabb XT-CF20] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 1 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 90 first_layer_temperature = 260 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 260 [filament:ColorFabb nGen] bed_temperature = 85 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 10 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = NGEN first_layer_bed_temperature = 85 first_layer_temperature = 240 max_fan_speed = 35 min_fan_speed = 20 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:ColorFabb nGen flex] bed_temperature = 85 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 10 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 5 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = FLEX first_layer_bed_temperature = 85 first_layer_temperature = 260 max_fan_speed = 35 min_fan_speed = 20 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 260 [filament:E3D Edge] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:E3D PC-ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 270 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 270 [filament:Fillamentum ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 100 first_layer_temperature = 240 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 240 [filament:Fillamentum ASA] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 265 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 265 [filament:Fillamentum CPE HG100 HM100] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "CPE HG100 , CPE HM100" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 90 first_layer_temperature = 260 max_fan_speed = 80 min_fan_speed = 80 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 260 [filament:Fillamentum Timberfil] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 190 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 190 [filament:Generic ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 100 first_layer_temperature = 255 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 255 [filament:Generic PET] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:Generic PLA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #FF3232 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 15 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 210 [filament:Polymaker PC-Max] bed_temperature = 120 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 120 first_layer_temperature = 270 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 270 [filament:Primavalue PVA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #FFFFD7 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of materials tested with standart PVA print settings for MK2:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" filament_settings_id = filament_soluble = 1 filament_type = PVA first_layer_bed_temperature = 60 first_layer_temperature = 195 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 195 [filament:Prusa ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 100 first_layer_temperature = 255 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 255 [filament:Prusa HIPS] bed_temperature = 100 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 0.9 fan_always_on = 1 fan_below_layer_time = 10 filament_colour = #FFFFD7 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 1 filament_type = HIPS first_layer_bed_temperature = 100 first_layer_temperature = 220 max_fan_speed = 20 min_fan_speed = 20 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 [filament:Prusa PET] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:Prusa PLA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #FF3232 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 15 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 210 [filament:SemiFlex or Flexfill 98A] bed_temperature = 50 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 cooling = 0 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #00CA0A filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 2.5 filament_notes = "List of materials tested with FLEX print settings & FLEX material settings for MK2:\n\nFillamentum Flex 98A\nFillamentum Flex 92A\nPlasty MladeÄ PP\nPlasty MladeÄ TPE32 \nPlasty MladeÄ TPE88" filament_settings_id = filament_soluble = 0 filament_type = FLEX first_layer_bed_temperature = 50 first_layer_temperature = 220 max_fan_speed = 90 min_fan_speed = 70 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 230 [filament:Taulman Bridge] bed_temperature = 50 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #DEE0E6 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 90 first_layer_temperature = 240 max_fan_speed = 5 min_fan_speed = 0 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 250 [filament:Taulman T-Glase] bed_temperature = 90 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 90 first_layer_temperature = 240 max_fan_speed = 5 min_fan_speed = 0 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 240 [filament:Verbatim BVOH] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #FFFFD7 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 1 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 210 [filament:Verbatim PP] bed_temperature = 100 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 2 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #DEE0E6 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 5 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 220 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 [printer:Original Prusa i3 MK2] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 0 end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors extruder_colour = #FFFF00 extruder_offset = 0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.25 min_layer_height = 0.07 nozzle_diameter = 0.4 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n printer_settings_id = retract_before_travel = 1 retract_before_wipe = 0% retract_layer_change = 1 retract_length = 0.8 retract_length_toolchange = 3 retract_lift = 0.6 retract_lift_above = 0 retract_lift_below = 199 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 35 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 1 wipe = 1 z_offset = 0 [printer:Original Prusa i3 MK2 0.25 nozzle] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 0 end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors extruder_colour = #FFFF00 extruder_offset = 0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.1 min_layer_height = 0.05 nozzle_diameter = 0.25 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n printer_settings_id = retract_before_travel = 1 retract_before_wipe = 0% retract_layer_change = 1 retract_length = 1 retract_length_toolchange = 3 retract_lift = 0.6 retract_lift_above = 0 retract_lift_below = 199 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 50 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 0 wipe = 1 z_offset = 0 [printer:Original Prusa i3 MK2 0.6 nozzle] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 0 end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors extruder_colour = #FFFF00 extruder_offset = 0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.35 min_layer_height = 0.1 nozzle_diameter = 0.6 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n printer_settings_id = retract_before_travel = 1 retract_before_wipe = 0% retract_layer_change = 1 retract_length = 0.8 retract_length_toolchange = 3 retract_lift = 0.6 retract_lift_above = 0 retract_lift_below = 199 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 35 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 1 wipe = 1 z_offset = 0 [presets] print = 0.15mm OPTIMAL printer = Original Prusa i3 MK2 filament = Prusa PLA Slic3r-version_1.39.1/resources/profiles/Original Prusa i3 MK2, MK2S, MK2MM and MK3.ini000066400000000000000000003335071324354444700273100ustar00rootroot00000000000000# generated by Slic3r Prusa Edition 1.39.0.29-prusa3d-win64 on 2018-01-22 at 14:53:14 [print:0.05mm DETAIL] avoid_crossing_perimeters = 0 bottom_solid_layers = 10 bridge_acceleration = 300 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 complete_objects = 0 default_acceleration = 500 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 25% fill_pattern = cubic first_layer_acceleration = 500 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 20 gcode_comments = 0 infill_acceleration = 800 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.5 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 30 interface_shells = 0 layer_height = 0.05 max_print_speed = 80 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 300 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 30 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 15 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 30 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.3 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1.5 support_material_speed = 30 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 20 top_solid_layers = 15 travel_speed = 180 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.05mm DETAIL 0.25 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 10 bridge_acceleration = 300 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 complete_objects = 0 default_acceleration = 500 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.28 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 500 first_layer_extrusion_width = 0.3 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 20 gcode_comments = 0 infill_acceleration = 800 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 20 interface_shells = 0 layer_height = 0.05 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 300 perimeter_extruder = 1 perimeter_extrusion_width = 0 perimeter_speed = 20 perimeters = 4 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 10 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0 solid_infill_speed = 20 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.18 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1 support_material_speed = 20 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0 top_solid_infill_speed = 20 top_solid_layers = 15 travel_speed = 200 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.05mm DETAIL MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 10 bridge_acceleration = 300 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 500 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 25% fill_pattern = grid first_layer_acceleration = 500 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 20 gcode_comments = 0 infill_acceleration = 800 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 30 interface_shells = 0 layer_height = 0.05 max_print_speed = 80 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 300 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 30 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 15 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 30 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.3 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1.5 support_material_speed = 30 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 20 top_solid_layers = 15 travel_speed = 180 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.10mm DETAIL] avoid_crossing_perimeters = 0 bottom_solid_layers = 7 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.1 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 40 top_solid_layers = 9 travel_speed = 120 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.10mm DETAIL 0.25 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 7 bridge_acceleration = 600 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.25 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.25 fill_angle = 45 fill_density = 15% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.25 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 1600 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.25 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 40 interface_shells = 0 layer_height = 0.1 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 600 perimeter_extruder = 1 perimeter_extrusion_width = 0.25 perimeter_speed = 25 perimeters = 4 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 10 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.25 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.18 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.25 top_solid_infill_speed = 30 top_solid_layers = 9 travel_speed = 120 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.10mm DETAIL MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 7 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 30 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 35 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = grid first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 1500 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 170 interface_shells = 0 layer_height = 0.1 max_print_speed = 170 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 45 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 170 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 50 top_solid_layers = 9 travel_speed = 170 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm 100mms Linear Advance] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 50 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 100 interface_shells = 0 layer_height = 0.15 max_print_speed = 150 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 60 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 30 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 100 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 60 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 70 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 40 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL 0.25 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 600 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.25 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.25 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.25 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 1600 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.25 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 40 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 600 perimeter_extruder = 1 perimeter_extrusion_width = 0.25 perimeter_speed = 25 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 10 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.25 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.2 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 35 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.25 top_solid_infill_speed = 30 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.61 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.6 top_solid_infill_speed = 40 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 30 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 35 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = grid first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 1500 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 170 interface_shells = 0 layer_height = 0.15 max_print_speed = 170 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 45 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 170 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 50 top_solid_layers = 7 travel_speed = 170 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL SOLUBLE FULL] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 25 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 4 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 2 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 1 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 30 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL SOLUBLE INTERFACE] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 25 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 3 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 30 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm 100mms Linear Advance] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 50 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 100 interface_shells = 0 layer_height = 0.2 max_print_speed = 150 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 60 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 30 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 100 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 60 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 70 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm FAST MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 30 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 35 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = grid first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 1500 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 170 interface_shells = 0 layer_height = 0.2 max_print_speed = 170 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 45 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 170 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 50 top_solid_layers = 5 travel_speed = 170 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 50 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 40 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.61 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.6 top_solid_infill_speed = 40 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL SOLUBLE FULL] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 4 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 2 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 1 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 30 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL SOLUBLE INTERFACE] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 3 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 30 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST] avoid_crossing_perimeters = 0 bottom_solid_layers = 3 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.7 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.43 perimeter_speed = 50 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.7 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.43 top_solid_infill_speed = 50 top_solid_layers = 4 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 7 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.61 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.6 top_solid_infill_speed = 50 top_solid_layers = 9 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST sol full 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 3 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 4 support_material_extrusion_width = 0.55 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 3 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.57 top_solid_infill_speed = 50 top_solid_layers = 4 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST sol int 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 3 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.55 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.57 top_solid_infill_speed = 50 top_solid_layers = 4 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [filament:ColorFabb Brass Bronze] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.3 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 210 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 210 [filament:ColorFabb HT] bed_temperature = 105 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 10 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 105 first_layer_temperature = 270 max_fan_speed = 20 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 270 [filament:ColorFabb PLA-PHA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #FF3232 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 15 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 210 [filament:ColorFabb Woodfil] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 200 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 200 [filament:ColorFabb XT] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 90 first_layer_temperature = 260 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 270 [filament:ColorFabb XT-CF20] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 1 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 90 first_layer_temperature = 260 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 260 [filament:ColorFabb nGen] bed_temperature = 85 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 10 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = NGEN first_layer_bed_temperature = 85 first_layer_temperature = 240 max_fan_speed = 35 min_fan_speed = 20 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:ColorFabb nGen flex] bed_temperature = 85 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 10 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 5 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = FLEX first_layer_bed_temperature = 85 first_layer_temperature = 260 max_fan_speed = 35 min_fan_speed = 20 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 260 [filament:E3D Edge] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:E3D PC-ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 270 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 270 [filament:Fillamentum ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 100 first_layer_temperature = 240 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 240 [filament:Fillamentum ASA] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 265 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 265 [filament:Fillamentum CPE HG100 HM100] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "CPE HG100 , CPE HM100" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 90 first_layer_temperature = 275 max_fan_speed = 50 min_fan_speed = 50 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 275 [filament:Fillamentum Timberfil] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 190 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 190 [filament:Generic ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 100 first_layer_temperature = 255 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 255 [filament:Generic PET] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:Generic PLA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #FF3232 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 15 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 210 [filament:Polymaker PC-Max] bed_temperature = 120 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 120 first_layer_temperature = 270 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 270 [filament:Primavalue PVA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #FFFFD7 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of materials tested with standart PVA print settings for MK2:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" filament_settings_id = filament_soluble = 1 filament_type = PVA first_layer_bed_temperature = 60 first_layer_temperature = 195 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 195 [filament:Prusa ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 100 first_layer_temperature = 255 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 255 [filament:Prusa HIPS] bed_temperature = 100 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 0.9 fan_always_on = 1 fan_below_layer_time = 10 filament_colour = #FFFFD7 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 1 filament_type = HIPS first_layer_bed_temperature = 100 first_layer_temperature = 220 max_fan_speed = 20 min_fan_speed = 20 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 [filament:Prusa PET] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:Prusa PLA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #FF3232 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 15 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 210 [filament:SemiFlex or Flexfill 98A] bed_temperature = 50 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 cooling = 0 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #00CA0A filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 1.5 filament_notes = "List of materials tested with FLEX print settings & FLEX material settings for MK2:\n\nFillamentum Flex 98A\nFillamentum Flex 92A\nPlasty MladeÄ PP\nPlasty MladeÄ TPE32 \nPlasty MladeÄ TPE88" filament_settings_id = filament_soluble = 0 filament_type = FLEX first_layer_bed_temperature = 50 first_layer_temperature = 240 max_fan_speed = 90 min_fan_speed = 70 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 240 [filament:Taulman Bridge] bed_temperature = 50 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #DEE0E6 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 90 first_layer_temperature = 240 max_fan_speed = 5 min_fan_speed = 0 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 250 [filament:Taulman T-Glase] bed_temperature = 90 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 90 first_layer_temperature = 240 max_fan_speed = 5 min_fan_speed = 0 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 240 [filament:Verbatim BVOH] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #FFFFD7 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 1 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 210 [filament:Verbatim PP] bed_temperature = 100 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 2 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #DEE0E6 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 5 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 220 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 [printer:Original Prusa i3 MK2] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 0 end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors extruder_colour = #FFFF00 extruder_offset = 0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.25 min_layer_height = 0.07 nozzle_diameter = 0.4 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n printer_settings_id = retract_before_travel = 1 retract_before_wipe = 0% retract_layer_change = 1 retract_length = 0.8 retract_length_toolchange = 3 retract_lift = 0.6 retract_lift_above = 0 retract_lift_below = 199 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 35 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 1 wipe = 1 z_offset = 0 [printer:Original Prusa i3 MK2 0.25 nozzle] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 0 end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors extruder_colour = #FFFF00 extruder_offset = 0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.1 min_layer_height = 0.05 nozzle_diameter = 0.25 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n printer_settings_id = retract_before_travel = 1 retract_before_wipe = 0% retract_layer_change = 1 retract_length = 1 retract_length_toolchange = 3 retract_lift = 0.6 retract_lift_above = 0 retract_lift_below = 199 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 50 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 0 wipe = 1 z_offset = 0 [printer:Original Prusa i3 MK2 0.6 nozzle] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 0 end_gcode = G4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors extruder_colour = #FFFF00 extruder_offset = 0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.35 min_layer_height = 0.1 nozzle_diameter = 0.6 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\n printer_settings_id = retract_before_travel = 1 retract_before_wipe = 0% retract_layer_change = 1 retract_length = 0.8 retract_length_toolchange = 3 retract_lift = 0.6 retract_lift_above = 0 retract_lift_below = 199 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 35 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 1 wipe = 1 z_offset = 0 [printer:Original Prusa i3 MK2 MM Single Mode] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 50 end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n extruder_colour = #FFAA55 extruder_offset = 0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.25 min_layer_height = 0.07 nozzle_diameter = 0.4 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN printer_settings_id = retract_before_travel = 3 retract_before_wipe = 60% retract_layer_change = 0 retract_length = 4 retract_length_toolchange = 6 retract_lift = 0.6 retract_lift_above = 0 retract_lift_below = 199 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 80 serial_port = serial_speed = 250000 single_extruder_multi_material = 1 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 1 wipe = 1 z_offset = 0 [printer:Original Prusa i3 MK2 MM Single Mode 0.6 nozzle] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 50 end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n extruder_colour = #FFAA55 extruder_offset = 0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.25 min_layer_height = 0.07 nozzle_diameter = 0.6 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN printer_settings_id = retract_before_travel = 3 retract_before_wipe = 60% retract_layer_change = 0 retract_length = 4 retract_length_toolchange = 6 retract_lift = 0.6 retract_lift_above = 0 retract_lift_below = 199 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 80 serial_port = serial_speed = 250000 single_extruder_multi_material = 1 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 1 wipe = 1 z_offset = 0 [printer:Original Prusa i3 MK2 MultiMaterial] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 50,50,50,50 end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 extruder_offset = 0x0,0x0,0x0,0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.25,0.25,0.25,0.25 min_layer_height = 0.07,0.07,0.07,0.07 nozzle_diameter = 0.4,0.4,0.4,0.4 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN printer_settings_id = retract_before_travel = 3,3,3,3 retract_before_wipe = 60%,60%,60%,60% retract_layer_change = 0,0,0,0 retract_length = 4,4,4,4 retract_length_toolchange = 4,4,4,4 retract_lift = 0.6,0.6,0.6,0.6 retract_lift_above = 0,0,0,0 retract_lift_below = 199,199,199,199 retract_restart_extra = 0,0,0,0 retract_restart_extra_toolchange = 0,0,0,0 retract_speed = 80,80,80,80 serial_port = serial_speed = 250000 single_extruder_multi_material = 1 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 0 wipe = 1,1,1,1 z_offset = 0 [printer:Original Prusa i3 MK2 MultiMaterial 0.6 nozzle] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 50,50,50,50 end_gcode = {if not has_wipe_tower}\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 extruder_offset = 0x0,0x0,0x0,0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.25,0.25,0.25,0.25 min_layer_height = 0.07,0.07,0.07,0.07 nozzle_diameter = 0.6,0.6,0.6,0.6 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN printer_settings_id = retract_before_travel = 3,3,3,3 retract_before_wipe = 60%,60%,60%,60% retract_layer_change = 0,0,0,0 retract_length = 4,4,4,4 retract_length_toolchange = 4,4,4,4 retract_lift = 0.6,0.6,0.6,0.6 retract_lift_above = 0,0,0,0 retract_lift_below = 199,199,199,199 retract_restart_extra = 0,0,0,0 retract_restart_extra_toolchange = 0,0,0,0 retract_speed = 80,80,80,80 serial_port = serial_speed = 250000 single_extruder_multi_material = 1 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 0 wipe = 1,1,1,1 z_offset = 0 [printer:Original Prusa i3 MK3] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 0 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors extruder_colour = #FFFF00 extruder_offset = 0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.25 min_layer_height = 0.07 nozzle_diameter = 0.4 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_before_travel = 1 retract_before_wipe = 0% retract_layer_change = 1 retract_length = 0.8 retract_length_toolchange = 3 retract_lift = 0.6 retract_lift_above = 0 retract_lift_below = 209 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 35 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 1 wipe = 1 z_offset = 0 [presets] print = 0.15mm OPTIMAL MK3 printer = Original Prusa i3 MK3 filament = Prusa PLA Slic3r-version_1.39.1/resources/profiles/Original Prusa i3 MK2MM.ini000066400000000000000000003125351324354444700250110ustar00rootroot00000000000000# generated by Slic3r Prusa Edition 1.39.0 on 2018-01-06 at 15:12:06 [print:0.05mm DETAIL] avoid_crossing_perimeters = 0 bottom_solid_layers = 10 bridge_acceleration = 300 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 complete_objects = 0 default_acceleration = 500 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 25% fill_pattern = cubic first_layer_acceleration = 500 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 20 gcode_comments = 0 infill_acceleration = 800 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.5 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 30 interface_shells = 0 layer_height = 0.05 max_print_speed = 80 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 300 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 30 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 15 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 30 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.3 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1.5 support_material_speed = 30 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 20 top_solid_layers = 15 travel_speed = 180 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.05mm DETAIL 0.25 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 10 bridge_acceleration = 300 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 complete_objects = 0 default_acceleration = 500 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.28 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 500 first_layer_extrusion_width = 0.3 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 20 gcode_comments = 0 infill_acceleration = 800 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 20 interface_shells = 0 layer_height = 0.05 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 300 perimeter_extruder = 1 perimeter_extrusion_width = 0 perimeter_speed = 20 perimeters = 4 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 10 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0 solid_infill_speed = 20 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.18 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1 support_material_speed = 20 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0 top_solid_infill_speed = 20 top_solid_layers = 15 travel_speed = 200 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.05mm DETAIL MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 10 bridge_acceleration = 300 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 500 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 25% fill_pattern = grid first_layer_acceleration = 500 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 20 gcode_comments = 0 infill_acceleration = 800 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 30 interface_shells = 0 layer_height = 0.05 max_print_speed = 80 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 300 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 30 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 15 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 30 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.3 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1.5 support_material_speed = 30 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 20 top_solid_layers = 15 travel_speed = 180 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.10mm DETAIL] avoid_crossing_perimeters = 0 bottom_solid_layers = 7 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.1 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 40 top_solid_layers = 9 travel_speed = 120 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.10mm DETAIL 0.25 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 7 bridge_acceleration = 600 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.25 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.25 fill_angle = 45 fill_density = 15% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.25 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 1600 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.25 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 40 interface_shells = 0 layer_height = 0.1 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 600 perimeter_extruder = 1 perimeter_extrusion_width = 0.25 perimeter_speed = 25 perimeters = 4 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 10 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.25 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.18 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.25 top_solid_infill_speed = 30 top_solid_layers = 9 travel_speed = 120 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.10mm DETAIL MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 30 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 35 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = grid first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 3500 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 200 interface_shells = 0 layer_height = 0.1 max_print_speed = 250 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 45 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 200 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 50 top_solid_layers = 5 travel_speed = 250 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm 100mms Linear Advance] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 50 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 100 interface_shells = 0 layer_height = 0.15 max_print_speed = 150 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 60 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 30 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 100 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 60 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 70 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 40 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL 0.25 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 600 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.25 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.25 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.25 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 1600 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.25 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 40 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 600 perimeter_extruder = 1 perimeter_extrusion_width = 0.25 perimeter_speed = 25 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 10 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.25 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.2 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 35 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.25 top_solid_infill_speed = 30 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.61 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.6 top_solid_infill_speed = 40 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 30 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 35 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = grid first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 3500 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 200 interface_shells = 0 layer_height = 0.15 max_print_speed = 250 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 45 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 200 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 50 top_solid_layers = 5 travel_speed = 250 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL SOLUBLE FULL] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 25 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 4 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 2 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 1 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 30 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL SOLUBLE INTERFACE] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 25 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 3 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 30 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm 100mms Linear Advance] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 50 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 100 interface_shells = 0 layer_height = 0.2 max_print_speed = 150 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 60 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 30 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 100 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 60 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 70 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm FAST MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 30 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 35 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = grid first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 3500 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 200 interface_shells = 0 layer_height = 0.2 max_print_speed = 250 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 45 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 200 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 50 top_solid_layers = 5 travel_speed = 250 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 50 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 40 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.61 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.6 top_solid_infill_speed = 40 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL SOLUBLE FULL] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 4 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 2 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 1 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 30 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL SOLUBLE INTERFACE] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 3 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 30 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST] avoid_crossing_perimeters = 0 bottom_solid_layers = 3 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.7 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.43 perimeter_speed = 50 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.7 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.43 top_solid_infill_speed = 50 top_solid_layers = 4 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 7 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.61 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.6 top_solid_infill_speed = 50 top_solid_layers = 9 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST sol full 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 3 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 4 support_material_extrusion_width = 0.55 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 3 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.57 top_solid_infill_speed = 50 top_solid_layers = 4 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST sol int 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 3 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.55 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.57 top_solid_infill_speed = 50 top_solid_layers = 4 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [filament:ColorFabb Brass Bronze] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.3 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 210 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 210 [filament:ColorFabb HT] bed_temperature = 105 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 10 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 105 first_layer_temperature = 270 max_fan_speed = 20 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 270 [filament:ColorFabb PLA-PHA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #FF3232 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 15 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 210 [filament:ColorFabb Woodfil] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 200 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 200 [filament:ColorFabb XT] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 90 first_layer_temperature = 260 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 270 [filament:ColorFabb XT-CF20] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 1 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 90 first_layer_temperature = 260 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 260 [filament:ColorFabb nGen] bed_temperature = 85 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 10 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = NGEN first_layer_bed_temperature = 85 first_layer_temperature = 240 max_fan_speed = 35 min_fan_speed = 20 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:ColorFabb nGen flex] bed_temperature = 85 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 10 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 5 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = FLEX first_layer_bed_temperature = 85 first_layer_temperature = 260 max_fan_speed = 35 min_fan_speed = 20 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 260 [filament:E3D Edge] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:E3D PC-ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 270 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 270 [filament:Fillamentum ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 100 first_layer_temperature = 240 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 240 [filament:Fillamentum ASA] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 265 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 265 [filament:Fillamentum CPE HG100 HM100] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "CPE HG100 , CPE HM100" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 90 first_layer_temperature = 260 max_fan_speed = 80 min_fan_speed = 80 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 260 [filament:Fillamentum Timberfil] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 190 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 190 [filament:Generic ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 100 first_layer_temperature = 255 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 255 [filament:Generic PET] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:Generic PLA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #FF3232 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 15 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 210 [filament:Polymaker PC-Max] bed_temperature = 120 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 120 first_layer_temperature = 270 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 270 [filament:Primavalue PVA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #FFFFD7 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of materials tested with standart PVA print settings for MK2:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" filament_settings_id = filament_soluble = 1 filament_type = PVA first_layer_bed_temperature = 60 first_layer_temperature = 195 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 195 [filament:Prusa ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 100 first_layer_temperature = 255 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 255 [filament:Prusa HIPS] bed_temperature = 100 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 0.9 fan_always_on = 1 fan_below_layer_time = 10 filament_colour = #FFFFD7 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 1 filament_type = HIPS first_layer_bed_temperature = 100 first_layer_temperature = 220 max_fan_speed = 20 min_fan_speed = 20 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 [filament:Prusa PET] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:Prusa PLA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #FF3232 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 15 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 210 [filament:SemiFlex or Flexfill 98A] bed_temperature = 50 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 cooling = 0 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #00CA0A filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 2.5 filament_notes = "List of materials tested with FLEX print settings & FLEX material settings for MK2:\n\nFillamentum Flex 98A\nFillamentum Flex 92A\nPlasty MladeÄ PP\nPlasty MladeÄ TPE32 \nPlasty MladeÄ TPE88" filament_settings_id = filament_soluble = 0 filament_type = FLEX first_layer_bed_temperature = 50 first_layer_temperature = 220 max_fan_speed = 90 min_fan_speed = 70 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 230 [filament:Taulman Bridge] bed_temperature = 50 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #DEE0E6 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 90 first_layer_temperature = 240 max_fan_speed = 5 min_fan_speed = 0 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 250 [filament:Taulman T-Glase] bed_temperature = 90 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 90 first_layer_temperature = 240 max_fan_speed = 5 min_fan_speed = 0 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 240 [filament:Verbatim BVOH] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #FFFFD7 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 1 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 210 [filament:Verbatim PP] bed_temperature = 100 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 2 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #DEE0E6 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 5 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 220 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 [printer:Original Prusa i3 MK2 MM Single Mode] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 50 end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n extruder_colour = #FFAA55 extruder_offset = 0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.25 min_layer_height = 0.07 nozzle_diameter = 0.4 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN printer_settings_id = retract_before_travel = 3 retract_before_wipe = 60% retract_layer_change = 0 retract_length = 4 retract_length_toolchange = 6 retract_lift = 0.6 retract_lift_above = 0 retract_lift_below = 199 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 80 serial_port = serial_speed = 250000 single_extruder_multi_material = 1 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 1 wipe = 1 z_offset = 0 [printer:Original Prusa i3 MK2 MM Single Mode 0.6 nozzle] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 50 end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n extruder_colour = #FFAA55 extruder_offset = 0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.25 min_layer_height = 0.07 nozzle_diameter = 0.6 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN printer_settings_id = retract_before_travel = 3 retract_before_wipe = 60% retract_layer_change = 0 retract_length = 4 retract_length_toolchange = 6 retract_lift = 0.6 retract_lift_above = 0 retract_lift_below = 199 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 80 serial_port = serial_speed = 250000 single_extruder_multi_material = 1 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 1 wipe = 1 z_offset = 0 [printer:Original Prusa i3 MK2 MultiMaterial] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 50,50,50,50 end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes.\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 extruder_offset = 0x0,0x0,0x0,0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.25,0.25,0.25,0.25 min_layer_height = 0.07,0.07,0.07,0.07 nozzle_diameter = 0.4,0.4,0.4,0.4 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN printer_settings_id = retract_before_travel = 3,3,3,3 retract_before_wipe = 60%,60%,60%,60% retract_layer_change = 0,0,0,0 retract_length = 4,4,4,4 retract_length_toolchange = 4,4,4,4 retract_lift = 0.6,0.6,0.6,0.6 retract_lift_above = 0,0,0,0 retract_lift_below = 199,199,199,199 retract_restart_extra = 0,0,0,0 retract_restart_extra_toolchange = 0,0,0,0 retract_speed = 80,80,80,80 serial_port = serial_speed = 250000 single_extruder_multi_material = 1 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 0 wipe = 1,1,1,1 z_offset = 0 [printer:Original Prusa i3 MK2 MultiMaterial 0.6 nozzle] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 50,50,50,50 end_gcode = {if not has_wipe_tower}\nG1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\n{endif}\nM107 ; fan off\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n extruder_colour = #FFAA55;#5182DB;#4ECDD3;#FB7259 extruder_offset = 0x0,0x0,0x0,0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.25,0.25,0.25,0.25 min_layer_height = 0.07,0.07,0.07,0.07 nozzle_diameter = 0.6,0.6,0.6,0.6 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN printer_settings_id = retract_before_travel = 3,3,3,3 retract_before_wipe = 60%,60%,60%,60% retract_layer_change = 0,0,0,0 retract_length = 4,4,4,4 retract_length_toolchange = 4,4,4,4 retract_lift = 0.6,0.6,0.6,0.6 retract_lift_above = 0,0,0,0 retract_lift_below = 199,199,199,199 retract_restart_extra = 0,0,0,0 retract_restart_extra_toolchange = 0,0,0,0 retract_speed = 80,80,80,80 serial_port = serial_speed = 250000 single_extruder_multi_material = 1 start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM201 X9000 Y9000 Z500 E10000 ; sets maximum accelerations, mm/sec^2\nM203 X500 Y500 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1500 T1500 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.2 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\n{if not has_wipe_tower}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG1 E-4 F1000.0\n{endif}\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 0 wipe = 1,1,1,1 z_offset = 0 [presets] print = 0.15mm OPTIMAL printer = Original Prusa i3 MK2 MultiMaterial filament = Prusa PLA filament_1 = Prusa PLA filament_2 = Prusa PLA filament_3 = Prusa PLA Slic3r-version_1.39.1/resources/profiles/Original Prusa i3 MK3.ini000066400000000000000000002704501324354444700245570ustar00rootroot00000000000000# generated by Slic3r Prusa Edition 1.39.0 on 2018-02-02 at 10:48:46 [print:0.05mm DETAIL] avoid_crossing_perimeters = 0 bottom_solid_layers = 10 bridge_acceleration = 300 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 complete_objects = 0 default_acceleration = 500 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 25% fill_pattern = cubic first_layer_acceleration = 500 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 20 gcode_comments = 0 infill_acceleration = 800 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.5 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 30 interface_shells = 0 layer_height = 0.05 max_print_speed = 80 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 300 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 30 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 15 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 30 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.3 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1.5 support_material_speed = 30 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 20 top_solid_layers = 15 travel_speed = 180 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.05mm DETAIL 0.25 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 10 bridge_acceleration = 300 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 complete_objects = 0 default_acceleration = 500 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.28 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 500 first_layer_extrusion_width = 0.3 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 20 gcode_comments = 0 infill_acceleration = 800 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 20 interface_shells = 0 layer_height = 0.05 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 300 perimeter_extruder = 1 perimeter_extrusion_width = 0 perimeter_speed = 20 perimeters = 4 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 10 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0 solid_infill_speed = 20 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.18 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1 support_material_speed = 20 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0 top_solid_infill_speed = 20 top_solid_layers = 15 travel_speed = 200 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.05mm DETAIL MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 10 bridge_acceleration = 300 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 500 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 25% fill_pattern = grid first_layer_acceleration = 500 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 20 gcode_comments = 0 infill_acceleration = 800 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 30 interface_shells = 0 layer_height = 0.05 max_print_speed = 80 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 300 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 30 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 15 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 30 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.3 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1.5 support_material_speed = 30 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 20 top_solid_layers = 15 travel_speed = 180 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.10mm DETAIL] avoid_crossing_perimeters = 0 bottom_solid_layers = 7 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders==1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.1 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 40 top_solid_layers = 9 travel_speed = 120 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.10mm DETAIL 0.25 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 7 bridge_acceleration = 600 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.25 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.25 fill_angle = 45 fill_density = 15% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.25 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 1600 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.25 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 40 interface_shells = 0 layer_height = 0.1 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 600 perimeter_extruder = 1 perimeter_extrusion_width = 0.25 perimeter_speed = 25 perimeters = 4 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 10 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.25 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.18 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.25 top_solid_infill_speed = 30 top_solid_layers = 9 travel_speed = 120 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.10mm DETAIL MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 7 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 30 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 35 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = grid first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 1500 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 170 interface_shells = 0 layer_height = 0.1 max_print_speed = 170 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 45 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 170 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 50 top_solid_layers = 9 travel_speed = 170 wipe_tower = 0 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm 100mms Linear Advance] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 50 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 100 interface_shells = 0 layer_height = 0.15 max_print_speed = 150 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 60 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 30 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 100 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 60 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 70 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 40 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL 0.25 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 600 bridge_angle = 0 bridge_flow_ratio = 0.7 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.25 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.25 external_perimeter_speed = 20 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.25 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.25 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 1600 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.25 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 40 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 600 perimeter_extruder = 1 perimeter_extrusion_width = 0.25 perimeter_speed = 25 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 10 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.25 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.2 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 1 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 35 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.25 top_solid_infill_speed = 30 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.61 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.6 top_solid_infill_speed = 40 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 30 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 35 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = grid first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 1500 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 170 interface_shells = 0 layer_height = 0.15 max_print_speed = 170 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 45 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 170 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 50 top_solid_layers = 7 travel_speed = 170 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL SOLUBLE FULL] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 25 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 4 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 2 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 1 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 30 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.15mm OPTIMAL SOLUBLE INTERFACE] avoid_crossing_perimeters = 0 bottom_solid_layers = 5 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 25 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.15 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 3 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.45 top_solid_infill_speed = 30 top_solid_layers = 7 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm 100mms Linear Advance] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 50 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 100 interface_shells = 0 layer_height = 0.2 max_print_speed = 150 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 60 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 30 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 100 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 60 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 70 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm FAST MK3] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 30 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 35 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = grid first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 1500 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 170 interface_shells = 0 layer_height = 0.2 max_print_speed = 170 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 45 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 170 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 50 top_solid_layers = 5 travel_speed = 170 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 50 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 40 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.61 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 50 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.6 top_solid_infill_speed = 40 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL SOLUBLE FULL] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft/skirt extruder & Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 4 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 2 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 1 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 30 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.20mm NORMAL SOLUBLE INTERFACE] avoid_crossing_perimeters = 0 bottom_solid_layers = 4 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.45 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.45 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.2 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.45 solid_infill_speed = 40 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.45 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 3 support_material_interface_spacing = 0.1 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.4 top_solid_infill_speed = 30 top_solid_layers = 5 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST] avoid_crossing_perimeters = 0 bottom_solid_layers = 3 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.95 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.4 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.42 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.7 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.43 perimeter_speed = 50 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.7 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 1 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.43 top_solid_infill_speed = 50 top_solid_layers = 4 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 7 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.61 external_perimeter_speed = 40 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 0 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 50 perimeters = 3 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 1 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 0 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0.15 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.35 support_material_interface_contact_loops = 0 support_material_interface_extruder = 1 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 0 support_material_threshold = 45 support_material_with_sheath = 0 support_material_xy_spacing = 60% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.6 top_solid_infill_speed = 50 top_solid_layers = 9 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 15 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST sol full 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 3 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 4 support_material_extrusion_width = 0.55 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 3 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 120% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.57 top_solid_infill_speed = 50 top_solid_layers = 4 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [print:0.35mm FAST sol int 0.6 nozzle] avoid_crossing_perimeters = 0 bottom_solid_layers = 3 bridge_acceleration = 1000 bridge_angle = 0 bridge_flow_ratio = 0.8 bridge_speed = 20 brim_width = 0 clip_multipart_objects = 1 compatible_printers = compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 complete_objects = 0 default_acceleration = 1000 dont_support_bridges = 1 elefant_foot_compensation = 0 ensure_vertical_shell_thickness = 1 external_fill_pattern = rectilinear external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 30 external_perimeters_first = 0 extra_perimeters = 0 extruder_clearance_height = 20 extruder_clearance_radius = 20 extrusion_width = 0.67 fill_angle = 45 fill_density = 20% fill_pattern = cubic first_layer_acceleration = 1000 first_layer_extrusion_width = 0.65 first_layer_height = 0.2 first_layer_speed = 30 gap_fill_speed = 40 gcode_comments = 0 infill_acceleration = 2000 infill_every_layers = 1 infill_extruder = 1 infill_extrusion_width = 0.75 infill_first = 0 infill_only_where_needed = 0 infill_overlap = 25% infill_speed = 60 interface_shells = 0 layer_height = 0.35 max_print_speed = 100 max_volumetric_extrusion_rate_slope_negative = 0 max_volumetric_extrusion_rate_slope_positive = 0 max_volumetric_speed = 0 min_skirt_length = 4 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 output_filename_format = [input_filename_base].gcode overhangs = 1 perimeter_acceleration = 800 perimeter_extruder = 1 perimeter_extrusion_width = 0.65 perimeter_speed = 40 perimeters = 2 post_process = print_settings_id = raft_layers = 0 resolution = 0 seam_position = nearest skirt_distance = 2 skirt_height = 3 skirts = 0 small_perimeter_speed = 20 solid_infill_below_area = 0 solid_infill_every_layers = 0 solid_infill_extruder = 1 solid_infill_extrusion_width = 0.65 solid_infill_speed = 60 spiral_vase = 0 standby_temperature_delta = -5 support_material = 1 support_material_angle = 0 support_material_buildplate_only = 0 support_material_contact_distance = 0 support_material_enforce_layers = 0 support_material_extruder = 0 support_material_extrusion_width = 0.55 support_material_interface_contact_loops = 0 support_material_interface_extruder = 4 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 support_material_interface_speed = 100% support_material_pattern = rectilinear support_material_spacing = 2 support_material_speed = 50 support_material_synchronize_layers = 1 support_material_threshold = 80 support_material_with_sheath = 0 support_material_xy_spacing = 150% thin_walls = 0 threads = 4 top_infill_extrusion_width = 0.57 top_solid_infill_speed = 50 top_solid_layers = 4 travel_speed = 120 wipe_tower = 1 wipe_tower_per_color_wipe = 20 wipe_tower_width = 60 wipe_tower_x = 180 wipe_tower_y = 140 xy_size_compensation = 0 [filament:ColorFabb Brass Bronze] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.3 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 210 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 210 [filament:ColorFabb HT] bed_temperature = 105 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 10 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 105 first_layer_temperature = 270 max_fan_speed = 20 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 270 [filament:ColorFabb PLA-PHA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #FF3232 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 15 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 210 [filament:ColorFabb Woodfil] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 200 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 200 [filament:ColorFabb XT] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 90 first_layer_temperature = 260 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 270 [filament:ColorFabb XT-CF20] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 1 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 90 first_layer_temperature = 260 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 260 [filament:ColorFabb nGen] bed_temperature = 85 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 10 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = NGEN first_layer_bed_temperature = 85 first_layer_temperature = 240 max_fan_speed = 35 min_fan_speed = 20 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:ColorFabb nGen flex] bed_temperature = 85 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 10 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 5 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = FLEX first_layer_bed_temperature = 85 first_layer_temperature = 260 max_fan_speed = 35 min_fan_speed = 20 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 260 [filament:E3D Edge] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:E3D PC-ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 270 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 270 [filament:Fillamentum ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 100 first_layer_temperature = 240 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 240 [filament:Fillamentum ASA] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 265 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 265 [filament:Fillamentum CPE HG100 HM100] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "CPE HG100 , CPE HM100" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 90 first_layer_temperature = 275 max_fan_speed = 50 min_fan_speed = 50 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 275 [filament:Fillamentum Timberfil] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #804040 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 190 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 190 [filament:Generic ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 100 first_layer_temperature = 255 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 255 [filament:Generic PET] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:Generic PLA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #FF3232 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 15 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 210 [filament:Polymaker PC-Max] bed_temperature = 120 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 120 first_layer_temperature = 270 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 270 [filament:Primavalue PVA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #FFFFD7 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of materials tested with standart PVA print settings for MK2:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" filament_settings_id = filament_soluble = 1 filament_type = PVA first_layer_bed_temperature = 60 first_layer_temperature = 195 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 195 [filament:Prusa ABS] bed_temperature = 100 bridge_fan_speed = 30 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #3A80CA filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "List of materials tested with standart ABS print settings for MK2:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty MladeÄ ABS" filament_settings_id = filament_soluble = 0 filament_type = ABS first_layer_bed_temperature = 100 first_layer_temperature = 255 max_fan_speed = 30 min_fan_speed = 10 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 255 [filament:Prusa HIPS] bed_temperature = 100 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 0.9 fan_always_on = 1 fan_below_layer_time = 10 filament_colour = #FFFFD7 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 13 filament_notes = "" filament_settings_id = filament_soluble = 1 filament_type = HIPS first_layer_bed_temperature = 100 first_layer_temperature = 220 max_fan_speed = 20 min_fan_speed = 20 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 [filament:Prusa PET] bed_temperature = 90 bridge_fan_speed = 50 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of manufacturers tested with standart PET print settings for MK2:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty MladeÄ PETG" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" temperature = 240 [filament:Prusa PLA] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #FF3232 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 15 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 210 [filament:SemiFlex or Flexfill 98A] bed_temperature = 50 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 cooling = 0 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1.2 fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #00CA0A filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 1.5 filament_notes = "List of materials tested with FLEX print settings & FLEX material settings for MK2:\n\nFillamentum Flex 98A\nFillamentum Flex 92A\nPlasty MladeÄ PP\nPlasty MladeÄ TPE32 \nPlasty MladeÄ TPE88" filament_settings_id = filament_soluble = 0 filament_type = FLEX first_layer_bed_temperature = 50 first_layer_temperature = 240 max_fan_speed = 90 min_fan_speed = 70 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 240 [filament:Taulman Bridge] bed_temperature = 50 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #DEE0E6 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 90 first_layer_temperature = 240 max_fan_speed = 5 min_fan_speed = 0 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 250 [filament:Taulman T-Glase] bed_temperature = 90 bridge_fan_speed = 40 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 3 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 20 filament_colour = #FF8000 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "" filament_settings_id = filament_soluble = 0 filament_type = PET first_layer_bed_temperature = 90 first_layer_temperature = 240 max_fan_speed = 5 min_fan_speed = 0 min_print_speed = 5 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 240 [filament:Verbatim BVOH] bed_temperature = 60 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 0 disable_fan_first_layers = 1 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #FFFFD7 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 10 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" filament_settings_id = filament_soluble = 1 filament_type = PLA first_layer_bed_temperature = 60 first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 210 [filament:Verbatim PP] bed_temperature = 100 bridge_fan_speed = 100 compatible_printers = compatible_printers_condition = cooling = 1 disable_fan_first_layers = 2 end_filament_gcode = "; Filament-specific end gcode" extrusion_multiplier = 1 fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #DEE0E6 filament_cost = 0 filament_density = 0 filament_diameter = 1.75 filament_max_volumetric_speed = 5 filament_notes = "List of materials tested with standart PLA print settings for MK2:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty MladeÄ PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" filament_settings_id = filament_soluble = 0 filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 220 max_fan_speed = 100 min_fan_speed = 100 min_print_speed = 15 slowdown_below_layer_time = 20 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 [printer:Original Prusa i3 MK3] bed_shape = 0x0,250x0,250x210,0x210 before_layer_gcode = ;BEFORE_LAYER_CHANGE\n;[layer_z]\n\n between_objects_gcode = deretract_speed = 0 end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors extruder_colour = #FFFF00 extruder_offset = 0x0 gcode_flavor = marlin layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] max_layer_height = 0.25 min_layer_height = 0.07 nozzle_diameter = 0.4 octoprint_apikey = octoprint_host = printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_before_travel = 1 retract_before_wipe = 0% retract_layer_change = 1 retract_length = 0.8 retract_length_toolchange = 3 retract_lift = 0.6 retract_lift_above = 0 retract_lift_below = 209 retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 35 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 use_volumetric_e = 0 variable_layer_height = 1 wipe = 1 z_offset = 0 [presets] print = 0.15mm OPTIMAL MK3 printer = Original Prusa i3 MK3 filament = Prusa PLA Slic3r-version_1.39.1/slic3r.pl000077500000000000000000000737041324354444700162750ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/lib"; use local::lib '--no-create', "$FindBin::Bin/local-lib"; } use File::Basename qw(basename); use Getopt::Long qw(:config no_auto_abbrev); use List::Util qw(first); use POSIX qw(setlocale LC_NUMERIC); use Slic3r; use Slic3r::Geometry qw(deg2rad); use Time::HiRes qw(gettimeofday tv_interval); $|++; binmode STDOUT, ':utf8'; # Convert all parameters from the local code page to utf8 on Windows. @ARGV = map Slic3r::decode_path($_), @ARGV if $^O eq 'MSWin32'; our %opt = (); my %cli_options = (); { my %options = ( 'help' => sub { usage() }, 'version' => sub { print "$Slic3r::VERSION\n"; exit 0 }, 'debug' => \$Slic3r::debug, 'gui' => \$opt{gui}, 'no-gui' => \$opt{no_gui}, 'o|output=s' => \$opt{output}, 'save=s' => \$opt{save}, 'load=s@' => \$opt{load}, 'autosave=s' => \$opt{autosave}, 'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config}, 'no-controller' => \$opt{no_controller}, 'no-plater' => \$opt{no_plater}, 'gui-mode=s' => \$opt{obsolete_ignore_this_option_gui_mode}, 'datadir=s' => \$opt{datadir}, 'export-svg' => \$opt{export_svg}, 'merge|m' => \$opt{merge}, 'repair' => \$opt{repair}, 'cut=f' => \$opt{cut}, 'split' => \$opt{split}, 'info' => \$opt{info}, 'scale=f' => \$opt{scale}, 'rotate=f' => \$opt{rotate}, 'duplicate=i' => \$opt{duplicate}, 'duplicate-grid=s' => \$opt{duplicate_grid}, 'print-center=s' => \$opt{print_center}, 'dont-arrange' => \$opt{dont_arrange}, ); foreach my $opt_key (keys %{$Slic3r::Config::Options}) { my $cli = $Slic3r::Config::Options->{$opt_key}->{cli} or next; # allow both the dash-separated option name and the full opt_key $options{ "$opt_key|$cli" } = \$cli_options{$opt_key}; } @ARGV = grep !/^-psn_\d/, @ARGV if $^O eq 'darwin'; GetOptions(%options) or usage(1); } # load configuration files my @external_configs = (); if ($opt{load}) { foreach my $configfile (@{$opt{load}}) { if (-e $configfile) { push @external_configs, Slic3r::Config::load($configfile); } elsif (-e "$FindBin::Bin/$configfile") { printf STDERR "Loading $FindBin::Bin/$configfile\n"; push @external_configs, Slic3r::Config::load("$FindBin::Bin/$configfile"); } else { $opt{ignore_nonexistent_config} or die "Cannot find specified configuration file ($configfile).\n"; } } } # process command line options my $cli_config = Slic3r::Config->new; foreach my $c (@external_configs, Slic3r::Config->new_from_cli(%cli_options)) { $c->normalize; # expand shortcuts before applying, otherwise destination values would be already filled with defaults $cli_config->apply($c); } # save configuration if ($opt{save}) { if (@{$cli_config->get_keys} > 0) { $cli_config->save($opt{save}); } else { Slic3r::Config::new_from_defaults->save($opt{save}); } } # apply command line config on top of default config my $config = Slic3r::Config::new_from_defaults; $config->apply($cli_config); # launch GUI my $gui; if ((!@ARGV || $opt{gui}) && !$opt{no_gui} && !$opt{save} && eval "require Slic3r::GUI; 1") { { no warnings 'once'; $Slic3r::GUI::datadir = Slic3r::decode_path($opt{datadir} // ''); $Slic3r::GUI::no_controller = $opt{no_controller}; $Slic3r::GUI::no_plater = $opt{no_plater}; $Slic3r::GUI::autosave = $opt{autosave}; } $gui = Slic3r::GUI->new; setlocale(LC_NUMERIC, 'C'); $gui->{mainframe}->load_config_file($_) for @{$opt{load}}; $gui->{mainframe}->load_config($cli_config); my @input_files = @ARGV; $gui->{mainframe}{plater}->load_files(\@input_files) unless $opt{no_plater}; $gui->MainLoop; exit; } die $@ if $@ && $opt{gui}; if (@ARGV) { # slicing from command line $config->validate; if ($opt{repair}) { foreach my $file (@ARGV) { die "Repair is currently supported only on STL files\n" if $file !~ /\.[sS][tT][lL]$/; my $output_file = $file; $output_file =~ s/\.([sS][tT][lL])$/_fixed.obj/; my $tmesh = Slic3r::TriangleMesh->new; $tmesh->ReadSTLFile($file); $tmesh->repair; $tmesh->WriteOBJFile($output_file); } exit; } if ($opt{cut}) { foreach my $file (@ARGV) { my $model = Slic3r::Model->read_from_file($file); my $mesh = $model->mesh; $mesh->translate(0, 0, -$mesh->bounding_box->z_min); my $upper = Slic3r::TriangleMesh->new; my $lower = Slic3r::TriangleMesh->new; $mesh->cut($opt{cut}, $upper, $lower); $upper->repair; $lower->repair; $upper->write_ascii("${file}_upper.stl") if $upper->facets_count > 0; $lower->write_ascii("${file}_lower.stl") if $lower->facets_count > 0; } exit; } if ($opt{split}) { foreach my $file (@ARGV) { my $model = Slic3r::Model->read_from_file($file); my $mesh = $model->mesh; $mesh->repair; my $part_count = 0; foreach my $new_mesh (@{$mesh->split}) { my $output_file = sprintf '%s_%02d.stl', $file, ++$part_count; printf "Writing to %s\n", basename($output_file); $new_mesh->write_binary($output_file); } } exit; } while (my $input_file = shift @ARGV) { my $model; if ($opt{merge}) { my @models = map Slic3r::Model->read_from_file($_), $input_file, (splice @ARGV, 0); $model = Slic3r::Model->merge(@models); } else { $model = Slic3r::Model->read_from_file($input_file); } if ($opt{info}) { $model->print_info; next; } if (defined $opt{duplicate_grid}) { $opt{duplicate_grid} = [ split /[,x]/, $opt{duplicate_grid}, 2 ]; } if (defined $opt{print_center}) { $opt{print_center} = Slic3r::Pointf->new(split /[,x]/, $opt{print_center}, 2); } my $sprint = Slic3r::Print::Simple->new( scale => $opt{scale} // 1, rotate => deg2rad($opt{rotate} // 0), duplicate => $opt{duplicate} // 1, duplicate_grid => $opt{duplicate_grid} // [1,1], print_center => $opt{print_center} // Slic3r::Pointf->new(100,100), dont_arrange => $opt{dont_arrange} // 0, status_cb => sub { my ($percent, $message) = @_; printf "=> %s\n", $message; }, output_file => $opt{output}, ); # This is delegated to C++ PrintObject::apply_config(). $sprint->apply_config($config); $sprint->set_model($model); # Do the apply_config once again to validate the layer height profiles at all the newly added PrintObjects. $sprint->apply_config($config); if ($opt{export_svg}) { $sprint->export_svg; } else { my $t0 = [gettimeofday]; # The following call may die if the output_filename_format template substitution fails, # if the file cannot be written into, or if the post processing scripts cannot be executed. $sprint->export_gcode; # output some statistics { my $duration = tv_interval($t0); printf "Done. Process took %d minutes and %.3f seconds\n", int($duration/60), ($duration - int($duration/60)*60); # % truncates to integer } printf "Filament required: %.1fmm (%.1fcm3)\n", $sprint->total_used_filament, $sprint->total_extruded_volume/1000; } } } else { usage(1) unless $opt{save}; } sub usage { my ($exit_code) = @_; my $config = Slic3r::Config::new_from_defaults->as_hash; print <<"EOF"; Slic3r $Slic3r::VERSION is a STL-to-GCODE translator for RepRap 3D printers written by Alessandro Ranellucci - http://slic3r.org/ 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: $config->{threads}) 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-plater Disable the plater tab --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: $config->{output_filename_format}) --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: --nozzle-diameter Diameter of nozzle in mm (default: $config->{nozzle_diameter}->[0]) --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: $config->{z_offset}) --gcode-flavor The type of G-code to generate (reprap/teacup/repetier/makerware/sailfish/mach3/machinekit/smoothie/no-extrusion, default: $config->{gcode_flavor}) --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) --set-and-wait-temperatures Use M190 instead of M140 for temperature changes past the first (default: no) --gcode-comments Make G-code verbose by adding comments (default: no) Filament options: --filament-diameter Diameter in mm of your raw filament (default: $config->{filament_diameter}->[0]) --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: $config->{extrusion_multiplier}->[0]) --temperature Extrusion temperature in degree Celsius, set 0 to disable (default: $config->{temperature}->[0]) --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: $config->{bed_temperature}[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: $config->{travel_speed}) --perimeter-speed Speed of print moves for perimeters in mm/s (default: $config->{perimeter_speed}) --small-perimeter-speed Speed of print moves for small perimeters in mm/s or % over perimeter speed (default: $config->{small_perimeter_speed}) --external-perimeter-speed Speed of print moves for the external perimeter in mm/s or % over perimeter speed (default: $config->{external_perimeter_speed}) --infill-speed Speed of print moves in mm/s (default: $config->{infill_speed}) --solid-infill-speed Speed of print moves for solid surfaces in mm/s or % over infill speed (default: $config->{solid_infill_speed}) --top-solid-infill-speed Speed of print moves for top surfaces in mm/s or % over solid infill speed (default: $config->{top_solid_infill_speed}) --support-material-speed Speed of support material print moves in mm/s (default: $config->{support_material_speed}) --support-material-interface-speed Speed of support material interface print moves in mm/s or % over support material speed (default: $config->{support_material_interface_speed}) --bridge-speed Speed of bridge print moves in mm/s (default: $config->{bridge_speed}) --gap-fill-speed Speed of gap fill print moves in mm/s (default: $config->{gap_fill_speed}) --first-layer-speed Speed of print moves for bottom layer, expressed either as an absolute value or as a percentage over normal speeds (default: $config->{first_layer_speed}) Acceleration options: --perimeter-acceleration Overrides firmware's default acceleration for perimeters. (mm/s^2, set zero to disable; default: $config->{perimeter_acceleration}) --infill-acceleration Overrides firmware's default acceleration for infill. (mm/s^2, set zero to disable; default: $config->{infill_acceleration}) --bridge-acceleration Overrides firmware's default acceleration for bridges. (mm/s^2, set zero to disable; default: $config->{bridge_acceleration}) --first-layer-acceleration Overrides firmware's default acceleration for first layer. (mm/s^2, set zero to disable; default: $config->{first_layer_acceleration}) --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: $config->{default_acceleration}) Accuracy options: --layer-height Layer height in mm (default: $config->{layer_height}) --first-layer-height Layer height for first layer (mm or %, default: $config->{first_layer_height}) --infill-every-layers Infill every N layers (default: $config->{infill_every_layers}) --solid-infill-every-layers Force a solid layer every N layers (default: $config->{solid_infill_every_layers}) Print options: --perimeters Number of perimeters/horizontal skins (range: 0+, default: $config->{perimeters}) --top-solid-layers Number of solid layers to do for top surfaces (range: 0+, default: $config->{top_solid_layers}) --bottom-solid-layers Number of solid layers to do for bottom surfaces (range: 0+, default: $config->{bottom_solid_layers}) --solid-layers Shortcut for setting the two options above at once --fill-density Infill density (range: 0%-100%, default: $config->{fill_density}%) --fill-angle Infill angle in degrees (range: 0-90, default: $config->{fill_angle}) --fill-pattern Pattern to use to fill non-solid layers (default: $config->{fill_pattern}) --external-fill-pattern Pattern to use to fill solid layers (default: $config->{external_fill_pattern}) --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 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: $config->{seam_position}). --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: $config->{solid_infill_below_area}) --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) --ensure-vertical-shell-thickness Add solid infill near sloping surfaces to guarantee the vertical shell thickness (top+bottom solid layers). (default: no) --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: $config->{support_material_threshold}) --support-material-pattern Pattern to use for support material (default: $config->{support_material_pattern}) --support-material-with-sheath Add a sheath (a single perimeter line) around the base support. This makes the support more reliable, but also more difficult to remove. (default: yes) --support-material-spacing Spacing between pattern lines (mm, default: $config->{support_material_spacing}) --support-material-angle Support material angle in degrees (range: 0-90, default: $config->{support_material_angle}) --support-material-contact-distance Vertical distance between object and support material (0+, default: $config->{support_material_contact_distance}) --support-material-xy-spacing "XY separation between an object and its support. If expressed as percentage (for example 50%), it will be calculated over external perimeter width (default: half of exteral perimeter width) --support-material-interface-layers Number of perpendicular layers between support material and object (0+, default: $config->{support_material_interface_layers}) --support-material-interface-spacing Spacing between interface pattern lines (mm, set 0 to get a solid layer, default: $config->{support_material_interface_spacing}) --raft-layers Number of layers to raise the printed objects by (range: 0+, default: $config->{raft_layers}) --support-material-enforce-layers Enforce support material on the specified number of layers from bottom, regardless of --support-material and threshold (0+, default: $config->{support_material_enforce_layers}) --support-material-buildplate-only Only create support if it lies on a build plate. Don't create support on a print. (default: no) --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: $config->{retract_length}[0]) --retract-speed Speed for retraction in mm/s (default: $config->{retract_speed}[0]) --deretract-speed Speed for deretraction (loading of filament after a retract) in mm/s (default: $config->{retract_speed}[0]) --retract-restart-extra Additional amount of filament in mm to push after compensating retraction (default: $config->{retract_restart_extra}[0]) --retract-before-travel Only retract before travel moves of this length in mm (default: $config->{retract_before_travel}[0]) --retract-lift Lift Z by the given distance in mm when retracting (default: $config->{retract_lift}[0]) --retract-lift-above Only lift Z when above the specified height (default: $config->{retract_lift_above}[0]) --retract-lift-below Only lift Z when below the specified height (default: $config->{retract_lift_below}[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: $config->{retract_length_toolchange}[0]) --retract-restart-extra-toolchange Additional amount of filament in mm to push after switching tool (default: $config->{retract_restart_extra_toolchange}[0]) Cooling options: --cooling Enable fan and cooling control --min-fan-speed Minimum fan speed (default: $config->{min_fan_speed}[0]%) --max-fan-speed Maximum fan speed (default: $config->{max_fan_speed}[0]%) --bridge-fan-speed Fan speed to use when bridging (default: $config->{bridge_fan_speed}[0]%) --fan-below-layer-time Enable fan if layer print time is below this approximate number of seconds (default: $config->{fan_below_layer_time}[0]) --slowdown-below-layer-time Slow down if layer print time is below this approximate number of seconds (default: $config->{slowdown_below_layer_time}[0]) --min-print-speed Minimum print speed (mm/s, default: $config->{min_print_speed}[0]) --disable-fan-first-layers Disable fan for the first N layers (default: $config->{disable_fan_first_layers}[0]) --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: $config->{skirts}) --skirt-distance Distance in mm between innermost skirt and object (default: $config->{skirt_distance}) --skirt-height Height of skirts to draw (expressed in layers, 0+, default: $config->{skirt_height}) --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: $config->{min_skirt_length}) --brim-width Width of the brim that will get added to each object to help adhesion (mm, default: $config->{brim_width}) 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: $config->{duplicate_distance}) --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. --clip_multipart_objects When printing multi-material objects, this settings will make slic3r to clip the overlapping object parts one by the other (2nd part will be clipped by the 1st, 3rd part will be clipped by the 1st and 2nd etc). (default: $config->{clip_multipart_objects})"; --elefant-foot-compensation Shrink the first layer by the configured value to compensate for the 1st layer squish aka an Elefant Foot effect (mm, default: $config->{elefant_foot_compensation}) --xy-size-compensation Grow/shrink objects by the configured absolute distance (mm, default: $config->{xy_size_compensation}) 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: $config->{extruder_clearance_radius}) --extruder-clearance-height Maximum vertical extruder depth; i.e. vertical distance from extruder tip and carriage bottom (default: $config->{extruder_clearance_height}) Miscellaneous options: --notes Notes to be added as comments to the output file --resolution Minimum detail resolution (mm, set zero for full resolution, default: $config->{resolution}) 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: $config->{infill_overlap}) --bridge-flow-ratio Multiplier for extrusion when bridging (> 0, default: $config->{bridge_flow_ratio}) 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: $config->{perimeter_extruder}) --infill-extruder Extruder to use for infill (1+, default: $config->{infill_extruder}) --solid-infill-extruder Extruder to use for solid infill (1+, default: $config->{solid_infill_extruder}) --support-material-extruder Extruder to use for support material, raft and skirt (1+, 0 to use the current extruder to minimize tool changes, default: $config->{support_material_extruder}) --support-material-interface-extruder Extruder to use for support material interface (1+, 0 to use the current extruder to minimize tool changes, default: $config->{support_material_interface_extruder}) --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: $config->{standby_temperature_delta}) EOF exit ($exit_code || 0); } __END__ Slic3r-version_1.39.1/slic3r.sublime-project000066400000000000000000000107441324354444700207560ustar00rootroot00000000000000{ "build_systems": [ { "name": "List", //"file_regex": " at ([^-\\s]*) line ([0-9]*)", // "file_regex": " at (D\\:\\/src\\/Slic3r\\/.*?) line ([0-9]*)", "shell_cmd": "ls -l" }, { "name": "Run", "working_dir": "$project_path", "file_regex": " at (.*?) line ([0-9]*)", // "shell_cmd": "chdir & perl slic3r.pl --DataDir \"C:\\Users\\Public\\Documents\\Prusa3D\\Slic3r settings MK2\" --gui \"..\\Slic3r-tests\\gap fill torture 20 -rt.stl\"" "shell_cmd": "chdir & perl slic3r.pl" }, { "name": "full", "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$", "shell_cmd": "chdir & perl Build.pl" }, { "name": "xs", "working_dir": "$project_path/build", // for Visual Studio: "file_regex": "^(..[^:]*)\\(([0-9]+)\\)(.*)$", // For GCC: // "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$", "shell_cmd": "chdir & ninja -j 6 -v", "env": { // "PATH": "C:\\Program Files (x86)\\MSBuild\\12.0\\bin\\amd64;C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\BIN\\amd64;C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\IDE;C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\Tools;%PATH%;c:\\wperl64d\\site\\bin;c:\\wperl64d\\bin", // "PERL_CPANM_HOME": "c:\\wperl64d\\cpanm", // "WXDIR": "D:\\src-perl\\wxWidgets-3.0.3-beta1", // "BOOST_DIR": "D:\\src-perl\\boost_1_61_0", // "BOOST_INCLUDEDIR": "D:\\src-perl\\boost_1_61_0", // "BOOST_LIBRARYDIR": "D:\\src-perl\\boost_1_61_0\\stage\\x64\\lib", // "SLIC3R_STATIC": "1" } }, { "name": "xs & run", "working_dir": "$project_path/build", "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$", "shell_cmd": "chdir & ninja -j 6 & cd .. & perl slic3r.pl --gui \"..\\Slic3r-tests\\star3-big2.stl\"" }, { "name": "Slic3r - clean", "working_dir": "$project_path/build", "file_regex": "^(..[^:]*)(?::|\\()([0-9]+)(?::|\\))(?:([0-9]+):)?\\s*(.*)", "shell_cmd": ["chdir & ninja clean"] }, { "name": "run tests", "working_dir": "$project_path/build", // for Visual Studio: "file_regex": "^(..[^:]*)\\(([0-9]+)\\)(.*)$", "shell_cmd": "chdir & ctest --verbose" }, { "name": "Clean & Configure", "working_dir": "$project_path", // for Visual Studio: "file_regex": "^(..[^:]*)(?::|\\()([0-9]+)(?::|\\))(?:([0-9]+):)?\\s*(.*)", "shell_cmd": "chdir & rmdir /S /Q build & mkdir build & cd build & cmake -G Ninja .. -DCMAKE_COLOR_MAKEFILE=OFF -DCMAKE_RULE_PROGRESS=OFF -DCMAKE_RULE_MESSAGES=OFF -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo" }, { "name": "Configure", "working_dir": "$project_path/build", // for Visual Studio: "file_regex": "^(..[^:]*)(?::|\\()([0-9]+)(?::|\\))(?:([0-9]+):)?\\s*(.*)", "shell_cmd": "cmake -G Ninja .. -DCMAKE_COLOR_MAKEFILE=OFF -DCMAKE_RULE_PROGRESS=OFF -DCMAKE_RULE_MESSAGES=OFF -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo" } ], "folders": [ { "path": ".", // "folder_exclude_patterns": [".svn", "._d", ".metadata", ".settings"], "file_exclude_patterns": ["XS.c"] } ], "settings": { "sublimegdb_workingdir": "${folder:${project_path:run}}", // NOTE: You MUST provide --interpreter=mi for the plugin to work // "sublimegdb_commandline": "D:\\Qt\\Tools\\mingw492_32\\bin\\gdb.exe --interpreter=mi -ex 'target localhost:2345'", // "sublimegdb_commandline": "D:\\Qt\\Tools\\mingw492_32\\bin\\gdb.exe --interpreter=mi perl --args perl slic3r.pl", // "sublimegdb_commandline": "D:\\Qt\\Tools\\mingw492_32\\bin\\gdb.exe --interpreter=mi perl --args slic3r.pl ", // "sublimegdb_commandline": "D:\\Qt\\Tools\\mingw492_32\\bin\\gdb.exe --interpreter=mi -e C:\\Strawberry\\perl\\bin\\perl.exe -s C:\\Strawberry\\perl\\site\\lib\\auto\\Slic3r\\XS\\XS.xs.dll --args perl slic3r.pl -j 1 --gui D:\\src\\Slic3r-tests\\star3-big.stl", "sublimegdb_commandline": "D:\\Qt\\Tools\\mingw492_32\\bin\\gdb.exe --interpreter=mi perl.exe --args perl slic3r.pl -j 1 --gui", // D:\\src\\Slic3r-tests\\star3-big.stl", // "sublimegdb_commandline": "D:\\Qt\\Tools\\mingw492_32\\bin\\gdb.exe --interpreter=mi -x slic3r.gdb", // "arguments": "slic3r -j 1 --gui ../Slic3r-tests/star3-big.stl", // "arguments": "../slic3r.pl -j 1 --gui", // "sublimegdb_exec_cmd": "-exec-continue", // Add "pending breakpoints" for symbols that are dynamically loaded from // external shared libraries "debug_ext" : true, "run_after_init": false, "close_views": false } } Slic3r-version_1.39.1/src/000077500000000000000000000000001324354444700153125ustar00rootroot00000000000000Slic3r-version_1.39.1/src/slic3r.cpp000066400000000000000000000147141324354444700172240ustar00rootroot00000000000000#include "Config.hpp" #include "Geometry.hpp" #include "Model.hpp" #include "TriangleMesh.hpp" #include "libslic3r.h" #include #include #include #include #include #include #include #include using namespace Slic3r; void confess_at(const char *file, int line, const char *func, const char *pat, ...){} int main(int argc, char **argv) { // Convert arguments to UTF-8 (needed on Windows). // argv then points to memory owned by a. boost::nowide::args a(argc, argv); #if 0 // parse all command line options into a DynamicConfig ConfigDef config_def; config_def.merge(cli_config_def); config_def.merge(print_config_def); DynamicConfig config(&config_def); t_config_option_keys input_files; config.read_cli(argc, argv, &input_files); // apply command line options to a more handy CLIConfig CLIConfig cli_config; cli_config.apply(config, true); DynamicPrintConfig print_config; // load config files supplied via --load for (const std::string &file : cli_config.load.values) { if (!boost::filesystem::exists(file)) { boost::nowide::cout << "No such file: " << file << std::endl; exit(1); } DynamicPrintConfig c; try { c.load(file); } catch (std::exception &e) { boost::nowide::cout << "Error while reading config file: " << e.what() << std::endl; exit(1); } c.normalize(); print_config.apply(c); } // apply command line options to a more specific DynamicPrintConfig which provides normalize() // (command line options override --load files) print_config.apply(config, true); print_config.normalize(); // write config if requested if (!cli_config.save.value.empty()) print_config.save(cli_config.save.value); // read input file(s) if any std::vector models; for (const t_config_option_key &file : input_files) { if (!boost::filesystem::exists(file)) { boost::nowide::cerr << "No such file: " << file << std::endl; exit(1); } Model model; try { model = Model::read_from_file(file); } catch (std::exception &e) { boost::nowide::cerr << file << ": " << e.what() << std::endl; exit(1); } if (model.objects.empty()) { boost::nowide::cerr << "Error: file is empty: " << file << std::endl; continue; } model.add_default_instances(); // apply command line transform options for (ModelObject* o : model.objects) { if (cli_config.scale_to_fit.is_positive_volume()) o->scale_to_fit(cli_config.scale_to_fit.value); // TODO: honor option order? o->scale(cli_config.scale.value); o->rotate(Geometry::deg2rad(cli_config.rotate_x.value), X); o->rotate(Geometry::deg2rad(cli_config.rotate_y.value), Y); o->rotate(Geometry::deg2rad(cli_config.rotate.value), Z); } // TODO: handle --merge models.push_back(model); } for (Model &model : models) { if (cli_config.info) { // --info works on unrepaired model model.print_info(); } else if (cli_config.export_obj) { std::string outfile = cli_config.output.value; if (outfile.empty()) outfile = model.objects.front()->input_file + ".obj"; TriangleMesh mesh = model.mesh(); mesh.repair(); IO::OBJ::write(mesh, outfile); boost::nowide::cout << "File exported to " << outfile << std::endl; } else if (cli_config.export_pov) { std::string outfile = cli_config.output.value; if (outfile.empty()) outfile = model.objects.front()->input_file + ".pov"; TriangleMesh mesh = model.mesh(); mesh.repair(); IO::POV::write(mesh, outfile); boost::nowide::cout << "File exported to " << outfile << std::endl; } else if (cli_config.export_svg) { std::string outfile = cli_config.output.value; if (outfile.empty()) outfile = model.objects.front()->input_file + ".svg"; SLAPrint print(&model); print.config.apply(print_config, true); print.slice(); print.write_svg(outfile); boost::nowide::cout << "SVG file exported to " << outfile << std::endl; } else if (cli_config.cut_x > 0 || cli_config.cut_y > 0 || cli_config.cut > 0) { model.repair(); model.translate(0, 0, -model.bounding_box().min.z); if (!model.objects.empty()) { // FIXME: cut all objects Model out; if (cli_config.cut_x > 0) { model.objects.front()->cut(X, cli_config.cut_x, &out); } else if (cli_config.cut_y > 0) { model.objects.front()->cut(Y, cli_config.cut_y, &out); } else { model.objects.front()->cut(Z, cli_config.cut, &out); } ModelObject &upper = *out.objects[0]; ModelObject &lower = *out.objects[1]; if (upper.facets_count() > 0) { TriangleMesh m = upper.mesh(); IO::STL::write(m, upper.input_file + "_upper.stl"); } if (lower.facets_count() > 0) { TriangleMesh m = lower.mesh(); IO::STL::write(m, lower.input_file + "_lower.stl"); } } } else if (cli_config.cut_grid.value.x > 0 && cli_config.cut_grid.value.y > 0) { TriangleMesh mesh = model.mesh(); mesh.repair(); TriangleMeshPtrs meshes = mesh.cut_by_grid(cli_config.cut_grid.value); size_t i = 0; for (TriangleMesh* m : meshes) { std::ostringstream ss; ss << model.objects.front()->input_file << "_" << i++ << ".stl"; IO::STL::write(*m, ss.str()); delete m; } } else { boost::nowide::cerr << "error: command not supported" << std::endl; return 1; } } #endif return 0; } Slic3r-version_1.39.1/t/000077500000000000000000000000001324354444700147665ustar00rootroot00000000000000Slic3r-version_1.39.1/t/angles.t000066400000000000000000000060271324354444700164310ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 34; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; use Slic3r::Geometry qw(rad2deg_dir angle3points PI); #========================================================== { is line_atan([ [0, 0], [10, 0] ]), (0), 'E atan2'; is line_atan([ [10, 0], [0, 0] ]), (PI), 'W atan2'; is line_atan([ [0, 0], [0, 10] ]), (PI/2), 'N atan2'; is line_atan([ [0, 10], [0, 0] ]), -(PI/2), 'S atan2'; is line_atan([ [10, 10], [0, 0] ]), -(PI*3/4), 'SW atan2'; is line_atan([ [0, 0], [10, 10] ]), (PI*1/4), 'NE atan2'; is line_atan([ [0, 10], [10, 0] ]), -(PI*1/4), 'SE atan2'; is line_atan([ [10, 0], [0, 10] ]), (PI*3/4), 'NW atan2'; } #========================================================== { is line_orientation([ [0, 0], [10, 0] ]), (0), 'E orientation'; is line_orientation([ [0, 0], [0, 10] ]), (PI/2), 'N orientation'; is line_orientation([ [10, 0], [0, 0] ]), (PI), 'W orientation'; is line_orientation([ [0, 10], [0, 0] ]), (PI*3/2), 'S orientation'; is line_orientation([ [0, 0], [10, 10] ]), (PI*1/4), 'NE orientation'; is line_orientation([ [10, 0], [0, 10] ]), (PI*3/4), 'NW orientation'; is line_orientation([ [10, 10], [0, 0] ]), (PI*5/4), 'SW orientation'; is line_orientation([ [0, 10], [10, 0] ]), (PI*7/4), 'SE orientation'; } #========================================================== { is line_direction([ [0, 0], [10, 0] ]), (0), 'E direction'; is line_direction([ [10, 0], [0, 0] ]), (0), 'W direction'; is line_direction([ [0, 0], [0, 10] ]), (PI/2), 'N direction'; is line_direction([ [0, 10], [0, 0] ]), (PI/2), 'S direction'; is line_direction([ [10, 10], [0, 0] ]), (PI*1/4), 'SW direction'; is line_direction([ [0, 0], [10, 10] ]), (PI*1/4), 'NE direction'; is line_direction([ [0, 10], [10, 0] ]), (PI*3/4), 'SE direction'; is line_direction([ [10, 0], [0, 10] ]), (PI*3/4), 'NW direction'; } #========================================================== { is rad2deg_dir(0), 90, 'E (degrees)'; is rad2deg_dir(PI), 270, 'W (degrees)'; is rad2deg_dir(PI/2), 0, 'N (degrees)'; is rad2deg_dir(-(PI/2)), 180, 'S (degrees)'; is rad2deg_dir(PI*1/4), 45, 'NE (degrees)'; is rad2deg_dir(PI*3/4), 135, 'NW (degrees)'; is rad2deg_dir(PI/6), 60, '30°'; is rad2deg_dir(PI/6*2), 30, '60°'; } #========================================================== { is angle3points([0,0], [10,0], [0,10]), PI/2, 'CW angle3points'; is angle3points([0,0], [0,10], [10,0]), PI/2*3, 'CCW angle3points'; } #========================================================== sub line_atan { my ($l) = @_; return Slic3r::Line->new(@$l)->atan2_; } sub line_orientation { my ($l) = @_; return Slic3r::Line->new(@$l)->orientation; } sub line_direction { my ($l) = @_; return Slic3r::Line->new(@$l)->direction; }Slic3r-version_1.39.1/t/avoid_crossing_perimeters.t000066400000000000000000000010451324354444700224230ustar00rootroot00000000000000use Test::More tests => 1; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first sum); use Slic3r; use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; $config->set('avoid_crossing_perimeters', 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); ok my $gcode = Slic3r::Test::gcode($print), "no crash with avoid_crossing_perimeters and multiple objects"; } __END__ Slic3r-version_1.39.1/t/bridges.t000066400000000000000000000111701324354444700165720ustar00rootroot00000000000000use Test::More tests => 16; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first sum); use Slic3r; use Slic3r::Geometry qw(scale epsilon deg2rad rad2deg); use Slic3r::Test; { my $test = sub { my ($bridge_size, $rotate, $expected_angle, $tolerance) = @_; my ($x, $y) = @$bridge_size; my $lower = Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([-2,-2], [$x+2,-2], [$x+2,$y+2], [-2,$y+2]), Slic3r::Polygon->new_scale([0,0], [0,$y], [$x,$y], [$x,0]), ); $lower->translate(scale 20, scale 20); # avoid negative coordinates for easier SVG preview $lower->rotate(deg2rad($rotate), [$x/2,$y/2]); my $bridge = $lower->[1]->clone; $bridge->reverse; $bridge = Slic3r::ExPolygon->new($bridge); ok check_angle([$lower], $bridge, $expected_angle, $tolerance), 'correct bridge angle for O-shaped overhang'; }; $test->([20,10], 0, 90); $test->([10,20], 0, 0); $test->([20,10], 45, 135, 20); $test->([20,10], 135, 45, 20); } { my $bridge = Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([0,0], [20,0], [20,10], [0,10]), ); my $lower = [ Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([-2,0], [0,0], [0,10], [-2,10]), ), ]; $_->translate(scale 20, scale 20) for $bridge, @$lower; # avoid negative coordinates for easier SVG preview $lower->[1] = $lower->[0]->clone; $lower->[1]->translate(scale 22, 0); ok check_angle($lower, $bridge, 0), 'correct bridge angle for two-sided bridge'; } { my $bridge = Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([0,0], [20,0], [10,10], [0,10]), ); my $lower = [ Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([0,0], [0,10], [10,10], [10,12], [-2,12], [-2,-2], [22,-2], [22,0]), ), ]; $_->translate(scale 20, scale 20) for $bridge, @$lower; # avoid negative coordinates for easier SVG preview ok check_angle($lower, $bridge, 135), 'correct bridge angle for C-shaped overhang'; } { my $bridge = Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([10,10],[20,10],[20,20], [10,20]), ); my $lower = [ Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([10,10],[10,20],[20,20],[30,30],[0,30],[0,0]), ), ]; $_->translate(scale 20, scale 20) for $bridge, @$lower; # avoid negative coordinates for easier SVG preview ok check_angle($lower, $bridge, 45, undef, $bridge->area/2), 'correct bridge angle for square overhang with L-shaped anchors'; } sub check_angle { my ($lower, $bridge, $expected, $tolerance, $expected_coverage) = @_; if (ref($lower) eq 'ARRAY') { $lower = Slic3r::ExPolygon::Collection->new(@$lower); } $expected_coverage //= -1; $expected_coverage = $bridge->area if $expected_coverage == -1; my $bd = Slic3r::BridgeDetector->new($bridge, $lower, scale 0.5); $tolerance //= rad2deg($bd->resolution) + epsilon; $bd->detect_angle; my $result = $bd->angle; my $coverage = $bd->coverage; is sum(map $_->area, @$coverage), $expected_coverage, 'correct coverage area'; # our epsilon is equal to the steps used by the bridge detection algorithm ###use XXX; YYY [ rad2deg($result), $expected ]; # returned value must be non-negative, check for that too my $delta=rad2deg($result) - $expected; $delta-=180 if $delta>=180 - epsilon; return defined $result && $result>=0 && abs($delta) < $tolerance; } { my $config = Slic3r::Config::new_from_defaults; $config->set('top_solid_layers', 0); # to prevent bridging on sparse infill $config->set('bridge_speed', 99); my $print = Slic3r::Test::init_print('bridge', config => $config); my %extrusions = (); # angle => length Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && ($args->{F} // $self->F)/60 == $config->bridge_speed) { my $line = Slic3r::Line->new_scale( [ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ], ); my $angle = $line->direction; $extrusions{$angle} //= 0; $extrusions{$angle} += $line->length; } }); ok !!%extrusions, "bridge is generated"; my ($main_angle) = sort { $extrusions{$b} <=> $extrusions{$a} } keys %extrusions; is $main_angle, 0, "bridge has the expected direction"; } __END__ Slic3r-version_1.39.1/t/clean_polylines.t000066400000000000000000000107501324354444700203360ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 6; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; { my $polyline = Slic3r::Polyline->new( [0,0],[1,0],[2,0],[2,1],[2,2],[1,2],[0,2],[0,1],[0,0], ); $polyline->simplify(1); is_deeply $polyline->pp, [ [0, 0], [2, 0], [2, 2], [0, 2], [0, 0] ], 'Douglas-Peucker'; } { my $polyline = Slic3r::Polyline->new( [0,0], [50,50], [100,0], [125,-25], [150,50], ); $polyline->simplify(25); is_deeply $polyline->pp, [ [0, 0], [50, 50], [125, -25], [150, 50] ], 'Douglas-Peucker'; } { my $gear = Slic3r::Polygon->new_scale( [144.9694,317.1543], [145.4181,301.5633], [146.3466,296.921], [131.8436,294.1643], [131.7467,294.1464], [121.7238,291.5082], [117.1631,290.2776], [107.9198,308.2068], [100.1735,304.5101], [104.9896,290.3672], [106.6511,286.2133], [93.453,279.2327], [81.0065,271.4171], [67.7886,286.5055], [60.7927,280.1127], [69.3928,268.2566], [72.7271,264.9224], [61.8152,253.9959], [52.2273,242.8494], [47.5799,245.7224], [34.6577,252.6559], [30.3369,245.2236], [42.1712,236.3251], [46.1122,233.9605], [43.2099,228.4876], [35.0862,211.5672], [33.1441,207.0856], [13.3923,212.1895], [10.6572,203.3273], [6.0707,204.8561], [7.2775,204.4259], [29.6713,196.3631], [25.9815,172.1277], [25.4589,167.2745], [19.8337,167.0129], [5.0625,166.3346], [5.0625,156.9425], [5.3701,156.9282], [21.8636,156.1628], [25.3713,156.4613], [25.4243,155.9976], [29.3432,155.8157], [30.3838,149.3549], [26.3596,147.8137], [27.1085,141.2604], [29.8466,126.8337], [24.5841,124.9201], [10.6664,119.8989], [13.4454,110.9264], [33.1886,116.0691], [38.817,103.1819], [45.8311,89.8133], [30.4286,76.81], [35.7686,70.0812], [48.0879,77.6873], [51.564,81.1635], [61.9006,69.1791], [72.3019,58.7916], [60.5509,42.5416], [68.3369,37.1532], [77.9524,48.1338], [80.405,52.2215], [92.5632,44.5992], [93.0123,44.3223], [106.3561,37.2056], [100.8631,17.4679], [108.759,14.3778], [107.3148,11.1283], [117.0002,32.8627], [140.9109,27.3974], [145.7004,26.4994], [145.1346,6.1011], [154.502,5.4063], [156.9398,25.6501], [171.0557,26.2017], [181.3139,27.323], [186.2377,27.8532], [191.6031,8.5474], [200.6724,11.2756], [197.2362,30.2334], [220.0789,39.1906], [224.3261,41.031], [236.3506,24.4291], [243.6897,28.6723], [234.2956,46.7747], [245.6562,55.1643], [257.2523,65.0901], [261.4374,61.5679], [273.1709,52.8031], [278.555,59.5164], [268.4334,69.8001], [264.1615,72.3633], [268.2763,77.9442], [278.8488,93.5305], [281.4596,97.6332], [286.4487,95.5191], [300.2821,90.5903], [303.4456,98.5849], [286.4523,107.7253], [293.7063,131.1779], [294.9748,135.8787], [314.918,133.8172], [315.6941,143.2589], [300.9234,146.1746], [296.6419,147.0309], [297.1839,161.7052], [296.6136,176.3942], [302.1147,177.4857], [316.603,180.3608], [317.1658,176.7341], [315.215,189.6589], [315.1749,189.6548], [294.9411,187.5222], [291.13,201.7233], [286.2615,215.5916], [291.1944,218.2545], [303.9158,225.1271], [299.2384,233.3694], [285.7165,227.6001], [281.7091,225.1956], [273.8981,237.6457], [268.3486,245.2248], [267.4538,246.4414], [264.8496,250.0221], [268.6392,253.896], [278.5017,265.2131], [272.721,271.4403], [257.2776,258.3579], [234.4345,276.5687], [242.6222,294.8315], [234.9061,298.5798], [227.0321,286.2841], [225.2505,281.8301], [211.5387,287.8187], [202.3025,291.0935], [197.307,292.831], [199.808,313.1906], [191.5298,315.0787], [187.3082,299.8172], [186.4201,295.3766], [180.595,296.0487], [161.7854,297.4248], [156.8058,297.6214], [154.3395,317.8592], ); my $num_points = scalar @$gear; my $simplified = $gear->simplify(1000); ok @$simplified == 1, 'gear simplified to a single polygon'; ###note sprintf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified->[0]}); ok @{$simplified->[0]} < $num_points, 'gear was further simplified using Douglas-Peucker'; } { my $hole_in_square = Slic3r::Polygon->new( # cw [140, 140], [140, 160], [160, 160], [160, 140], ); my $simplified = $hole_in_square->simplify(2); is scalar(@$simplified), 1, 'hole simplification returns one polygon'; ok $simplified->[0]->is_counter_clockwise, 'hole simplification turns cw polygon into ccw polygon'; } Slic3r-version_1.39.1/t/clipper.t000066400000000000000000000044611324354444700166160ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 6; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(sum); use Slic3r; use Slic3r::Geometry::Clipper qw(intersection_ex union_ex diff_ex diff_pl); { my $square = [ # ccw [10, 10], [20, 10], [20, 20], [10, 20], ]; my $hole_in_square = [ # cw [14, 14], [14, 16], [16, 16], [16, 14], ]; my $square2 = [ # ccw [5, 12], [25, 12], [25, 18], [5, 18], ]; my $intersection = intersection_ex([ $square, $hole_in_square ], [ $square2 ]); is sum(map $_->area, @$intersection), Slic3r::ExPolygon->new( [ [20, 18], [10, 18], [10, 12], [20, 12], ], [ [14, 16], [16, 16], [16, 14], [14, 14], ], )->area, 'hole is preserved after intersection'; } #========================================================== { my $contour1 = [ [0,0], [40,0], [40,40], [0,40] ]; # ccw my $contour2 = [ [10,10], [30,10], [30,30], [10,30] ]; # ccw my $hole = [ [15,15], [15,25], [25,25], [25,15] ]; # cw my $union = union_ex([ $contour1, $contour2, $hole ]); is_deeply [ map $_->pp, @$union ], [[ [ [40,40], [0,40], [0,0], [40,0] ] ]], 'union of two ccw and one cw is a contour with no holes'; my $diff = diff_ex([ $contour1, $contour2 ], [ $hole ]); is sum(map $_->area, @$diff), Slic3r::ExPolygon->new([ [40,40], [0,40], [0,0], [40,0] ], [ [15,25], [25,25], [25,15], [15,15] ])->area, 'difference of a cw from two ccw is a contour with one hole'; } #========================================================== { my $square = Slic3r::Polygon->new_scale( # ccw [10, 10], [20, 10], [20, 20], [10, 20], ); my $square_pl = $square->split_at_first_point; my $res = diff_pl([$square_pl], []); is scalar(@$res), 1, 'no-op diff_pl returns the right number of polylines'; isa_ok $res->[0], 'Slic3r::Polyline', 'no-op diff_pl result'; is scalar(@{$res->[0]}), scalar(@$square_pl), 'no-op diff_pl returns the unmodified input polyline'; } __END__ Slic3r-version_1.39.1/t/collinear.t000066400000000000000000000042221324354444700171230ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 11; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; use Slic3r::Geometry qw(collinear); #========================================================== { my @lines = ( Slic3r::Line->new([0,4], [4,2]), Slic3r::Line->new([2,3], [8,0]), Slic3r::Line->new([6,1], [8,0]), ); is collinear($lines[0], $lines[1]), 1, 'collinear'; is collinear($lines[1], $lines[2]), 1, 'collinear'; is collinear($lines[0], $lines[2]), 1, 'collinear'; } #========================================================== { # horizontal my @lines = ( Slic3r::Line->new([0,1], [5,1]), Slic3r::Line->new([2,1], [8,1]), ); is collinear($lines[0], $lines[1]), 1, 'collinear'; } #========================================================== { # vertical my @lines = ( Slic3r::Line->new([1,0], [1,5]), Slic3r::Line->new([1,2], [1,8]), ); is collinear($lines[0], $lines[1]), 1, 'collinear'; } #========================================================== { # non overlapping my @lines = ( Slic3r::Line->new([0,1], [5,1]), Slic3r::Line->new([7,1], [10,1]), ); is collinear($lines[0], $lines[1], 1), 0, 'non overlapping'; is collinear($lines[0], $lines[1], 0), 1, 'overlapping'; } #========================================================== { # with one common point my @lines = ( Slic3r::Line->new([0,4], [4,2]), Slic3r::Line->new([4,2], [8,0]), ); is collinear($lines[0], $lines[1], 1), 1, 'one common point'; is collinear($lines[0], $lines[1], 0), 1, 'one common point'; } #========================================================== { # not collinear my @lines = ( Slic3r::Line->new([290000000,690525600], [285163380,684761540]), Slic3r::Line->new([285163380,684761540], [193267599,575244400]), ); is collinear($lines[0], $lines[1], 0), 0, 'not collinear'; is collinear($lines[0], $lines[1], 1), 0, 'not collinear'; } #========================================================== Slic3r-version_1.39.1/t/combineinfill.t000066400000000000000000000143211324354444700177660ustar00rootroot00000000000000use Test::More; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Surface ':types'; use Slic3r::Test; plan tests => 8; { my $test = sub { my ($config) = @_; my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok my $gcode = Slic3r::Test::gcode($print), "infill_every_layers does not crash"; my $tool = undef; my %layers = (); # layer_z => 1 my %layer_infill = (); # layer_z => has_infill Slic3r::GCode::Reader->new->parse($gcode, sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0 && $tool != $config->support_material_extruder-1) { $layer_infill{$self->Z} //= 0; if ($tool == $config->infill_extruder-1) { $layer_infill{$self->Z} = 1; } } $layers{$args->{Z}} = 1 if $cmd eq 'G1' && $info->{dist_Z} > 0; }); my $layers_with_perimeters = scalar(keys %layer_infill); my $layers_with_infill = grep $_ > 0, values %layer_infill; is scalar(keys %layers), $layers_with_perimeters+$config->raft_layers, 'expected number of layers'; # first infill layer is never combined, so we don't consider it $layers_with_infill--; $layers_with_perimeters--; # we expect that infill is generated for half the number of combined layers # plus for each single layer that was not combined (remainder) is $layers_with_infill, int($layers_with_perimeters/$config->infill_every_layers) + ($layers_with_perimeters % $config->infill_every_layers), 'infill is only present in correct number of layers'; }; my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); $config->set('nozzle_diameter', [0.5]); $config->set('infill_every_layers', 2); $config->set('perimeter_extruder', 1); $config->set('infill_extruder', 2); $config->set('support_material_extruder', 3); $config->set('support_material_interface_extruder', 3); $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); $test->($config); $config->set('skirts', 0); # prevent usage of perimeter_extruder in raft layers $config->set('raft_layers', 5); $test->($config); } { my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); $config->set('nozzle_diameter', [0.5]); $config->set('infill_every_layers', 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); $print->process; ok defined(first { @{$_->get_region(0)->fill_surfaces->filter_by_type(S_TYPE_INTERNALVOID)} > 0 } @{$print->print->get_object(0)->layers}), 'infill combination produces internal void surfaces'; # we disable combination after infill has been generated $config->set('infill_every_layers', 1); $print->apply_config($config); $print->process; ok !(defined first { @{$_->get_region(0)->fill_surfaces} == 0 } @{$print->print->get_object(0)->layers}), 'infill combination is idempotent'; } # the following needs to be adapted to the new API if (0) { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('solid_layers', 0); $config->set('bottom_solid_layers', 0); $config->set('top_solid_layers', 0); $config->set('infill_every_layers', 6); $config->set('layer_height', 0.06); $config->set('perimeters', 1); my $test = sub { my ($shift) = @_; my $self = Slic3r::Test::init_print('20mm_cube', config => $config); $shift /= &Slic3r::SCALING_FACTOR; my $scale = 4; # make room for fat infill lines with low layer height # Put a slope on the box's sides by shifting x and y coords by $tilt * (z / boxheight). # The test here is to put such a slight slope on the walls that it should # not trigger any extra fill on fill layers that should be empty when # combine infill is enabled. $_->[0] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices}; $_->[1] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices}; $_ = [$_->[0]*$scale, $_->[1]*$scale, $_->[2]] for @{$self->objects->[0]->meshes->[0]->vertices}; # copy of Print::export_gcode() up to the point # after fill surfaces are combined $_->slice for @{$self->objects}; $_->make_perimeters for @{$self->objects}; $_->detect_surfaces_type for @{$self->objects}; $_->prepare_fill_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; $_->process_external_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects}; $_->discover_horizontal_shells for @{$self->objects}; $_->combine_infill for @{$self->objects}; # Only layers with id % 6 == 0 should have fill. my $spurious_infill = 0; foreach my $layer (map @{$_->layers}, @{$self->objects}) { ++$spurious_infill if ($layer->id % 6 && grep @{$_->fill_surfaces} > 0, @{$layer->regions}); } $spurious_infill -= scalar(@{$self->objects->[0]->layers} - 1) % 6; fail "spurious fill surfaces found on layers that should have none (walls " . sprintf("%.4f", Slic3r::Geometry::rad2deg(atan2($shift, 20/&Slic3r::SCALING_FACTOR))) . " degrees off vertical)" unless $spurious_infill == 0; 1; }; # Test with mm skew offsets for the top of the 20mm-high box for my $shift (0, 0.0001, 1) { ok $test->($shift), "no spurious fill surfaces with box walls " . sprintf("%.4f",Slic3r::Geometry::rad2deg(atan2($shift, 20))) . " degrees off of vertical"; } } __END__ Slic3r-version_1.39.1/t/config.t000066400000000000000000000006001324354444700164140ustar00rootroot00000000000000use Test::More tests => 1; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; $config->set('perimeter_extrusion_width', '250%'); ok $config->validate, 'percent extrusion width is validated'; } __END__ Slic3r-version_1.39.1/t/cooling.t000066400000000000000000000200461324354444700166070ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 15; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(none all); use Slic3r; use Slic3r::Test; my $gcodegen; sub buffer { my $config = shift; if (defined($config)) { $config = $config->clone(); } else { $config = Slic3r::Config->new; } my $config_override = shift; foreach my $key (keys %{$config_override}) { $config->set($key, ${$config_override}{$key}); } my $print_config = Slic3r::Config::Print->new; $print_config->apply_dynamic($config); $gcodegen = Slic3r::GCode->new; $gcodegen->apply_print_config($print_config); $gcodegen->set_layer_count(10); $gcodegen->set_extruders([ 0 ]); return Slic3r::GCode::CoolingBuffer->new($gcodegen); } my $gcode1 = "G1 X100 E1 F3000\n"; my $print_time1 = 100 / (3000 / 60); # 2 sec my $gcode2 = $gcode1 . "G1 X0 E1 F3000\n"; my $print_time2 = 2 * $print_time1; # 4 sec my $config = Slic3r::Config::new_from_defaults; # Default cooling settings. $config->set('bridge_fan_speed', [ 100 ]); $config->set('cooling', [ 1 ]); $config->set('fan_always_on', [ 0 ]); $config->set('fan_below_layer_time', [ 60 ]); $config->set('max_fan_speed', [ 100 ]); $config->set('min_print_speed', [ 10 ]); $config->set('slowdown_below_layer_time', [ 5 ]); # Default print speeds. $config->set('bridge_speed', 60); $config->set('external_perimeter_speed', '50%'); $config->set('first_layer_speed', 30); $config->set('gap_fill_speed', 20); $config->set('infill_speed', 80); $config->set('perimeter_speed', 60); $config->set('small_perimeter_speed', 15); $config->set('solid_infill_speed', 20); $config->set('top_solid_infill_speed', 15); $config->set('max_print_speed', 80); # Override for tests. $config->set('disable_fan_first_layers', [ 0 ]); { my $gcode_src = "G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1"; # Print time of $gcode. my $print_time = 100 / (3000 / 60); my $buffer = buffer($config, { 'slowdown_below_layer_time' => [ $print_time * 0.999 ] }); my $gcode = $buffer->process_layer($gcode_src, 0); like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold'; } { my $gcode_src = "G1 X50 F2500\n" . "G1 F3000;_EXTRUDE_SET_SPEED\n" . "G1 X100 E1\n" . "G1 E4 F400", # Print time of $gcode. my $print_time = 50 / (2500 / 60) + 100 / (3000 / 60) + 4 / (400 / 60); my $buffer = buffer($config, { 'slowdown_below_layer_time' => [ $print_time * 1.001 ] }); my $gcode = $buffer->process_layer($gcode_src, 0); unlike $gcode, qr/F3000/, 'speed is altered when elapsed time is lower than slowdown threshold'; like $gcode, qr/F2500/, 'speed is not altered for travel moves'; like $gcode, qr/F400/, 'speed is not altered for extruder-only moves'; } { my $buffer = buffer($config, { 'fan_below_layer_time' => [ $print_time1 * 0.88 ], 'slowdown_below_layer_time' => [ $print_time1 * 0.99 ] }); my $gcode = $buffer->process_layer($gcode1, 0); unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold'; } { my $gcode .= buffer($config, { 'slowdown_below_layer_time', [ $print_time2 * 0.99 ] })->process_layer($gcode2, 0); like $gcode, qr/F3000/, 'slowdown is computed on all objects printing at the same Z'; } { # use an elapsed time which is < the threshold but greater than it when summed twice my $buffer = buffer($config, { 'fan_below_layer_time' => [ $print_time2 * 0.65], 'slowdown_below_layer_time' => [ $print_time2 * 0.7 ] }); my $gcode = $buffer->process_layer($gcode2, 0) . $buffer->process_layer($gcode2, 1); unlike $gcode, qr/M106/, 'fan is not activated on all objects printing at different Z'; } { # use an elapsed time which is < the threshold even when summed twice my $buffer = buffer($config, { 'fan_below_layer_time' => [ $print_time2 + 1 ], 'slowdown_below_layer_time' => [ $print_time2 + 2 ] }); my $gcode = $buffer->process_layer($gcode2, 0) . $buffer->process_layer($gcode2, 1); like $gcode, qr/M106/, 'fan is activated on all objects printing at different Z'; } { my $buffer = buffer($config, { 'cooling' => [ 1 , 0 ], 'fan_below_layer_time' => [ $print_time2 + 1, $print_time2 + 1 ], 'slowdown_below_layer_time' => [ $print_time2 + 2, $print_time2 + 2 ] }); $buffer->gcodegen->set_extruders([ 0, 1 ]); my $gcode = $buffer->process_layer($gcode1 . "T1\nG1 X0 E1 F3000\n", 0); like $gcode, qr/^M106/, 'fan is activated for the 1st tool'; like $gcode, qr/.*M107/, 'fan is disabled for the 2nd tool'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('cooling', [ 1 ]); $config->set('bridge_fan_speed', [ 100 ]); $config->set('fan_below_layer_time', [ 0 ]); $config->set('slowdown_below_layer_time', [ 0 ]); $config->set('bridge_speed', 99); $config->set('top_solid_layers', 1); # internal bridges use solid_infil speed $config->set('bottom_solid_layers', 1); # internal bridges use solid_infil speed my $print = Slic3r::Test::init_print('overhang', config => $config); my $fan = 0; my $fan_with_incorrect_speeds = my $fan_with_incorrect_print_speeds = 0; my $bridge_with_no_fan = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'M106') { $fan = $args->{S}; $fan_with_incorrect_speeds++ if $fan != 255; } elsif ($cmd eq 'M107') { $fan = 0; } elsif ($info->{extruding} && $info->{dist_XY} > 0) { $fan_with_incorrect_print_speeds++ if ($fan > 0) && ($args->{F} // $self->F) != 60*$config->bridge_speed; $bridge_with_no_fan++ if !$fan && ($args->{F} // $self->F) == 60*$config->bridge_speed; } }); ok !$fan_with_incorrect_speeds, 'bridge fan speed is applied correctly'; ok !$fan_with_incorrect_print_speeds, 'bridge fan is only turned on for bridges'; ok !$bridge_with_no_fan, 'bridge fan is turned on for all bridges'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('cooling', [ 1 ]); $config->set('fan_below_layer_time', [ 0 ]); $config->set('slowdown_below_layer_time', [ 10 ]); $config->set('min_print_speed', [ 0 ]); $config->set('start_gcode', ''); $config->set('first_layer_speed', '100%'); $config->set('external_perimeter_speed', 99); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my @layer_times = (0); # in seconds my %layer_external = (); # z => 1 Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1') { if ($info->{dist_Z}) { push @layer_times, 0; $layer_external{ $args->{Z} } = 0; } $layer_times[-1] += abs($info->{dist_XY} || $info->{dist_E} || $info->{dist_Z} || 0) / ($args->{F} // $self->F) * 60; if ($args->{F} && $args->{F} == $config->external_perimeter_speed*60) { $layer_external{ $self->Z }++; } } }); @layer_times = grep $_, @layer_times; my $all_below = none { $_ < $config->slowdown_below_layer_time->[0] } @layer_times; ok $all_below, 'slowdown_below_layer_time is honored'; # check that all layers have at least one unaltered external perimeter speed my $external = all { $_ > 0 } values %layer_external; ok $external, 'slowdown_below_layer_time does not alter external perimeters'; } __END__ Slic3r-version_1.39.1/t/custom_gcode.t000066400000000000000000000324411324354444700176320ustar00rootroot00000000000000use Test::More tests => 77; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; my $test = sub { my ($conf) = @_; $conf ||= $config; my $print = Slic3r::Test::init_print('2x20x10', config => $conf); my $last_move_was_z_change = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($last_move_was_z_change && $cmd ne $config->layer_gcode) { fail 'custom layer G-code was not applied after Z change'; } if (!$last_move_was_z_change && $cmd eq $config->layer_gcode) { fail 'custom layer G-code was not applied after Z change'; } $last_move_was_z_change = (defined $info->{dist_Z} && $info->{dist_Z} > 0); }); 1; }; $config->set('start_gcode', '_MY_CUSTOM_START_GCODE_'); # to avoid dealing with the nozzle lift in start G-code $config->set('layer_gcode', '_MY_CUSTOM_LAYER_GCODE_'); ok $test->(), "custom layer G-code is applied after Z move and before other moves"; } #========================================================== { my $parser = Slic3r::GCode::PlaceholderParser->new; my $config = Slic3r::Config::new_from_defaults; $config->set('printer_notes', ' PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 '); $config->set('nozzle_diameter', [0.6, 0.6, 0.6, 0.6]); $parser->apply_config($config); $parser->set('foo' => 0); $parser->set('bar' => 2); $parser->set('num_extruders' => 4); is $parser->process('[temperature_[foo]]'), $config->temperature->[0], "nested config options (legacy syntax)"; is $parser->process('{temperature[foo]}'), $config->temperature->[0], "array reference"; is $parser->process("test [ temperature_ [foo] ] \n hu"), "test " . $config->temperature->[0] . " \n hu", "whitespaces and newlines are maintained"; is $parser->process('{2*3}'), '6', 'math: 2*3'; is $parser->process('{2*3/6}'), '1', 'math: 2*3/6'; is $parser->process('{2*3/12}'), '0', 'math: 2*3/12'; ok abs($parser->process('{2.*3/12}') - 0.5) < 1e-7, 'math: 2.*3/12'; is $parser->process('{2*(3-12)}'), '-18', 'math: 2*(3-12)'; is $parser->process('{2*foo*(3-12)}'), '0', 'math: 2*foo*(3-12)'; is $parser->process('{2*bar*(3-12)}'), '-36', 'math: 2*bar*(3-12)'; ok abs($parser->process('{2.5*bar*(3-12)}') - -45) < 1e-7, 'math: 2.5*bar*(3-12)'; # Test the boolean expression parser. is $parser->evaluate_boolean_expression('12 == 12'), 1, 'boolean expression parser: 12 == 12'; is $parser->evaluate_boolean_expression('12 != 12'), 0, 'boolean expression parser: 12 != 12'; is $parser->evaluate_boolean_expression('"has some PATTERN embedded" =~ /.*PATTERN.*/'), 1, 'boolean expression parser: regex matches'; is $parser->evaluate_boolean_expression('"has some PATTERN embedded" =~ /.*PTRN.*/'), 0, 'boolean expression parser: regex does not match'; is $parser->evaluate_boolean_expression('foo + 2 == bar'), 1, 'boolean expression parser: accessing variables, equal'; is $parser->evaluate_boolean_expression('foo + 3 == bar'), 0, 'boolean expression parser: accessing variables, not equal'; is $parser->evaluate_boolean_expression('(12 == 12) and (13 != 14)'), 1, 'boolean expression parser: (12 == 12) and (13 != 14)'; is $parser->evaluate_boolean_expression('(12 == 12) && (13 != 14)'), 1, 'boolean expression parser: (12 == 12) && (13 != 14)'; is $parser->evaluate_boolean_expression('(12 == 12) or (13 == 14)'), 1, 'boolean expression parser: (12 == 12) or (13 == 14)'; is $parser->evaluate_boolean_expression('(12 == 12) || (13 == 14)'), 1, 'boolean expression parser: (12 == 12) || (13 == 14)'; is $parser->evaluate_boolean_expression('(12 == 12) and not (13 == 14)'), 1, 'boolean expression parser: (12 == 12) and not (13 == 14)'; is $parser->evaluate_boolean_expression('(12 == 12) ? (1 - 1 == 0) : (2 * 2 == 3)'), 1, 'boolean expression parser: ternary true'; is $parser->evaluate_boolean_expression('(12 == 21/2) ? (1 - 1 == 0) : (2 * 2 == 3)'), 0, 'boolean expression parser: ternary false'; is $parser->evaluate_boolean_expression('(12 == 13) ? (1 - 1 == 3) : (2 * 2 == 4)'), 1, 'boolean expression parser: ternary false'; is $parser->evaluate_boolean_expression('(12 == 2 * 6) ? (1 - 1 == 3) : (2 * 2 == 4)'), 0, 'boolean expression parser: ternary true'; is $parser->evaluate_boolean_expression('12 < 3'), 0, 'boolean expression parser: lower than - false'; is $parser->evaluate_boolean_expression('12 < 22'), 1, 'boolean expression parser: lower than - true'; is $parser->evaluate_boolean_expression('12 > 3'), 1, 'boolean expression parser: greater than - true'; is $parser->evaluate_boolean_expression('12 > 22'), 0, 'boolean expression parser: greater than - false'; is $parser->evaluate_boolean_expression('12 <= 3'), 0, 'boolean expression parser: lower than or equal- false'; is $parser->evaluate_boolean_expression('12 <= 22'), 1, 'boolean expression parser: lower than or equal - true'; is $parser->evaluate_boolean_expression('12 >= 3'), 1, 'boolean expression parser: greater than or equal - true'; is $parser->evaluate_boolean_expression('12 >= 22'), 0, 'boolean expression parser: greater than or equal - false'; is $parser->evaluate_boolean_expression('12 <= 12'), 1, 'boolean expression parser: lower than or equal (same values) - true'; is $parser->evaluate_boolean_expression('12 >= 12'), 1, 'boolean expression parser: greater than or equal (same values) - true'; is $parser->evaluate_boolean_expression('printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1'), 1, 'complex expression'; is $parser->evaluate_boolean_expression('printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.6 and num_extruders>1)'), 1, 'complex expression2'; is $parser->evaluate_boolean_expression('printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.3 and num_extruders>1)'), 0, 'complex expression3'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('output_filename_format', 'ts_[travel_speed]_lh_[layer_height].gcode'); $config->set('start_gcode', "TRAVEL:[travel_speed] HEIGHT:[layer_height]\n"); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $output_file = $print->print->output_filepath; my ($t, $h) = map $config->$_, qw(travel_speed layer_height); ok $output_file =~ /ts_${t}_/, 'print config options are replaced in output filename'; ok $output_file =~ /lh_$h\./, 'region config options are replaced in output filename'; my $gcode = Slic3r::Test::gcode($print); ok $gcode =~ /TRAVEL:$t/, 'print config options are replaced in custom G-code'; ok $gcode =~ /HEIGHT:$h/, 'region config options are replaced in custom G-code'; } { my $config = Slic3r::Config->new; $config->set('extruder', 2); $config->set('first_layer_temperature', [200,205]); { my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $gcode = Slic3r::Test::gcode($print); ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for non-zero yet single extruder'; ok $gcode !~ /M104 S\d+ T0/, 'unused extruder correctly ignored'; } $config->set('infill_extruder', 1); { my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $gcode = Slic3r::Test::gcode($print); ok $gcode =~ /M104 S200 T0/, 'temperature set correctly for first extruder'; ok $gcode =~ /M104 S205 T1/, 'temperature set correctly for second extruder'; } my @start_gcode = (qq! ;__temp0:[first_layer_temperature_0]__ ;__temp1:[first_layer_temperature_1]__ ;__temp2:[first_layer_temperature_2]__ !, qq! ;__temp0:{first_layer_temperature[0]}__ ;__temp1:{first_layer_temperature[1]}__ ;__temp2:{first_layer_temperature[2]}__ !); my @syntax_description = (' (legacy syntax)', ' (new syntax)'); for my $i (0, 1) { $config->set('start_gcode', $start_gcode[$i]); { my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $gcode = Slic3r::Test::gcode($print); # we use the [infill_extruder] placeholder to make sure this test doesn't # catch a false positive caused by the unparsed start G-code option itself # being embedded in the G-code ok $gcode =~ /temp0:200/, 'temperature placeholder for first extruder correctly populated' . $syntax_description[$i]; ok $gcode =~ /temp1:205/, 'temperature placeholder for second extruder correctly populated' . $syntax_description[$i]; ok $gcode =~ /temp2:200/, 'temperature placeholder for unused extruder populated with first value' . $syntax_description[$i]; } } $config->set('start_gcode', qq! ;substitution:{if infill_extruder==1}extruder1 {elsif infill_extruder==2}extruder2 {else}extruder3{endif} !); { my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $gcode = Slic3r::Test::gcode($print); ok $gcode =~ /substitution:extruder1/, 'if / else / endif - first block returned'; } } { my $config = Slic3r::Config::new_from_defaults; $config->set('before_layer_gcode', ';BEFORE [layer_num]'); $config->set('layer_gcode', ';CHANGE [layer_num]'); $config->set('support_material', 1); $config->set('layer_height', 0.2); my $print = Slic3r::Test::init_print('overhang', config => $config); my $gcode = Slic3r::Test::gcode($print); my @before = (); my @change = (); foreach my $line (split /\R+/, $gcode) { if ($line =~ /;BEFORE (\d+)/) { push @before, $1; } elsif ($line =~ /;CHANGE (\d+)/) { push @change, $1; fail 'inconsistent layer_num before and after layer change' if $1 != $before[-1]; } } is_deeply \@before, \@change, 'layer_num is consistent before and after layer changes'; ok !defined(first { $change[$_] != $change[$_-1]+1 } 1..$#change), 'layer_num grows continously'; # i.e. no duplicates or regressions } { my $config = Slic3r::Config->new; $config->set('start_gcode', qq! ;substitution:{if infill_extruder==1}if block {elsif infill_extruder==2}elsif block 1 {elsif infill_extruder==3}elsif block 2 {elsif infill_extruder==4}elsif block 3 {else}endif block{endif} !); my @returned = ('', 'if block', 'elsif block 1', 'elsif block 2', 'elsif block 3', 'endif block'); for my $i (1,2,3,4,5) { $config->set('infill_extruder', $i); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $gcode = Slic3r::Test::gcode($print); my $found_other = 0; for my $j (1,2,3,4,5) { next if $i == $j; $found_other = 1 if $gcode =~ /substitution:$returned[$j]/; } ok $gcode =~ /substitution:$returned[$i]/, 'if / else / endif - ' . $returned[$i] . ' returned'; ok !$found_other, 'if / else / endif - only ' . $returned[$i] . ' returned'; } } { my $config = Slic3r::Config->new; $config->set('start_gcode', ';substitution:{if infill_extruder==1}{if perimeter_extruder==1}block11{else}block12{endif}' . '{elsif infill_extruder==2}{if perimeter_extruder==1}block21{else}block22{endif}' . '{else}{if perimeter_extruder==1}block31{else}block32{endif}{endif}:end'); for my $i (1,2,3) { $config->set('infill_extruder', $i); for my $j (1,2) { $config->set('perimeter_extruder', $j); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $gcode = Slic3r::Test::gcode($print); ok $gcode =~ /substitution:block$i$j:end/, "two level if / else / endif - block$i$j returned"; } } } { my $config = Slic3r::Config->new; $config->set('start_gcode', ';substitution:{if notes=="MK2"}MK2{elsif notes=="MK3"}MK3{else}MK1{endif}:end'); for my $printer_name ("MK2", "MK3", "MK1") { $config->set('notes', $printer_name); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $gcode = Slic3r::Test::gcode($print); ok $gcode =~ /substitution:$printer_name:end/, "printer name $printer_name matched"; } } { my $config = Slic3r::Config::new_from_defaults; $config->set('complete_objects', 1); $config->set('between_objects_gcode', '_MY_CUSTOM_GCODE_'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 3); my $gcode = Slic3r::Test::gcode($print); is scalar(() = $gcode =~ /^_MY_CUSTOM_GCODE_/gm), 2, 'between_objects_gcode is applied correctly'; } __END__ Slic3r-version_1.39.1/t/dynamic.t000066400000000000000000000061551324354444700166060ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan skip_all => 'variable-width paths are currently disabled'; plan tests => 20; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Geometry qw(X Y scale epsilon); use Slic3r::Surface ':types'; sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $square = Slic3r::ExPolygon->new([ scale_points [0,0], [10,0], [10,10], [0,10], ]); my @offsets = @{$square->noncollapsing_offset_ex(- scale 5)}; is scalar @offsets, 1, 'non-collapsing offset'; } { local $Slic3r::Config = Slic3r::Config->new( perimeters => 3, ); my $w = 0.7; my $perimeter_flow = Slic3r::Flow->new( nozzle_diameter => 0.5, layer_height => 0.4, width => $w, ); my $print = Slic3r::Print->new; my $region = Slic3r::Print::Region->new( print => $print, flows => { perimeter => $perimeter_flow }, ); push @{$print->regions}, $region; my $object = Slic3r::Print::Object->new( print => $print, size => [1,1], ); my $make_layer = sub { my ($width) = @_; my $layer = Slic3r::Layer->new( object => $object, id => 1, slices => [ Slic3r::Surface->new( surface_type => S_TYPE_INTERNAL, expolygon => Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,$width], [0,$width] ]), ), ], thin_walls => [], ); my $layerm = $layer->region(0); $layer->make_perimeters; return $layerm; }; my %widths = ( 1 * $w => { perimeters => 1, gaps => 0 }, 1.3 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $perimeter_flow->clone(width => 0.2 * $w)->spacing }, 1.5 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $perimeter_flow->clone(width => 0.5 * $w)->spacing }, 2 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $perimeter_flow->spacing }, 2.5 * $w => { perimeters => 1, gaps => 1, gap_flow_spacing => $perimeter_flow->clone(width => 1.5 * $w)->spacing }, 3 * $w => { perimeters => 2, gaps => 0 }, 4 * $w => { perimeters => 2, gaps => 1, gap_flow_spacing => $perimeter_flow->spacing }, ); foreach my $width (sort keys %widths) { my $layerm = $make_layer->($width); is scalar @{$layerm->perimeters}, $widths{$width}{perimeters}, 'right number of perimeters'; is scalar @{$layerm->thin_fills} ? 1 : 0, $widths{$width}{gaps}, ($widths{$width}{gaps} ? 'gaps were filled' : 'no gaps detected'); # TODO: we should check the exact number of gaps, but we need a better medial axis algorithm my @gaps = map $_, @{$layerm->thin_fills}; if (@gaps) { ok +(!first { abs($_->flow_spacing - $widths{$width}{gap_flow_spacing}) > epsilon } @gaps), 'flow spacing was dynamically adjusted'; } } } __END__ Slic3r-version_1.39.1/t/fill.t000066400000000000000000000304441324354444700161060ustar00rootroot00000000000000use Test::More; use strict; use warnings; #plan tests => 43; # Test of a 100% coverage is off. plan tests => 19; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first sum); use Slic3r; use Slic3r::Geometry qw(X Y scale unscale convex_hull); use Slic3r::Geometry::Clipper qw(union diff diff_ex offset offset2_ex); use Slic3r::Surface qw(:types); use Slic3r::Test; sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [50,0], [50,50], [0,50] ]); my $filler = Slic3r::Filler->new_from_type('rectilinear'); $filler->set_bounding_box($expolygon->bounding_box); $filler->set_angle(0); my $surface = Slic3r::Surface->new( surface_type => S_TYPE_TOP, expolygon => $expolygon, ); my $flow = Slic3r::Flow->new( width => 0.69, height => 0.4, nozzle_diameter => 0.50, ); $filler->set_spacing($flow->spacing); foreach my $angle (0, 45) { $surface->expolygon->rotate(Slic3r::Geometry::deg2rad($angle), [0,0]); my $paths = $filler->fill_surface($surface, layer_height => 0.4, density => 0.4); is scalar @$paths, 1, 'one continuous path'; } } SKIP: { skip "The FillRectilinear2 does not fill the surface completely", 1; my $test = sub { my ($expolygon, $flow_spacing, $angle, $density) = @_; my $filler = Slic3r::Filler->new_from_type('rectilinear'); $filler->set_bounding_box($expolygon->bounding_box); $filler->set_angle($angle // 0); # Adjust line spacing to fill the region. $filler->set_dont_adjust(0); $filler->set_link_max_length(scale(1.2*$flow_spacing)); my $surface = Slic3r::Surface->new( surface_type => S_TYPE_BOTTOM, expolygon => $expolygon, ); my $flow = Slic3r::Flow->new( width => $flow_spacing, height => 0.4, nozzle_diameter => $flow_spacing, ); $filler->set_spacing($flow->spacing); my $paths = $filler->fill_surface( $surface, layer_height => $flow->height, density => $density // 1, ); # check whether any part was left uncovered my @grown_paths = map @{Slic3r::Polyline->new(@$_)->grow(scale $filler->spacing/2)}, @$paths; my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1); # ignore very small dots my $uncovered_filtered = [ grep $_->area > (scale $flow_spacing)**2, @$uncovered ]; is scalar(@$uncovered_filtered), 0, 'solid surface is fully filled'; if (0 && @$uncovered_filtered) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("uncovered.svg", no_arrows => 1, expolygons => [ $expolygon ], blue_expolygons => [ @$uncovered ], red_expolygons => [ @$uncovered_filtered ], polylines => [ @$paths ], ); exit; } }; my $expolygon = Slic3r::ExPolygon->new([ [6883102, 9598327.01296997], [6883102, 20327272.01297], [3116896, 20327272.01297], [3116896, 9598327.01296997], ]); $test->($expolygon, 0.55); for (1..20) { $expolygon->scale(1.05); $test->($expolygon, 0.55); } $expolygon = Slic3r::ExPolygon->new( [[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]] ); $test->($expolygon, 0.524341649025257); $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [98,0], [98,10], [0,10] ]); $test->($expolygon, 0.5, 45, 0.99); # non-solid infill } { my $collection = Slic3r::Polyline::Collection->new( Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), ); is_deeply [ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], [20, 18, 15, 10, 8, 5], 'chained path'; } { my $collection = Slic3r::Polyline::Collection->new( Slic3r::Polyline->new([4,0], [10,0], [15,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), ); is_deeply [ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], [reverse 4, 10, 15, 10, 15, 20], 'chained path'; } { my $collection = Slic3r::ExtrusionPath::Collection->new( map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), ); is_deeply [ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], [20, 18, 15, 10, 8, 5], 'chained path'; } { my $collection = Slic3r::ExtrusionPath::Collection->new( map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([15,0], [10,0], [4,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), ); is_deeply [ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], [reverse 4, 10, 15, 10, 15, 20], 'chained path'; } for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { my $config = Slic3r::Config::new_from_defaults; $config->set('fill_pattern', $pattern); $config->set('external_fill_pattern', $pattern); $config->set('perimeters', 1); $config->set('skirts', 0); $config->set('fill_density', 20); $config->set('layer_height', 0.05); $config->set('perimeter_extruder', 1); $config->set('infill_extruder', 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation"; my $tool = undef; my @perimeter_points = my @infill_points = (); Slic3r::GCode::Reader->new->parse($gcode, sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { if ($tool == $config->perimeter_extruder-1) { push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); } elsif ($tool == $config->infill_extruder-1) { push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); } } }); my $convex_hull = convex_hull(\@perimeter_points); ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)"; } { my $config = Slic3r::Config::new_from_defaults; $config->set('infill_only_where_needed', 1); $config->set('bottom_solid_layers', 0); $config->set('infill_extruder', 2); $config->set('infill_extrusion_width', 0.5); $config->set('fill_density', 40); $config->set('cooling', [ 0 ]); # for preventing speeds from being altered $config->set('first_layer_speed', '100%'); # for preventing speeds from being altered my $test = sub { my $print = Slic3r::Test::init_print('pyramid', config => $config); my $tool = undef; my @infill_extrusions = (); # array of polylines Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { if ($tool == $config->infill_extruder-1) { push @infill_extrusions, Slic3r::Line->new_scale( [ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ], ); } } }); return 0 if !@infill_extrusions; # prevent calling convex_hull() with no points my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]); return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))}); }; my $tolerance = 5; # mm^2 $config->set('solid_infill_below_area', 0); ok $test->() < $tolerance, 'no infill is generated when using infill_only_where_needed on a pyramid'; $config->set('solid_infill_below_area', 70); ok abs($test->() - $config->solid_infill_below_area) < $tolerance, 'infill is only generated under the forced solid shells'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); $config->set('solid_infill_below_area', 20000000); $config->set('solid_infill_every_layers', 2); $config->set('perimeter_speed', 99); $config->set('external_perimeter_speed', 99); $config->set('cooling', [ 0 ]); $config->set('first_layer_speed', '100%'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my %layers_with_extrusion = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) { if (($args->{F} // $self->F) != $config->perimeter_speed*60) { $layers_with_extrusion{$self->Z} = ($args->{F} // $self->F); } } }); ok !%layers_with_extrusion, "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0"; } { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 3); $config->set('fill_density', 0); $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.2); $config->set('nozzle_diameter', [0.35]); $config->set('infill_extruder', 2); $config->set('solid_infill_extruder', 2); $config->set('infill_extrusion_width', 0.52); $config->set('solid_infill_extrusion_width', 0.52); $config->set('first_layer_extrusion_width', 0); my $print = Slic3r::Test::init_print('A', config => $config); my %infill = (); # Z => [ Line, Line ... ] my $tool = undef; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { if ($tool == $config->infill_extruder-1) { my $z = 1 * $self->Z; $infill{$z} ||= []; push @{$infill{$z}}, Slic3r::Line->new_scale( [ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ], ); } } }); my $grow_d = scale($config->infill_extrusion_width)/2; my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]); my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]); my $diff = diff($layer0_infill, $layer1_infill); $diff = offset2_ex($diff, -$grow_d, +$grow_d); $diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ]; is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0'; } __END__ Slic3r-version_1.39.1/t/flow.t000066400000000000000000000055751324354444700161360ustar00rootroot00000000000000use Test::More tests => 6; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first sum); use Slic3r; use Slic3r::Geometry qw(scale PI); use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 1); $config->set('brim_width', 2); $config->set('perimeters', 3); $config->set('fill_density', 0.4); $config->set('bottom_solid_layers', 1); $config->set('first_layer_extrusion_width', 2); $config->set('first_layer_height', '100%'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my @E_per_mm = (); Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($self->Z == $config->layer_height) { # only consider first layer if ($info->{extruding} && $info->{dist_XY} > 0) { push @E_per_mm, $info->{dist_E} / $info->{dist_XY}; } } }); my $E_per_mm_avg = sum(@E_per_mm) / @E_per_mm; # allow some tolerance because solid rectilinear infill might be adjusted/stretched ok !(defined first { abs($_ - $E_per_mm_avg) > 0.015 } @E_per_mm), 'first_layer_extrusion_width applies to everything on first layer'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('bridge_speed', 99); $config->set('bridge_flow_ratio', 1); $config->set('cooling', [ 0 ]); # to prevent speeds from being altered $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered my $test = sub { my $print = Slic3r::Test::init_print('overhang', config => $config); my @E_per_mm = (); Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0) { if (($args->{F} // $self->F) == $config->bridge_speed*60) { push @E_per_mm, $info->{dist_E} / $info->{dist_XY}; } } }); my $expected_mm3_per_mm = ($config->nozzle_diameter->[0]**2) * PI/4 * $config->bridge_flow_ratio; my $expected_E_per_mm = $expected_mm3_per_mm / ((($config->filament_diameter->[0]/2)**2)*PI); ok !(defined first { abs($_ - $expected_E_per_mm) > 0.01 } @E_per_mm), 'expected flow when using bridge_flow_ratio = ' . $config->bridge_flow_ratio; }; $config->set('bridge_flow_ratio', 0.5); $test->(); $config->set('bridge_flow_ratio', 2); $test->(); $config->set('extrusion_width', 0.4); $config->set('bridge_flow_ratio', 1); $test->(); $config->set('bridge_flow_ratio', 0.5); $test->(); $config->set('bridge_flow_ratio', 2); $test->(); } __END__ Slic3r-version_1.39.1/t/gaps.t000066400000000000000000000041131324354444700161040ustar00rootroot00000000000000use Test::More tests => 1; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(PI scale unscale convex_hull); use Slic3r::Surface ':types'; use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeter_speed', 66); $config->set('external_perimeter_speed', 66); $config->set('small_perimeter_speed', 66); $config->set('gap_fill_speed', 99); $config->set('perimeters', 1); $config->set('cooling', [ 0 ]); # to prevent speeds from being altered $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered $config->set('perimeter_extrusion_width', 0.35); $config->set('first_layer_extrusion_width', 0.35); my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config); my @perimeter_points = (); my $last = ''; # perimeter | gap my $gap_fills_outside_last_perimeters = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0) { my $F = $args->{F} // $self->F; my $point = Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y}); if ($F == $config->perimeter_speed*60) { if ($last eq 'gap') { @perimeter_points = (); } push @perimeter_points, $point; $last = 'perimeter'; } elsif ($F == $config->gap_fill_speed*60) { my $convex_hull = convex_hull(\@perimeter_points); if (!$convex_hull->contains_point($point)) { $gap_fills_outside_last_perimeters++; } $last = 'gap'; } } }); is $gap_fills_outside_last_perimeters, 0, 'gap fills are printed before leaving islands'; } __END__ Slic3r-version_1.39.1/t/gcode.t000066400000000000000000000215111324354444700162340ustar00rootroot00000000000000use Test::More tests => 25; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Geometry qw(scale convex_hull); use Slic3r::Test; { my $gcodegen = Slic3r::GCode->new(); $gcodegen->set_layer_count(1); $gcodegen->set_origin(Slic3r::Pointf->new(10, 10)); is_deeply $gcodegen->last_pos->arrayref, [scale -10, scale -10], 'last_pos is shifted correctly'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('wipe', [1]); $config->set('retract_layer_change', [0]); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $have_wipe = 0; my @retract_speeds = (); my $extruded_on_this_layer = 0; my $wiping_on_new_layer = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{travel} && $info->{dist_Z}) { # changing layer $extruded_on_this_layer = 0; } elsif ($info->{extruding} && $info->{dist_XY}) { $extruded_on_this_layer = 1; } elsif ($info->{retracting} && $info->{dist_XY} > 0) { $have_wipe = 1; $wiping_on_new_layer = 1 if !$extruded_on_this_layer; my $move_time = $info->{dist_XY} / ($args->{F} // $self->F); push @retract_speeds, abs($info->{dist_E}) / $move_time; } }); ok $have_wipe, "wipe"; ok !defined (first { abs($_ - $config->retract_speed->[0]*60) < 5 } @retract_speeds), 'wipe moves don\'t retract faster than configured speed'; ok !$wiping_on_new_layer, 'no wiping after layer change'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('z_offset', 5); $config->set('start_gcode', ''); my $test = sub { my ($comment) = @_; my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $moves_below_z_offset = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{travel} && exists $args->{Z}) { $moves_below_z_offset++ if $args->{Z} < $config->z_offset; } }); is $moves_below_z_offset, 0, "no Z moves below Z offset ($comment)"; }; $test->("no lift"); $config->set('retract_lift', [3]); $test->("lift < z_offset"); $config->set('retract_lift', [6]); $test->("lift > z_offset"); } { # This tests the following behavior: # - complete objects does not crash # - no hard-coded "E" are generated # - Z moves are correctly generated for both objects # - no travel moves go outside skirt # - temperatures are set correctly my $config = Slic3r::Config::new_from_defaults; $config->set('gcode_comments', 1); $config->set('complete_objects', 1); $config->set('extrusion_axis', 'A'); $config->set('start_gcode', ''); # prevent any default extra Z move $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.4); $config->set('temperature', [200]); $config->set('first_layer_temperature', [210]); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); ok my $gcode = Slic3r::Test::gcode($print), "complete_objects"; my @z_moves = (); my @travel_moves = (); # array of scaled points my @extrusions = (); # array of scaled points my @temps = (); Slic3r::GCode::Reader->new->parse($gcode, sub { my ($self, $cmd, $args, $info) = @_; fail 'unexpected E argument' if defined $args->{E}; if (defined $args->{Z}) { push @z_moves, $args->{Z}; } if ($info->{dist_XY}) { if ($info->{extruding} || $args->{A}) { push @extrusions, Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y}); } else { push @travel_moves, Slic3r::Point->new_scale($info->{new_X}, $info->{new_Y}) if @extrusions; # skip initial travel move to first skirt point } } elsif ($cmd eq 'M104' || $cmd eq 'M109') { push @temps, $args->{S} if !@temps || $args->{S} != $temps[-1]; } }); my $layer_count = 20/0.4; # cube is 20mm tall is scalar(@z_moves), 2*$layer_count, 'complete_objects generates the correct number of Z moves'; is_deeply [ @z_moves[0..($layer_count-1)] ], [ @z_moves[$layer_count..$#z_moves] ], 'complete_objects generates the correct Z moves'; my $convex_hull = convex_hull(\@extrusions); ok !(defined first { !$convex_hull->contains_point($_) } @travel_moves), 'all travel moves happen within skirt'; is_deeply \@temps, [210, 200, 210, 200, 0], 'expected temperature changes'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('retract_length', [1000000]); $config->set('use_relative_e_distances', 1); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); Slic3r::Test::gcode($print); ok $print->print->total_used_filament > 0, 'final retraction is not considered in total used filament'; } { my $test = sub { my ($print, $comment) = @_; my @percent = (); my $got_100 = 0; my $extruding_after_100 = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'M73') { push @percent, $args->{P}; $got_100 = 1 if $args->{P} eq '100'; } if ($info->{extruding} && $got_100) { $extruding_after_100 = 1; } }); # the extruder heater is turned off when M73 P100 is reached ok !(defined first { $_ > 100 } @percent), "M73 is never given more than 100% ($comment)"; ok !$extruding_after_100, "no extrusions after M73 P100 ($comment)"; }; { my $config = Slic3r::Config::new_from_defaults; $config->set('gcode_flavor', 'sailfish'); $config->set('raft_layers', 3); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); $test->($print, 'single object'); } { my $config = Slic3r::Config::new_from_defaults; $config->set('gcode_flavor', 'sailfish'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); $test->($print, 'two copies of single object'); } { my $config = Slic3r::Config::new_from_defaults; $config->set('gcode_flavor', 'sailfish'); my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config); $test->($print, 'two objects'); } { my $config = Slic3r::Config::new_from_defaults; $config->set('gcode_flavor', 'sailfish'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale_xyz => [1,1, 1/(20/$config->layer_height) ]); $test->($print, 'one layer object'); } } { my $config = Slic3r::Config::new_from_defaults; $config->set('start_gcode', 'START:[input_filename]'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $gcode = Slic3r::Test::gcode($print); like $gcode, qr/START:20mm_cube/, '[input_filename] is also available in custom G-code'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('spiral_vase', 1); my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); my $spiral = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && exists $args->{E} && exists $args->{Z}) { $spiral = 1; } }); ok !$spiral, 'spiral vase is correctly disabled on layers with multiple loops'; } { # Tests that the Repetier flavor produces M201 Xnnn Ynnn for resetting # acceleration, also that M204 Snnn syntax is not generated. my $config = Slic3r::Config::new_from_defaults; $config->set('gcode_flavor', 'repetier'); $config->set('default_acceleration', 1337); my $print = Slic3r::Test::init_print('cube_with_hole', config => $config); my $has_accel = 0; my $has_m204 = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'M201' && exists $args->{X} && exists $args->{Y}) { if ($args->{X} == 1337 && $args->{Y} == 1337) { $has_accel = 1; } } if ($cmd eq 'M204' && exists $args->{S}) { $has_m204 = 1; } }); ok $has_accel, 'M201 is generated for repetier firmware.'; ok !$has_m204, 'M204 is not generated for repetier firmware'; } __END__ Slic3r-version_1.39.1/t/geometry.t000066400000000000000000000235301324354444700170110ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 42; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; use Slic3r::Geometry qw(PI polygon_is_convex chained_path_from epsilon scale); { # this test was failing on Windows (GH #1950) my $polygon = Slic3r::Polygon->new( [207802834,-57084522],[196528149,-37556190],[173626821,-25420928],[171285751,-21366123], [118673592,-21366123],[116332562,-25420928],[93431208,-37556191],[82156517,-57084523], [129714478,-84542120],[160244873,-84542120], ); my $point = Slic3r::Point->new(95706562, -57294774); ok $polygon->contains_point($point), 'contains_point'; } #========================================================== my $line1 = [ [5, 15], [30, 15] ]; my $line2 = [ [10, 20], [10, 10] ]; is_deeply Slic3r::Geometry::line_intersection($line1, $line2, 1)->arrayref, [10, 15], 'line_intersection'; #========================================================== $line1 = [ [73.6310778185108/0.0000001, 371.74239268924/0.0000001], [73.6310778185108/0.0000001, 501.74239268924/0.0000001] ]; $line2 = [ [75/0.0000001, 437.9853/0.0000001], [62.7484/0.0000001, 440.4223/0.0000001] ]; isnt Slic3r::Geometry::line_intersection($line1, $line2, 1), undef, 'line_intersection'; #========================================================== { my $polygon = Slic3r::Polygon->new( [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800], [43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600], [75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500], [107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300], [82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500], [285273900, 461246400], [254081000, 515273900], ); # this points belongs to $polyline # note: it's actually a vertex, while we should better check an intermediate point my $point = Slic3r::Point->new(104577600, 327748400); local $Slic3r::Geometry::epsilon = 1E-5; is_deeply Slic3r::Geometry::polygon_segment_having_point($polygon, $point)->pp, [ [107014700, 340000000], [104577600, 327748400] ], 'polygon_segment_having_point'; } #========================================================== { my $point = Slic3r::Point->new(736310778.185108, 5017423926.8924); my $line = Slic3r::Line->new([627484000, 3695776000], [750000000, 3720147000]); is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; } #========================================================== { my $point = Slic3r::Point->new(736310778.185108, 5017423926.8924); my $line = Slic3r::Line->new([627484000, 3695776000], [750000000, 3720147000]); is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; } #========================================================== my $polygons = [ Slic3r::Polygon->new( # contour, ccw [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800], [43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600], [75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500], [107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300], [82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500], [285273900, 461246400], [254081000, 515273900], ), Slic3r::Polygon->new( # hole, cw [75000000, 502014700], [87251500, 499577600], [97637800, 492637800], [104577600, 482251500], [107014700, 470000000], [104577600, 457748400], [97637800, 447362100], [87251500, 440422300], [75000000, 437985300], [62748400, 440422300], [52362100, 447362100], [45422300, 457748400], [42985300, 470000000], [45422300, 482251500], [52362100, 492637800], [62748400, 499577600], ), ]; #========================================================== { my $p1 = [10, 10]; my $p2 = [10, 20]; my $p3 = [10, 30]; my $p4 = [20, 20]; my $p5 = [0, 20]; is Slic3r::Geometry::angle3points($p2, $p3, $p1), PI(), 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p3, $p4), PI()/2*3, 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p4, $p3), PI()/2, 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2, 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p1, $p5), PI()/2*3, 'angle3points'; } { my $p1 = [30, 30]; my $p2 = [20, 20]; my $p3 = [10, 10]; my $p4 = [30, 10]; is Slic3r::Geometry::angle3points($p2, $p1, $p3), PI(), 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p1, $p4), PI()/2*3, 'angle3points'; is Slic3r::Geometry::angle3points($p2, $p1, $p1), 2*PI(), 'angle3points'; } #========================================================== { my $cw_square = [ [0,0], [0,10], [10,10], [10,0] ]; is polygon_is_convex($cw_square), 0, 'cw square is not convex'; is polygon_is_convex([ reverse @$cw_square ]), 1, 'ccw square is convex'; my $convex1 = [ [0,0], [10,0], [10,10], [0,10], [0,6], [4,6], [4,4], [0,4] ]; is polygon_is_convex($convex1), 0, 'concave polygon'; } #========================================================== { my $polyline = Slic3r::Polyline->new([0, 0], [10, 0], [20, 0]); is_deeply [ map $_->pp, @{$polyline->lines} ], [ [ [0, 0], [10, 0] ], [ [10, 0], [20, 0] ], ], 'polyline_lines'; } #========================================================== { my $polygon = Slic3r::Polygon->new([0, 0], [10, 0], [5, 5]); my $result = $polygon->split_at_index(1); is ref($result), 'Slic3r::Polyline', 'split_at_index returns polyline'; is_deeply $result->pp, [ [10, 0], [5, 5], [0, 0], [10, 0] ], 'split_at_index'; } #========================================================== { my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), [0, 1], [10, 2], [20, 2] ]); $bb->scale(2); is_deeply [ $bb->min_point->pp, $bb->max_point->pp ], [ [0,2], [40,4] ], 'bounding box is scaled correctly'; } #========================================================== { my $line = Slic3r::Line->new([10,10], [20,10]); is $line->grow(5)->[0]->area, Slic3r::Polygon->new([10,5], [20,5], [20,15], [10,15])->area, 'grow line'; } #========================================================== { # if chained_path() works correctly, these points should be joined with no diagonal paths # (thus 26 units long) my @points = map Slic3r::Point->new_scale(@$_), [26,26],[52,26],[0,26],[26,52],[26,0],[0,52],[52,52],[52,0]; my @ordered = @points[@{chained_path_from(\@points, $points[0])}]; ok !(grep { abs($ordered[$_]->distance_to($ordered[$_+1]) - scale 26) > epsilon } 0..$#ordered-1), 'chained_path'; } #========================================================== { my $line = Slic3r::Line->new([0, 0], [20, 0]); is +Slic3r::Point->new(10, 10)->distance_to_line($line), 10, 'distance_to'; is +Slic3r::Point->new(50, 0)->distance_to_line($line), 30, 'distance_to'; is +Slic3r::Point->new(0, 0)->distance_to_line($line), 0, 'distance_to'; is +Slic3r::Point->new(20, 0)->distance_to_line($line), 0, 'distance_to'; is +Slic3r::Point->new(10, 0)->distance_to_line($line), 0, 'distance_to'; } #========================================================== { my $square = Slic3r::Polygon->new_scale( [100,100], [200,100], [200,200], [100,200], ); is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in ccw square'; is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in ccw square'; $square->make_clockwise; is scalar(@{$square->concave_points(PI*4/3)}), 4, 'fuor concave vertices detected in cw square'; is scalar(@{$square->convex_points(PI*2/3)}), 0, 'no convex vertices detected in cw square'; } { my $square = Slic3r::Polygon->new_scale( [150,100], [200,100], [200,200], [100,200], [100,100], ); is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in convex polygon'; is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in square'; } { my $square = Slic3r::Polygon->new_scale( [200,200], [100,200], [100,100], [150,100], [200,100], ); is scalar(@{$square->concave_points(PI*4/3)}), 0, 'no concave vertices detected in convex polygon'; is scalar(@{$square->convex_points(PI*2/3)}), 4, 'four convex vertices detected in square'; } { my $triangle = Slic3r::Polygon->new( [16000170,26257364], [714223,461012], [31286371,461008], ); is scalar(@{$triangle->concave_points(PI*4/3)}), 0, 'no concave vertices detected in triangle'; is scalar(@{$triangle->convex_points(PI*2/3)}), 3, 'three convex vertices detected in triangle'; } { my $triangle = Slic3r::Polygon->new( [16000170,26257364], [714223,461012], [20000000,461012], [31286371,461012], ); is scalar(@{$triangle->concave_points(PI*4/3)}), 0, 'no concave vertices detected in triangle having collinear point'; is scalar(@{$triangle->convex_points(PI*2/3)}), 3, 'three convex vertices detected in triangle having collinear point'; } { my $triangle = Slic3r::Polygon->new( [16000170,26257364], [714223,461012], [31286371,461008], ); my $simplified = $triangle->simplify(250000)->[0]; is scalar(@$simplified), 3, 'triangle is never simplified to less than 3 points'; } __END__ Slic3r-version_1.39.1/t/layers.t000066400000000000000000000043071324354444700164560ustar00rootroot00000000000000use Test::More tests => 5; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Test qw(_eq); { my $config = Slic3r::Config::new_from_defaults; my $test = sub { my ($conf) = @_; $conf ||= $config; my $print = Slic3r::Test::init_print('20mm_cube', config => $conf); my @z = (); my @increments = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{dist_Z}) { push @z, 1*$args->{Z}; push @increments, $info->{dist_Z}; } }); fail 'wrong first layer height' if $z[0] ne $config->get_value('first_layer_height') + $config->z_offset; fail 'wrong second layer height' if $z[1] ne $config->get_value('first_layer_height') + $config->get_value('layer_height') + $config->z_offset; fail 'wrong layer height' if first { !_eq($_, $config->layer_height) } @increments[1..$#increments]; 1; }; $config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code $config->set('layer_height', 0.3); $config->set('first_layer_height', 0.2); ok $test->(), "absolute first layer height"; $config->set('first_layer_height', '60%'); ok $test->(), "relative first layer height"; $config->set('z_offset', 0.9); ok $test->(), "positive Z offset"; $config->set('z_offset', -0.8); ok $test->(), "negative Z offset"; } { my $config = Slic3r::Config->new; $config->set('fill_density', 0); # just for making the test faster my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); my @z = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{dist_Z}) { push @z, 1*$args->{Z}; } }); ok $z[-1] > 20*1.8 && $z[-1] < 20*2.2, 'resulting G-code has reasonable height'; } __END__ Slic3r-version_1.39.1/t/loops.t000066400000000000000000000040351324354444700163110ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan skip_all => 'temporarily disabled'; plan tests => 4; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; use Slic3r::Test; { # We only need to slice at one height, so we'll build a non-manifold mesh # that still produces complete loops at that height. Triangular walls are # enough for this purpose. # Basically we want to check what happens when three concentric loops happen # to be at the same height, the two external ones being ccw and the other being # a hole, thus cw. my (@vertices, @facets) = (); Slic3r::Test::add_facet($_, \@vertices, \@facets) for # external surface below the slicing Z [ [0,0,0], [20,0,10], [0,0,10] ], [ [20,0,0], [20,20,10], [20,0,10] ], [ [20,20,0], [0,20,10], [20,20,10] ], [ [0,20,0], [0,0,10], [0,20,10] ], # external insetted surface above the slicing Z [ [2,2,10], [18,2,10], [2,2,20] ], [ [18,2,10], [18,18,10], [18,2,20] ], [ [18,18,10], [2,18,10], [18,18,20] ], [ [2,18,10], [2,2,10], [2,18,20] ], # insetted hole below the slicing Z [ [15,5,0], [5,5,10], [15,5,10] ], [ [15,15,0], [15,5,10], [15,15,10] ], [ [5,15,0], [15,15,10], [5,15,10] ], [ [5,5,0], [5,15,10], [5,5,10] ]; my $mesh = Slic3r::TriangleMesh->new; $mesh->ReadFromPerl(\@vertices, \@facets); $mesh->analyze; my @lines = map $mesh->intersect_facet($_, 10), 0..$#facets; my $loops = Slic3r::TriangleMesh::make_loops(\@lines); is scalar(@$loops), 3, 'correct number of loops detected'; is scalar(grep $_->is_counter_clockwise, @$loops), 2, 'correct number of ccw loops detected'; my @surfaces = Slic3r::Layer::Region::_merge_loops($loops, 0); is scalar(@surfaces), 1, 'one surface detected'; is scalar(@{$surfaces[0]->expolygon})-1, 1, 'surface has one hole'; } __END__ Slic3r-version_1.39.1/t/multi.t000066400000000000000000000200601324354444700163030ustar00rootroot00000000000000use Test::More tests => 13; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Geometry qw(scale convex_hull); use Slic3r::Geometry::Clipper qw(offset); use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; $config->set('raft_layers', 2); $config->set('infill_extruder', 2); $config->set('solid_infill_extruder', 3); $config->set('support_material_extruder', 4); $config->set('ooze_prevention', 1); $config->set('extruder_offset', [ [0,0], [20,0], [0,20], [20,20] ]); $config->set('temperature', [200, 180, 170, 160]); $config->set('first_layer_temperature', [206, 186, 166, 156]); $config->set('toolchange_gcode', ';toolchange'); # test that it doesn't crash when this is supplied my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $tool = undef; my @tool_temp = (0,0,0,0); my @toolchange_points = (); my @extrusion_points = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { # ignore initial toolchange if (defined $tool) { my $expected_temp = $self->Z == ($config->get_value('first_layer_height') + $config->z_offset) ? $config->first_layer_temperature->[$tool] : $config->temperature->[$tool]; die 'standby temperature was not set before toolchange' if $tool_temp[$tool] != $expected_temp + $config->standby_temperature_delta; push @toolchange_points, my $point = Slic3r::Point->new_scale($self->X, $self->Y); } $tool = $1; } elsif ($cmd eq 'M104' || $cmd eq 'M109') { my $t = $args->{T} // $tool; if ($tool_temp[$t] == 0) { fail 'initial temperature is not equal to first layer temperature + standby delta' unless $args->{S} == $config->first_layer_temperature->[$t] + $config->standby_temperature_delta; } $tool_temp[$t] = $args->{S}; } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { push @extrusion_points, my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y}); $point->translate(map +scale($_), @{ $config->extruder_offset->[$tool] }); } }); my $convex_hull = convex_hull(\@extrusion_points); my @t = (); foreach my $point (@toolchange_points) { foreach my $offset (@{$config->extruder_offset}) { push @t, my $p = $point->clone; $p->translate(map +scale($_), @$offset); } } ok !(defined first { $convex_hull->contains_point($_) } @t), 'all nozzles are outside skirt at toolchange'; if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "ooze_prevention_test.svg", no_arrows => 1, polygons => [$convex_hull], red_points => \@t, points => \@toolchange_points, ); } # offset the skirt by the maximum displacement between extruders plus a safety extra margin my $delta = scale(20 * sqrt(2) + 1); my $outer_convex_hull = offset([$convex_hull], +$delta)->[0]; ok !(defined first { !$outer_convex_hull->contains_point($_) } @toolchange_points), 'all toolchanges happen within expected area'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('support_material_extruder', 3); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok Slic3r::Test::gcode($print), 'no errors when using non-consecutive extruders'; } { my $config = Slic3r::Config->new; $config->set('extruder', 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); like Slic3r::Test::gcode($print), qr/ T1/, 'extruder shortcut'; } { my $config = Slic3r::Config->new; $config->set('perimeter_extruder', 2); $config->set('infill_extruder', 2); $config->set('support_material_extruder', 2); $config->set('support_material_interface_extruder', 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok Slic3r::Test::gcode($print), 'no errors when using multiple skirts with a single, non-zero, extruder'; } { my $model = stacked_cubes(); my $lower_config = $model->get_material('lower')->config; my $upper_config = $model->get_material('upper')->config; $lower_config->set('extruder', 1); $lower_config->set('bottom_solid_layers', 0); $lower_config->set('top_solid_layers', 1); $upper_config->set('extruder', 2); $upper_config->set('bottom_solid_layers', 1); $upper_config->set('top_solid_layers', 0); my $config = Slic3r::Config::new_from_defaults; $config->set('fill_density', 0); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); $config->set('cooling', [ 0 ]); # for preventing speeds from being altered $config->set('first_layer_speed', '100%'); # for preventing speeds from being altered my $test = sub { my $print = Slic3r::Test::init_print($model, config => $config); my $tool = undef; my %T0_shells = my %T1_shells = (); # Z => 1 Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { if (($args->{F} // $self->F) == $config->solid_infill_speed*60) { if ($tool == 0) { $T0_shells{$self->Z} = 1; } elsif ($tool == 1) { $T1_shells{$self->Z} = 1; } } } }); return [ sort keys %T0_shells ], [ sort keys %T1_shells ]; }; { my ($t0, $t1) = $test->(); is scalar(@$t0), 0, 'no interface shells'; is scalar(@$t1), 0, 'no interface shells'; } { $config->set('interface_shells', 1); my ($t0, $t1) = $test->(); is scalar(@$t0), $lower_config->top_solid_layers, 'top interface shells'; is scalar(@$t1), $upper_config->bottom_solid_layers, 'bottom interface shells'; } } { my $model = stacked_cubes(); my $object = $model->objects->[0]; my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.4); $config->set('first_layer_height', '100%'); $config->set('skirts', 0); my $print = Slic3r::Test::init_print($model, config => $config); is $object->volumes->[0]->config->extruder, 1, 'auto_assign_extruders() assigned correct extruder to first volume'; is $object->volumes->[1]->config->extruder, 2, 'auto_assign_extruders() assigned correct extruder to second volume'; my $tool = undef; my %T0 = my %T1 = (); # Z => 1 Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { if ($tool == 0) { $T0{$self->Z} = 1; } elsif ($tool == 1) { $T1{$self->Z} = 1; } } }); ok !(defined first { $_ > 20 } keys %T0), 'T0 is never used for upper object'; ok !(defined first { $_ < 20 } keys %T1), 'T1 is never used for lower object'; } sub stacked_cubes { my $model = Slic3r::Model->new; my $object = $model->add_object; $object->add_volume(mesh => Slic3r::Test::mesh('20mm_cube'), material_id => 'lower'); $object->add_volume(mesh => Slic3r::Test::mesh('20mm_cube', translate => [0,0,20]), material_id => 'upper'); $object->add_instance(offset => Slic3r::Pointf->new(0,0)); return $model; } __END__ Slic3r-version_1.39.1/t/perimeters.t000066400000000000000000000573241324354444700173450ustar00rootroot00000000000000use Test::More tests => 59; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r::ExtrusionLoop ':roles'; use Slic3r::ExtrusionPath ':roles'; use List::Util qw(first); use Slic3r; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(PI scale unscale); use Slic3r::Geometry::Clipper qw(union_ex diff union offset); use Slic3r::Surface ':types'; use Slic3r::Test; { my $flow = Slic3r::Flow->new( width => 1, height => 1, nozzle_diameter => 1, ); my $config = Slic3r::Config->new; my $test = sub { my ($expolygons, %expected) = @_; my $slices = Slic3r::Surface::Collection->new; $slices->append(Slic3r::Surface->new( surface_type => S_TYPE_INTERNAL, expolygon => $_, )) for @$expolygons; my ($region_config, $object_config, $print_config, $loops, $gap_fill, $fill_surfaces); my $g = Slic3r::Layer::PerimeterGenerator->new( # input: $slices, 1, # layer height $flow, ($region_config = Slic3r::Config::PrintRegion->new), ($object_config = Slic3r::Config::PrintObject->new), ($print_config = Slic3r::Config::Print->new), # output: ($loops = Slic3r::ExtrusionPath::Collection->new), ($gap_fill = Slic3r::ExtrusionPath::Collection->new), ($fill_surfaces = Slic3r::Surface::Collection->new), ); $g->config->apply_dynamic($config); $g->process; is scalar(@$loops), scalar(@$expolygons), 'expected number of collections'; ok !defined(first { !$_->isa('Slic3r::ExtrusionPath::Collection') } @$loops), 'everything is returned as collections'; my $flattened_loops = $loops->flatten; my @loops = @$flattened_loops; is scalar(@loops), $expected{total}, 'expected number of loops'; is scalar(grep $_->role == EXTR_ROLE_EXTERNAL_PERIMETER, map @$_, @loops), $expected{external}, 'expected number of external loops'; is_deeply [ map { ($_->role == EXTR_ROLE_EXTERNAL_PERIMETER) || 0 } map @$_, @loops ], $expected{ext_order}, 'expected external order'; is scalar(grep $_->loop_role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER, @loops), $expected{cinternal}, 'expected number of internal contour loops'; is scalar(grep $_->polygon->is_counter_clockwise, @loops), $expected{ccw}, 'expected number of ccw loops'; is_deeply [ map $_->polygon->is_counter_clockwise, @loops ], $expected{ccw_order}, 'expected ccw/cw order'; if ($expected{nesting}) { foreach my $nesting (@{ $expected{nesting} }) { for my $i (1..$#$nesting) { ok $loops[$nesting->[$i-1]]->polygon->contains_point($loops[$nesting->[$i]]->first_point), 'expected nesting order'; } } } }; $config->set('perimeters', 3); $test->( [ Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), ), ], total => 3, external => 1, ext_order => [0,0,1], cinternal => 1, ccw => 3, ccw_order => [1,1,1], nesting => [ [2,1,0] ], ); $test->( [ Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([0,0], [100,0], [100,100], [0,100]), Slic3r::Polygon->new_scale([40,40], [40,60], [60,60], [60,40]), ), ], total => 6, external => 2, ext_order => [0,0,1,0,0,1], cinternal => 1, ccw => 3, ccw_order => [0,0,0,1,1,1], nesting => [ [5,4,3,0,1,2] ], ); $test->( [ Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([0,0], [200,0], [200,200], [0,200]), Slic3r::Polygon->new_scale([20,20], [20,180], [180,180], [180,20]), ), # nested: Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([50,50], [150,50], [150,150], [50,150]), Slic3r::Polygon->new_scale([80,80], [80,120], [120,120], [120,80]), ), ], total => 4*3, external => 4, ext_order => [0,0,1,0,0,1,0,0,1,0,0,1], cinternal => 2, ccw => 2*3, ccw_order => [0,0,0,1,1,1,0,0,0,1,1,1], ); $config->set('perimeters', 2); $test->( [ Slic3r::ExPolygon->new( Slic3r::Polygon->new_scale([0,0], [50,0], [50,50], [0,50]), Slic3r::Polygon->new_scale([7.5,7.5], [7.5,12.5], [12.5,12.5], [12.5,7.5]), Slic3r::Polygon->new_scale([7.5,17.5], [7.5,22.5], [12.5,22.5], [12.5,17.5]), Slic3r::Polygon->new_scale([7.5,27.5], [7.5,32.5], [12.5,32.5], [12.5,27.5]), Slic3r::Polygon->new_scale([7.5,37.5], [7.5,42.5], [12.5,42.5], [12.5,37.5]), Slic3r::Polygon->new_scale([17.5,7.5], [17.5,12.5], [22.5,12.5], [22.5,7.5]), ), ], total => 12, external => 6, ext_order => [0,1,0,1,0,1,0,1,0,1,0,1], cinternal => 1, ccw => 2, ccw_order => [0,0,0,0,0,0,0,0,0,0,1,1], nesting => [ [0,1],[2,3],[4,5],[6,7],[8,9] ], ); } { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('fill_density', 0); $config->set('perimeters', 3); $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); $config->set('cooling', [ 0 ]); # to prevent speeds from being altered $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered { my $print = Slic3r::Test::init_print('overhang', config => $config); my $has_cw_loops = 0; my $cur_loop; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0) { $cur_loop ||= [ [$self->X, $self->Y] ]; push @$cur_loop, [ @$info{qw(new_X new_Y)} ]; } else { if ($cur_loop) { $has_cw_loops = 1 if Slic3r::Polygon->new(@$cur_loop)->is_clockwise; $cur_loop = undef; } } }); ok !$has_cw_loops, 'all perimeters extruded ccw'; } foreach my $model (qw(cube_with_hole cube_with_concave_hole)) { $config->set('external_perimeter_speed', 68); my $print = Slic3r::Test::init_print( $model, config => $config, duplicate => 2, # we test two copies to make sure ExtrusionLoop objects are not modified in-place (the second object would not detect cw loops and thus would calculate wrong inwards moves) ); my $has_cw_loops = my $has_outwards_move = my $starts_on_convex_point = 0; my $cur_loop; my %external_loops = (); # print_z => count of external loops Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0) { $cur_loop ||= [ [$self->X, $self->Y] ]; push @$cur_loop, [ @$info{qw(new_X new_Y)} ]; } else { if ($cur_loop) { $has_cw_loops = 1 if Slic3r::Polygon->new_scale(@$cur_loop)->is_clockwise; if ($self->F == $config->external_perimeter_speed*60) { my $move_dest = Slic3r::Point->new_scale(@$info{qw(new_X new_Y)}); # reset counter for second object $external_loops{$self->Z} = 0 if defined($external_loops{$self->Z}) && $external_loops{$self->Z} == 2; $external_loops{$self->Z}++; my $is_contour = $external_loops{$self->Z} == 2; my $is_hole = $external_loops{$self->Z} == 1; my $loop = Slic3r::Polygon->new_scale(@$cur_loop); my $loop_contains_point = $loop->contains_point($move_dest); $has_outwards_move = 1 if (!$loop_contains_point && $is_contour) # contour should include destination || ($loop_contains_point && $is_hole); # hole should not if ($model eq 'cube_with_concave_hole') { # check that loop starts at a concave vertex my $ccw_angle = $loop->first_point->ccw_angle(@$loop[-2,1]); my $convex = ($ccw_angle > PI); # whether the angle on the *right* side is convex $starts_on_convex_point = 1 if ($convex && $is_contour) || (!$convex && $is_hole); } } $cur_loop = undef; } } }); ok !$has_cw_loops, 'all perimeters extruded ccw'; ok !$has_outwards_move, 'move inwards after completing external loop'; ok !$starts_on_convex_point, 'loops start on concave point if any'; } { $config->set('perimeters', 1); $config->set('perimeter_speed', 77); $config->set('external_perimeter_speed', 66); $config->set('bridge_speed', 99); $config->set('cooling', [ 1 ]); $config->set('fan_below_layer_time', [ 0 ]); $config->set('slowdown_below_layer_time', [ 0 ]); $config->set('bridge_fan_speed', [ 100 ]); $config->set('bridge_flow_ratio', 33); # arbitrary value $config->set('overhangs', 1); my $print = Slic3r::Test::init_print('overhang', config => $config); my %layer_speeds = (); # print Z => [ speeds ] my $fan_speed = 0; my $bridge_mm_per_mm = ($config->nozzle_diameter->[0]**2) / ($config->filament_diameter->[0]**2) * $config->bridge_flow_ratio; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; $fan_speed = 0 if $cmd eq 'M107'; $fan_speed = $args->{S} if $cmd eq 'M106'; if ($info->{extruding} && $info->{dist_XY} > 0) { $layer_speeds{$self->Z} ||= {}; $layer_speeds{$self->Z}{my $feedrate = $args->{F} // $self->F} = 1; fail 'wrong speed found' if $feedrate != $config->perimeter_speed*60 && $feedrate != $config->external_perimeter_speed*60 && $feedrate != $config->bridge_speed*60; if ($feedrate == $config->bridge_speed*60) { fail 'printing overhang but fan is not enabled or running at wrong speed' if $fan_speed != 255; my $mm_per_mm = $info->{dist_E} / $info->{dist_XY}; fail 'wrong bridge flow' if abs($mm_per_mm - $bridge_mm_per_mm) > 0.01; } else { fail 'fan is running when not supposed to' if $fan_speed > 0; } } }); is scalar(grep { keys %$_ > 1 } values %layer_speeds), 1, 'only overhang layer has more than one speed'; } } { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 3); $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.35); $config->set('extra_perimeters', 1); $config->set('cooling', [ 0 ]); # to prevent speeds from being altered $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered $config->set('perimeter_speed', 99); $config->set('external_perimeter_speed', 99); $config->set('small_perimeter_speed', 99); $config->set('thin_walls', 0); my $print = Slic3r::Test::init_print('ipadstand', config => $config); my %perimeters = (); # z => number of loops my $in_loop = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0 && ($args->{F} // $self->F) == $config->perimeter_speed*60) { $perimeters{$self->Z}++ if !$in_loop; $in_loop = 1; } else { $in_loop = 0; } }); ok !(grep { $_ % $config->perimeters } values %perimeters), 'no superfluous extra perimeters'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('nozzle_diameter', [0.4]); $config->set('perimeters', 2); $config->set('perimeter_extrusion_width', 0.4); $config->set('external_perimeter_extrusion_width', 0.4); $config->set('infill_extrusion_width', 0.53); $config->set('solid_infill_extrusion_width', 0.53); # we just need a pre-filled Print object my $print = Slic3r::Test::init_print('20mm_cube', config => $config); # override a layer's slices my $expolygon = Slic3r::ExPolygon->new([[-71974463,-139999376],[-71731792,-139987456],[-71706544,-139985616],[-71682119,-139982639],[-71441248,-139946912],[-71417487,-139942895],[-71379384,-139933984],[-71141800,-139874480],[-71105247,-139862895],[-70873544,-139779984],[-70838592,-139765856],[-70614943,-139660064],[-70581783,-139643567],[-70368368,-139515680],[-70323751,-139487872],[-70122160,-139338352],[-70082399,-139306639],[-69894800,-139136624],[-69878679,-139121327],[-69707992,-138933008],[-69668575,-138887343],[-69518775,-138685359],[-69484336,-138631632],[-69356423,-138418207],[-69250040,-138193296],[-69220920,-138128976],[-69137992,-137897168],[-69126095,-137860255],[-69066568,-137622608],[-69057104,-137582511],[-69053079,-137558751],[-69017352,-137317872],[-69014392,-137293456],[-69012543,-137268207],[-68999369,-137000000],[-63999999,-137000000],[-63705947,-136985551],[-63654984,-136977984],[-63414731,-136942351],[-63364756,-136929840],[-63129151,-136870815],[-62851950,-136771631],[-62585807,-136645743],[-62377483,-136520895],[-62333291,-136494415],[-62291908,-136463728],[-62096819,-136319023],[-62058644,-136284432],[-61878676,-136121328],[-61680968,-135903184],[-61650275,-135861807],[-61505591,-135666719],[-61354239,-135414191],[-61332211,-135367615],[-61228359,-135148063],[-61129179,-134870847],[-61057639,-134585262],[-61014451,-134294047],[-61000000,-134000000],[-61000000,-107999999],[-61014451,-107705944],[-61057639,-107414736],[-61129179,-107129152],[-61228359,-106851953],[-61354239,-106585808],[-61505591,-106333288],[-61680967,-106096816],[-61878675,-105878680],[-62096820,-105680967],[-62138204,-105650279],[-62333292,-105505591],[-62585808,-105354239],[-62632384,-105332207],[-62851951,-105228360],[-62900463,-105211008],[-63129152,-105129183],[-63414731,-105057640],[-63705947,-105014448],[-63999999,-105000000],[-68999369,-105000000],[-69012543,-104731792],[-69014392,-104706544],[-69017352,-104682119],[-69053079,-104441248],[-69057104,-104417487],[-69066008,-104379383],[-69125528,-104141799],[-69137111,-104105248],[-69220007,-103873544],[-69234136,-103838591],[-69339920,-103614943],[-69356415,-103581784],[-69484328,-103368367],[-69512143,-103323752],[-69661647,-103122160],[-69693352,-103082399],[-69863383,-102894800],[-69878680,-102878679],[-70066999,-102707992],[-70112656,-102668576],[-70314648,-102518775],[-70368367,-102484336],[-70581783,-102356424],[-70806711,-102250040],[-70871040,-102220919],[-71102823,-102137992],[-71139752,-102126095],[-71377383,-102066568],[-71417487,-102057104],[-71441248,-102053079],[-71682119,-102017352],[-71706535,-102014392],[-71731784,-102012543],[-71974456,-102000624],[-71999999,-102000000],[-104000000,-102000000],[-104025536,-102000624],[-104268207,-102012543],[-104293455,-102014392],[-104317880,-102017352],[-104558751,-102053079],[-104582512,-102057104],[-104620616,-102066008],[-104858200,-102125528],[-104894751,-102137111],[-105126455,-102220007],[-105161408,-102234136],[-105385056,-102339920],[-105418215,-102356415],[-105631632,-102484328],[-105676247,-102512143],[-105877839,-102661647],[-105917600,-102693352],[-106105199,-102863383],[-106121320,-102878680],[-106292007,-103066999],[-106331424,-103112656],[-106481224,-103314648],[-106515663,-103368367],[-106643575,-103581783],[-106749959,-103806711],[-106779080,-103871040],[-106862007,-104102823],[-106873904,-104139752],[-106933431,-104377383],[-106942896,-104417487],[-106946920,-104441248],[-106982648,-104682119],[-106985607,-104706535],[-106987456,-104731784],[-107000630,-105000000],[-112000000,-105000000],[-112294056,-105014448],[-112585264,-105057640],[-112870848,-105129184],[-112919359,-105146535],[-113148048,-105228360],[-113194624,-105250392],[-113414191,-105354239],[-113666711,-105505591],[-113708095,-105536279],[-113903183,-105680967],[-114121320,-105878679],[-114319032,-106096816],[-114349720,-106138200],[-114494408,-106333288],[-114645760,-106585808],[-114667792,-106632384],[-114771640,-106851952],[-114788991,-106900463],[-114870815,-107129151],[-114942359,-107414735],[-114985551,-107705943],[-115000000,-107999999],[-115000000,-134000000],[-114985551,-134294048],[-114942359,-134585263],[-114870816,-134870847],[-114853464,-134919359],[-114771639,-135148064],[-114645759,-135414192],[-114494407,-135666720],[-114319031,-135903184],[-114121320,-136121327],[-114083144,-136155919],[-113903184,-136319023],[-113861799,-136349712],[-113666711,-136494416],[-113458383,-136619264],[-113414192,-136645743],[-113148049,-136771631],[-112870848,-136870815],[-112820872,-136883327],[-112585264,-136942351],[-112534303,-136949920],[-112294056,-136985551],[-112000000,-137000000],[-107000630,-137000000],[-106987456,-137268207],[-106985608,-137293440],[-106982647,-137317872],[-106946920,-137558751],[-106942896,-137582511],[-106933991,-137620624],[-106874471,-137858208],[-106862888,-137894751],[-106779992,-138126463],[-106765863,-138161424],[-106660080,-138385055],[-106643584,-138418223],[-106515671,-138631648],[-106487855,-138676256],[-106338352,-138877839],[-106306647,-138917600],[-106136616,-139105199],[-106121320,-139121328],[-105933000,-139291999],[-105887344,-139331407],[-105685351,-139481232],[-105631632,-139515663],[-105418216,-139643567],[-105193288,-139749951],[-105128959,-139779072],[-104897175,-139862016],[-104860247,-139873904],[-104622616,-139933423],[-104582511,-139942896],[-104558751,-139946912],[-104317880,-139982656],[-104293463,-139985616],[-104268216,-139987456],[-104025544,-139999376],[-104000000,-140000000],[-71999999,-140000000]],[[-105000000,-138000000],[-105000000,-104000000],[-71000000,-104000000],[-71000000,-138000000]],[[-69000000,-132000000],[-69000000,-110000000],[-64991180,-110000000],[-64991180,-132000000]],[[-111008824,-132000000],[-111008824,-110000000],[-107000000,-110000000],[-107000000,-132000000]]); my $object = $print->print->objects->[0]; $object->slice; my $layer = $object->get_layer(1); my $layerm = $layer->regions->[0]; $layerm->slices->clear; $layerm->slices->append(Slic3r::Surface->new(surface_type => S_TYPE_INTERNAL, expolygon => $expolygon)); # make perimeters $layer->make_perimeters; # compute the covered area my $pflow = $layerm->flow(FLOW_ROLE_PERIMETER); my $iflow = $layerm->flow(FLOW_ROLE_INFILL); my $covered_by_perimeters = union_ex([ (map @{$_->polygon->split_at_first_point->grow($pflow->scaled_width/2)}, map @$_, @{$layerm->perimeters}), ]); my $covered_by_infill = union_ex([ (map $_->p, @{$layerm->fill_surfaces}), (map @{$_->polyline->grow($iflow->scaled_width/2)}, @{$layerm->thin_fills}), ]); # compute the non covered area my $non_covered = diff( [ map @{$_->expolygon}, @{$layerm->slices} ], [ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ], ); if (0) { printf "max non covered = %f\n", List::Util::max(map unscale unscale $_->area, @$non_covered); require "Slic3r/SVG.pm"; Slic3r::SVG::output( "gaps.svg", expolygons => [ map $_->expolygon, @{$layerm->slices} ], red_expolygons => union_ex([ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ]), green_expolygons => union_ex($non_covered), no_arrows => 1, polylines => [ map $_->polygon->split_at_first_point, map @$_, @{$layerm->perimeters}, ], ); } ok !(defined first { $_->area > ($iflow->scaled_width**2) } @$non_covered), 'no gap between perimeters and infill'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 3); $config->set('layer_height', 0.4); $config->set('bridge_speed', 99); $config->set('fill_density', 0); # to prevent bridging over sparse infill $config->set('overhangs', 1); $config->set('cooling', [ 0 ]); # to prevent speeds from being altered $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered my $test = sub { my ($print) = @_; my %z_with_bridges = (); # z => 1 Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0) { $z_with_bridges{$self->Z} = 1 if ($args->{F} // $self->F) == $config->bridge_speed*60; } }); return scalar keys %z_with_bridges; }; ok $test->(Slic3r::Test::init_print('V', config => $config)) == 1, 'no overhangs printed with bridge speed'; # except for the first internal solid layers above void ok $test->(Slic3r::Test::init_print('V', config => $config, scale_xyz => [3,1,1])) > 1, 'overhangs printed with bridge speed'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('seam_position', 'random'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok Slic3r::Test::gcode($print), 'successful generation of G-code with seam_position = random'; } { my $test = sub { my ($model_name) = @_; my $config = Slic3r::Config::new_from_defaults; $config->set('seam_position', 'aligned'); $config->set('skirts', 0); $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); $config->set('retract_layer_change', [0]); my $was_extruding = 0; my @seam_points = (); my $print = Slic3r::Test::init_print($model_name, config => $config); Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding}) { if (!$was_extruding) { push @seam_points, Slic3r::Point->new_scale($self->X, $self->Y); } $was_extruding = 1; } else { $was_extruding = 0; } }); my @dist = map unscale($_), map $seam_points[$_]->distance_to($seam_points[$_+1]), 0..($#seam_points-1); ok !(defined first { $_ > 3 } @dist), 'seam is aligned'; }; $test->('20mm_cube'); $test->('small_dorito'); } __END__ Slic3r-version_1.39.1/t/polyclip.t000066400000000000000000000145641324354444700170200ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan tests => 18; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; use Slic3r::Geometry::Clipper qw(intersection_pl); #========================================================== is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 10), Slic3r::Line->new([5, 10], [20, 10])), 1, 'point in horizontal segment'; is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(30, 10), Slic3r::Line->new([5, 10], [20, 10])), 0, 'point not in horizontal segment'; is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 10), Slic3r::Line->new([10, 5], [10, 20])), 1, 'point in vertical segment'; is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(10, 30), Slic3r::Line->new([10, 5], [10, 20])), 0, 'point not in vertical segment'; is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(15, 15), Slic3r::Line->new([10, 10], [20, 20])), 1, 'point in diagonal segment'; is Slic3r::Geometry::point_in_segment(Slic3r::Point->new(20, 15), Slic3r::Line->new([10, 10], [20, 20])), 0, 'point not in diagonal segment'; #========================================================== my $square = Slic3r::Polygon->new( # ccw [100, 100], [200, 100], [200, 200], [100, 200], ); #========================================================== { my $hole_in_square = [ # cw [140, 140], [140, 160], [160, 160], [160, 140], ]; my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); #is $expolygon->contains_point(Slic3r::Point->new(100, 100)), 1, 'corner point is recognized'; #is $expolygon->contains_point(Slic3r::Point->new(100, 180)), 1, 'point on contour is recognized'; #is $expolygon->contains_point(Slic3r::Point->new(140, 150)), 1, 'point on hole contour is recognized'; #is $expolygon->contains_point(Slic3r::Point->new(140, 140)), 1, 'point on hole corner is recognized'; { my $intersection = intersection_pl([Slic3r::Polyline->new([150,180], [150,150])], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([150, 180], [150, 160])->length, 'line is clipped to square with hole'; } { my $intersection = intersection_pl([Slic3r::Polyline->new([150,150], [150,120])], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([150, 140], [150, 120])->length, 'line is clipped to square with hole'; } { my $intersection = intersection_pl([Slic3r::Polyline->new([120,180], [180,180])], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([120,180], [180,180])->length, 'line is clipped to square with hole'; } { my $intersection = intersection_pl([Slic3r::Polyline->new([50, 150], [300, 150])], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([100, 150], [140, 150])->length, 'line is clipped to square with hole'; is $intersection->[1]->length, Slic3r::Line->new([160, 150], [200, 150])->length, 'line is clipped to square with hole'; } { my $intersection = intersection_pl([Slic3r::Polyline->new([300, 150], [50, 150])], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([200, 150], [160, 150])->length, 'reverse line is clipped to square with hole'; is $intersection->[1]->length, Slic3r::Line->new([140, 150], [100, 150])->length, 'reverse line is clipped to square with hole'; } { my $intersection = intersection_pl([Slic3r::Polyline->new([100,180], [200,180])], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([100,180], [200,180])->length, 'tangent line is clipped to square with hole'; } } #========================================================== { my $large_circle = Slic3r::Polygon->new_scale( # ccw [151.8639,288.1192], [133.2778,284.6011], [115.0091,279.6997], [98.2859,270.8606], [82.2734,260.7933], [68.8974,247.4181], [56.5622,233.0777], [47.7228,216.3558], [40.1617,199.0172], [36.6431,180.4328], [34.932,165.2312], [37.5567,165.1101], [41.0547,142.9903], [36.9056,141.4295], [40.199,124.1277], [47.7776,106.7972], [56.6335,90.084], [68.9831,75.7557], [82.3712,62.3948], [98.395,52.3429], [115.1281,43.5199], [133.4004,38.6374], [151.9884,35.1378], [170.8905,35.8571], [189.6847,37.991], [207.5349,44.2488], [224.8662,51.8273], [240.0786,63.067], [254.407,75.4169], [265.6311,90.6406], [275.6832,106.6636], [281.9225,124.52], [286.8064,142.795], [287.5061,161.696], [286.7874,180.5972], [281.8856,198.8664], [275.6283,216.7169], [265.5604,232.7294], [254.3211,247.942], [239.9802,260.2776], [224.757,271.5022], [207.4179,279.0635], [189.5605,285.3035], [170.7649,287.4188], ); ok $large_circle->is_counter_clockwise, "contour is counter-clockwise"; my $small_circle = Slic3r::Polygon->new_scale( # cw [158.227,215.9007], [164.5136,215.9007], [175.15,214.5007], [184.5576,210.6044], [190.2268,207.8743], [199.1462,201.0306], [209.0146,188.346], [213.5135,177.4829], [214.6979,168.4866], [216.1025,162.3325], [214.6463,151.2703], [213.2471,145.1399], [209.0146,134.9203], [199.1462,122.2357], [189.8944,115.1366], [181.2504,111.5567], [175.5684,108.8205], [164.5136,107.3655], [158.2269,107.3655], [147.5907,108.7656], [138.183,112.6616], [132.5135,115.3919], [123.5943,122.2357], [113.7259,134.92], [109.2269,145.7834], [108.0426,154.7799], [106.638,160.9339], [108.0941,171.9957], [109.4933,178.1264], [113.7259,188.3463], [123.5943,201.0306], [132.8461,208.1296], [141.4901,211.7094], [147.172,214.4458], ); ok $small_circle->is_clockwise, "hole is clockwise"; my $expolygon = Slic3r::ExPolygon->new($large_circle, $small_circle); my $line = Slic3r::Polyline->new_scale([152.742,288.086671142818], [152.742,34.166466971035]); my $intersection = intersection_pl([$line], \@$expolygon); is $intersection->[0]->length, Slic3r::Line->new([152742000, 288086661], [152742000, 215178843])->length, 'line is clipped to square with hole'; is $intersection->[1]->length, Slic3r::Line->new([152742000, 108087507], [152742000, 35166477])->length, 'line is clipped to square with hole'; } #========================================================== Slic3r-version_1.39.1/t/print.t000066400000000000000000000045361324354444700163170ustar00rootroot00000000000000use Test::More tests => 6; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Geometry qw(unscale X Y); use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; my $print_center = [100,100]; my $print = Slic3r::Test::init_print('20mm_cube', config => $config, print_center => $print_center); my @extrusion_points = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { push @extrusion_points, my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y}); } }); my $bb = Slic3r::Geometry::BoundingBox->new_from_points(\@extrusion_points); my $center = $bb->center; ok abs(unscale($center->[X]) - $print_center->[X]) < 0.005, 'print is centered around print_center (X)'; ok abs(unscale($center->[Y]) - $print_center->[Y]) < 0.005, 'print is centered around print_center (Y)'; } { # this represents the aggregate config from presets my $config = Slic3r::Config::new_from_defaults; # user adds one object to the plater my $print = Slic3r::Test::init_print(my $model = Slic3r::Test::model('20mm_cube'), config => $config); # user sets a per-region option $print->print->objects->[0]->model_object->config->set('fill_density', 100); $print->print->reload_object(0); is $print->print->regions->[0]->config->fill_density, 100, 'region config inherits model object config'; # user exports G-code, thus the default config is reapplied $print->print->apply_config($config); is $print->print->regions->[0]->config->fill_density, 100, 'apply_config() does not override per-object settings'; # user assigns object extruders $print->print->objects->[0]->model_object->config->set('extruder', 3); $print->print->objects->[0]->model_object->config->set('perimeter_extruder', 2); $print->print->reload_object(0); is $print->print->regions->[0]->config->infill_extruder, 3, 'extruder setting is correctly expanded'; is $print->print->regions->[0]->config->perimeter_extruder, 2, 'extruder setting does not override explicitely specified extruders'; } __END__ Slic3r-version_1.39.1/t/retraction.t000066400000000000000000000235671324354444700173420ustar00rootroot00000000000000use Test::More tests => 26; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(any); use Slic3r; use Slic3r::Test qw(_eq); { my $config = Slic3r::Config::new_from_defaults; my $duplicate = 1; my $test = sub { my ($conf) = @_; $conf ||= $config; my $print = Slic3r::Test::init_print('20mm_cube', config => $conf, duplicate => $duplicate); my $tool = 0; my @toolchange_count = (); # track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time my @retracted = (1); # ignore the first travel move from home to first point my @retracted_length = (0); my $lifted = 0; my $lift_dist = 0; # track lifted distance for toolchanges and extruders with different retract_lift values my $changed_tool = 0; my $wait_for_toolchange = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; $changed_tool = 1; $wait_for_toolchange = 0; $toolchange_count[$tool] //= 0; $toolchange_count[$tool]++; } elsif ($cmd =~ /^G[01]$/ && !$args->{Z}) { # ignore lift taking place after retraction fail 'toolchange happens right after retraction' if $wait_for_toolchange; } if ($info->{dist_Z}) { # lift move or lift + change layer if (_eq($info->{dist_Z}, $print->print->config->get_at('retract_lift', $tool)) || (_eq($info->{dist_Z}, $conf->layer_height + $print->print->config->get_at('retract_lift', $tool)) && $print->print->config->get_at('retract_lift', $tool) > 0)) { fail 'only lifting while retracted' if !$retracted[$tool]; fail 'double lift' if $lifted; $lifted = 1; $lift_dist = $info->{dist_Z}; } if ($info->{dist_Z} < 0) { fail 'going down only after lifting' if !$lifted; fail 'going down by the same amount of the lift or by the amount needed to get to next layer' if !_eq($info->{dist_Z}, -$lift_dist) && !_eq($info->{dist_Z}, -lift_dist + $conf->layer_height); $lift_dist = 0; $lifted = 0; } fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60; } if ($info->{retracting}) { $retracted[$tool] = 1; $retracted_length[$tool] += -$info->{dist_E}; if (_eq($retracted_length[$tool], $print->print->config->get_at('retract_length', $tool))) { # okay } elsif (_eq($retracted_length[$tool], $print->print->config->get_at('retract_length_toolchange', $tool))) { $wait_for_toolchange = 1; } else { fail 'retracted by the correct amount'; } } if ($info->{extruding}) { fail 'only extruding while not lifted' if $lifted; if ($retracted[$tool]) { my $expected_amount = $retracted_length[$tool] + $print->print->config->get_at('retract_restart_extra', $tool); if ($changed_tool && $toolchange_count[$tool] > 1) { $expected_amount = $print->print->config->get_at('retract_length_toolchange', $tool) + $print->print->config->get_at('retract_restart_extra_toolchange', $tool); $changed_tool = 0; } fail 'unretracted by the correct amount' && exit if !_eq($info->{dist_E}, $expected_amount); $retracted[$tool] = 0; $retracted_length[$tool] = 0; } } if ($info->{travel} && $info->{dist_XY} >= $print->print->config->get_at('retract_before_travel', $tool)) { fail 'retracted before long travel move' if !$retracted[$tool]; } }); 1; }; $config->set('first_layer_height', $config->layer_height); $config->set('first_layer_speed', '100%'); $config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code $config->set('retract_length', [1.5]); $config->set('retract_before_travel', [3]); $config->set('only_retract_when_crossing_perimeters', 0); my $retract_tests = sub { my ($descr) = @_; ok $test->(), "retraction$descr"; my $conf = $config->clone; $conf->set('retract_restart_extra', [1]); ok $test->($conf), "restart extra length$descr"; $conf->set('retract_restart_extra', [-1]); ok $test->($conf), "negative restart extra length$descr"; $conf->set('retract_lift', [1, 2]); ok $test->($conf), "lift$descr"; }; $retract_tests->(''); $duplicate = 2; $retract_tests->(' (duplicate)'); $duplicate = 1; $config->set('infill_extruder', 2); $config->set('skirts', 4); $config->set('skirt_height', 3); $retract_tests->(' (dual extruder with multiple skirt layers)'); } { my $config = Slic3r::Config::new_from_defaults; $config->set('start_gcode', ''); # prevent any default priming Z move from affecting our lift detection $config->set('retract_length', [0]); $config->set('retract_layer_change', [0]); $config->set('retract_lift', [0.2]); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $retracted = 0; my $layer_changes_with_retraction = 0; my $retractions = my $z_restores = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{retracting}) { $retracted = 1; $retractions++; } elsif ($info->{extruding} && $retracted) { $retracted = 0; } if ($info->{dist_Z} && $retracted) { $layer_changes_with_retraction++; } if ($info->{dist_Z} && $args->{Z} < $self->Z) { $z_restores++; } }); is $layer_changes_with_retraction, 0, 'no retraction on layer change'; is $retractions, 0, 'no retractions'; is $z_restores, 0, 'no lift'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('use_firmware_retraction', 1); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $retracted = 0; my $double_retractions = my $double_unretractions = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G10') { $double_retractions++ if $retracted; $retracted = 1; } elsif ($cmd eq 'G11') { $double_unretractions++ if !$retracted; $retracted = 0; } }); is $double_retractions, 0, 'no double retractions'; is $double_unretractions, 0, 'no double unretractions'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('use_firmware_retraction', 1); $config->set('retract_length', [0]); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $retracted = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G10') { $retracted = 1; } }); ok $retracted, 'retracting also when --retract-length is 0 but --use-firmware-retraction is enabled'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('start_gcode', ''); $config->set('retract_lift', [3, 4]); my @lifted_at = (); my $test = sub { my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); @lifted_at = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && $info->{dist_Z} < 0) { push @lifted_at, $info->{new_Z}; } }); }; $config->set('retract_lift_above', [0, 0]); $config->set('retract_lift_below', [0, 0]); $test->(); ok !!@lifted_at, 'lift takes place when above/below == 0'; $config->set('retract_lift_above', [5, 6]); $config->set('retract_lift_below', [15, 13]); $test->(); ok !!@lifted_at, 'lift takes place when above/below != 0'; ok !(any { $_ < $config->get_at('retract_lift_above', 0) } @lifted_at), 'Z is not lifted below the configured value'; ok !(any { $_ > $config->get_at('retract_lift_below', 0) } @lifted_at), 'Z is not lifted above the configured value'; # check lifting with different values for 2. extruder $config->set('perimeter_extruder', 2); $config->set('infill_extruder', 2); $config->set('retract_lift_above', [0, 0]); $config->set('retract_lift_below', [0, 0]); $test->(); ok !!@lifted_at, 'lift takes place when above/below == 0 for 2. extruder'; $config->set('retract_lift_above', [5, 6]); $config->set('retract_lift_below', [15, 13]); $test->(); ok !!@lifted_at, 'lift takes place when above/below != 0 for 2. extruder'; ok !(any { $_ < $config->get_at('retract_lift_above', 1) } @lifted_at), 'Z is not lifted below the configured value for 2. extruder'; ok !(any { $_ > $config->get_at('retract_lift_below', 1) } @lifted_at), 'Z is not lifted above the configured value for 2. extruder'; } __END__Slic3r-version_1.39.1/t/shells.t000066400000000000000000000327021324354444700164510ustar00rootroot00000000000000use Test::More tests => 21; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first sum); use Slic3r; use Slic3r::Geometry qw(epsilon); use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 0); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); $config->set('bridge_speed', 72); $config->set('first_layer_speed', '100%'); $config->set('cooling', [ 0 ]); my $test = sub { my ($conf) = @_; $conf ||= $config; my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my %z = (); # Z => 1 my %layers_with_solid_infill = (); # Z => $count my %layers_with_bridge_infill = (); # Z => $count Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($self->Z > 0) { $z{ $self->Z } = 1; if ($info->{extruding} && $info->{dist_XY} > 0) { my $F = $args->{F} // $self->F; $layers_with_solid_infill{$self->Z} = 1 if $F == $config->solid_infill_speed*60; $layers_with_bridge_infill{$self->Z} = 1 if $F == $config->bridge_speed*60; } } }); my @z = sort { $a <=> $b } keys %z; my @shells = map $layers_with_solid_infill{$_} || $layers_with_bridge_infill{$_}, @z; fail "insufficient number of bottom solid layers" unless !defined(first { !$_ } @shells[0..$config->bottom_solid_layers-1]); fail "excessive number of bottom solid layers" unless scalar(grep $_, @shells[0 .. $#shells/2]) == $config->bottom_solid_layers; fail "insufficient number of top solid layers" unless !defined(first { !$_ } @shells[-$config->top_solid_layers..-1]); fail "excessive number of top solid layers" unless scalar(grep $_, @shells[($#shells/2)..$#shells]) == $config->top_solid_layers; if ($config->top_solid_layers > 0) { fail "unexpected solid infill speed in first solid layer over sparse infill" if $layers_with_solid_infill{ $z[-$config->top_solid_layers] }; die "bridge speed not used in first solid layer over sparse infill" if !$layers_with_bridge_infill{ $z[-$config->top_solid_layers] }; } 1; }; $config->set('top_solid_layers', 3); $config->set('bottom_solid_layers', 3); ok $test->(), "proper number of shells is applied"; $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); ok $test->(), "no shells are applied when both top and bottom are set to zero"; $config->set('perimeters', 1); $config->set('top_solid_layers', 3); $config->set('bottom_solid_layers', 3); $config->set('fill_density', 0); ok $test->(), "proper number of shells is applied even when fill density is none"; } # issue #1161 { my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.3); $config->set('first_layer_height', '100%'); $config->set('bottom_solid_layers', 0); $config->set('top_solid_layers', 3); $config->set('cooling', [ 0 ]); $config->set('bridge_speed', 99); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); $config->set('first_layer_speed', '100%'); my $print = Slic3r::Test::init_print('V', config => $config); my %layers_with_solid_infill = (); # Z => 1 Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; $layers_with_solid_infill{$self->Z} = 1 if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; }); is scalar(map $layers_with_solid_infill{$_}, grep $_ <= 7.2, keys %layers_with_solid_infill), 3, "correct number of top solid shells is generated in V-shaped object"; } { my $config = Slic3r::Config::new_from_defaults; # we need to check against one perimeter because this test is calibrated # (shape, extrusion_width) so that perimeters cover the bottom surfaces of # their lower layer - the test checks that shells are not generated on the # above layers (thus 'across' the shadow perimeter) # the test is actually calibrated to leave a narrow bottom region for each # layer - we test that in case of fill_density = 0 such narrow shells are # discarded instead of grown $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('cooling', [ 0 ]); # prevent speed alteration $config->set('first_layer_speed', '100%'); # prevent speed alteration $config->set('layer_height', 0.4); $config->set('first_layer_height', '100%'); $config->set('extrusion_width', 0.55); $config->set('bottom_solid_layers', 3); $config->set('top_solid_layers', 0); $config->set('solid_infill_speed', 99); my $print = Slic3r::Test::init_print('V', config => $config); my %layers = (); # Z => 1 Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; $layers{$self->Z} = 1 if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; }); is scalar(keys %layers), $config->bottom_solid_layers, "shells are not propagated across perimeters of the neighbor layer"; } { my $config = Slic3r::Config::new_from_defaults; $config->set('perimeters', 3); $config->set('cooling', [ 0 ]); # prevent speed alteration $config->set('first_layer_speed', '100%'); # prevent speed alteration $config->set('layer_height', 0.4); $config->set('first_layer_height', '100%'); $config->set('bottom_solid_layers', 3); $config->set('top_solid_layers', 3); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); $config->set('bridge_speed', 99); my $print = Slic3r::Test::init_print('sloping_hole', config => $config); my %solid_layers = (); # Z => 1 Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; $solid_layers{$self->Z} = 1 if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; }); is scalar(keys %solid_layers), $config->bottom_solid_layers + $config->top_solid_layers, "no superfluous shells are generated"; } { my $config = Slic3r::Config::new_from_defaults; $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('top_solid_layers', 0); $config->set('spiral_vase', 1); $config->set('bottom_solid_layers', 0); $config->set('skirts', 0); $config->set('first_layer_height', '100%'); $config->set('start_gcode', ''); $config->set('temperature', [200]); $config->set('first_layer_temperature', [205]); # TODO: this needs to be tested with a model with sloping edges, where starting # points of each layer are not aligned - in that case we would test that no # travel moves are left to move to the new starting point - in a cube, end # points coincide with next layer starting points (provided there's no clipping) my $test = sub { my ($model_name, $description) = @_; my $print = Slic3r::Test::init_print($model_name, config => $config); my $travel_moves_after_first_extrusion = 0; my $started_extruding = 0; my $first_layer_temperature_set = 0; my $temperature_set = 0; my @z_steps = (); Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1') { $started_extruding = 1 if $info->{extruding}; push @z_steps, $info->{dist_Z} if $started_extruding && $info->{dist_Z} > 0; $travel_moves_after_first_extrusion++ if $info->{travel} && $info->{dist_XY} > 0 && $started_extruding && !exists $args->{Z}; } elsif ($cmd eq 'M104') { $first_layer_temperature_set = 1 if $args->{S} == 205; $temperature_set = 1 if $args->{S} == 200; } }); ok $first_layer_temperature_set, 'first layer temperature is preserved'; ok $temperature_set, 'temperature is preserved'; # we allow one travel move after first extrusion: i.e. when moving to the first # spiral point after moving to second layer (bottom layer had loop clipping, so # we're slightly distant from the starting point of the loop) ok $travel_moves_after_first_extrusion <= 1, "no gaps in spiral vase ($description)"; ok !(grep { $_ > $config->layer_height + epsilon } @z_steps), "no gaps in Z ($description)"; }; $test->('20mm_cube', 'solid model'); $config->set('z_offset', -10); $test->('20mm_cube', 'solid model with negative z-offset'); ### Disabled because the current unreliable medial axis code doesn't ### always produce valid loops. ###$test->('40x10', 'hollow model with negative z-offset'); } { my $config = Slic3r::Config::new_from_defaults; $config->set('spiral_vase', 1); $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); $config->set('retract_layer_change', [0]); $config->set('skirts', 0); $config->set('first_layer_height', '100%'); $config->set('layer_height', 0.4); $config->set('start_gcode', ''); $config->validate; my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $z_moves = 0; my @this_layer = (); # [ dist_Z, dist_XY ], ... my $bottom_layer_not_flat = 0; my $null_z_moves_not_layer_changes = 0; my $null_z_moves_not_multiples_of_layer_height = 0; my $sum_of_partial_z_equals_to_layer_height = 0; my $all_layer_segments_have_same_slope = 0; my $horizontal_extrusions = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1') { if ($z_moves < 2) { # skip everything up to the second Z move # (i.e. start of second layer) if (exists $args->{Z}) { $z_moves++; $bottom_layer_not_flat = 1 if $info->{dist_Z} > 0 && $info->{dist_Z} != $config->layer_height; } } elsif ($info->{dist_Z} == 0 && $args->{Z}) { $null_z_moves_not_layer_changes = 1 if $info->{dist_XY} != 0; # % doesn't work easily with floats $null_z_moves_not_multiples_of_layer_height = 1 if abs(($args->{Z} / $config->layer_height) * $config->layer_height - $args->{Z}) > epsilon; my $total_dist_XY = sum(map $_->[1], @this_layer); $sum_of_partial_z_equals_to_layer_height = 1 if abs(sum(map $_->[0], @this_layer) - $config->layer_height) > epsilon; foreach my $segment (@this_layer) { # check that segment's dist_Z is proportioned to its dist_XY $all_layer_segments_have_same_slope = 1 if abs($segment->[0]*$total_dist_XY/$config->layer_height - $segment->[1]) > 0.2; } @this_layer = (); } elsif ($info->{extruding} && $info->{dist_XY} > 0) { $horizontal_extrusions = 1 if $info->{dist_Z} == 0; push @this_layer, [ $info->{dist_Z}, $info->{dist_XY} ]; } } }); ok !$bottom_layer_not_flat, 'bottom layer is flat when using spiral vase'; ok !$null_z_moves_not_layer_changes, 'null Z moves are layer changes'; ok !$null_z_moves_not_multiples_of_layer_height, 'null Z moves are multiples of layer height'; ok !$sum_of_partial_z_equals_to_layer_height, 'sum of partial Z increments equals to a full layer height'; ok !$all_layer_segments_have_same_slope, 'all layer segments have the same slope'; ok !$horizontal_extrusions, 'no horizontal extrusions'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('top_solid_layers', 0); $config->set('spiral_vase', 1); $config->set('bottom_solid_layers', 0); $config->set('skirts', 0); $config->set('first_layer_height', '100%'); $config->set('start_gcode', ''); my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config); my $diagonal_moves = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1') { if ($info->{extruding} && $info->{dist_XY} > 0) { if ($info->{dist_Z} > 0) { $diagonal_moves++; } } } }); is $diagonal_moves, 0, 'no spiral moves on two-island object'; } __END__ Slic3r-version_1.39.1/t/skirt_brim.t000066400000000000000000000125651324354444700173310ustar00rootroot00000000000000use Test::More tests => 6; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Geometry qw(unscale convex_hull); use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 1); $config->set('skirt_height', 2); $config->set('perimeters', 0); $config->set('support_material_speed', 99); $config->set('cooling', [ 0 ]); # to prevent speeds to be altered $config->set('first_layer_speed', '100%'); # to prevent speeds to be altered my $test = sub { my ($conf) = @_; $conf ||= $config; my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config); my %layers_with_skirt = (); # Z => $count Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if (defined $self->Z) { $layers_with_skirt{$self->Z} //= 0; $layers_with_skirt{$self->Z} = 1 if $info->{extruding} && ($args->{F} // $self->F) == $config->support_material_speed*60; } }); fail "wrong number of layers with skirt" unless (grep $_, values %layers_with_skirt) == $config->skirt_height; }; ok $test->(), "skirt_height is honored when printing multiple objects too"; } { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('perimeters', 0); $config->set('top_solid_layers', 0); # to prevent solid shells and their speeds $config->set('bottom_solid_layers', 0); # to prevent solid shells and their speeds $config->set('brim_width', 5); $config->set('support_material_speed', 99); $config->set('cooling', [ 0 ]); # to prevent speeds to be altered $config->set('first_layer_speed', '100%'); # to prevent speeds to be altered my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my %layers_with_brim = (); # Z => $count Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if (defined $self->Z) { $layers_with_brim{$self->Z} //= 0; $layers_with_brim{$self->Z} = 1 if $info->{extruding} && $info->{dist_XY} > 0 && ($args->{F} // $self->F) != $config->infill_speed*60; } }); is scalar(grep $_, values %layers_with_brim), 1, "brim is generated"; } { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 1); $config->set('brim_width', 10); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok Slic3r::Test::gcode($print), 'successful G-code generation when skirt is smaller than brim width'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 1); $config->set('skirt_height', 0); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok Slic3r::Test::gcode($print), 'successful G-code generation when skirt_height = 0 and skirts > 0'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.4); $config->set('skirts', 1); $config->set('skirt_distance', 0); $config->set('support_material_speed', 99); $config->set('perimeter_extruder', 1); $config->set('support_material_extruder', 2); $config->set('cooling', [ 0 ]); # to prevent speeds to be altered $config->set('first_layer_speed', '100%'); # to prevent speeds to be altered my $print = Slic3r::Test::init_print('overhang', config => $config); $print->process; # we enable support material after skirt has been generated $config->set('support_material', 1); $print->apply_config($config); my $skirt_length = 0; my @extrusion_points = (); my $tool = undef; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif (defined $self->Z && $self->Z == $config->first_layer_height) { # we're on first layer if ($info->{extruding} && $info->{dist_XY} > 0) { my $speed = ($args->{F} // $self->F) / 60; if ($speed == $config->support_material_speed && $tool == $config->perimeter_extruder-1) { # skirt uses support material speed but first object's extruder $skirt_length += $info->{dist_XY}; } else { push @extrusion_points, my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y}); } } } }); my $convex_hull = convex_hull(\@extrusion_points); my $hull_perimeter = unscale($convex_hull->split_at_first_point->length); ok $skirt_length > $hull_perimeter, 'skirt lenght is large enough to contain object with support'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('min_skirt_length', 20); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok Slic3r::Test::gcode($print), 'no crash when using min_skirt_length'; } __END__ Slic3r-version_1.39.1/t/slice.t000066400000000000000000000112741324354444700162570ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan skip_all => 'temporarily disabled'; plan tests => 16; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } # temporarily disable compilation errors due to constant not being exported anymore sub Slic3r::TriangleMesh::I_B {} sub Slic3r::TriangleMesh::I_FACET_EDGE {} sub Slic3r::TriangleMesh::FE_BOTTOM { sub Slic3r::TriangleMesh::FE_TOP {}} use Slic3r; use Slic3r::Geometry qw(X Y Z); my @lines; my $z = 20; my @points = ([3, 4], [8, 5], [1, 9]); # XY coordinates of the facet vertices # NOTE: # the first point of the intersection lines is replaced by -1 because TriangleMesh.pm # is saving memory and doesn't store point A anymore since it's not actually needed. # We disable this test because intersect_facet() now assumes we never feed a horizontal # facet to it. # is_deeply lines(20, 20, 20), [ # [ -1, $points[1] ], # $points[0] # [ -1, $points[2] ], # $points[1] # [ -1, $points[0] ], # $points[2] # ], 'horizontal'; is_deeply lines(22, 20, 20), [ [ -1, $points[2] ] ], 'lower edge on layer'; # $points[1] is_deeply lines(20, 20, 22), [ [ -1, $points[1] ] ], 'lower edge on layer'; # $points[0] is_deeply lines(20, 22, 20), [ [ -1, $points[0] ] ], 'lower edge on layer'; # $points[2] is_deeply lines(20, 20, 10), [ [ -1, $points[0] ] ], 'upper edge on layer'; # $points[1] is_deeply lines(10, 20, 20), [ [ -1, $points[1] ] ], 'upper edge on layer'; # $points[2] is_deeply lines(20, 10, 20), [ [ -1, $points[2] ] ], 'upper edge on layer'; # $points[0] is_deeply lines(20, 15, 10), [ ], 'upper vertex on layer'; is_deeply lines(28, 20, 30), [ ], 'lower vertex on layer'; { my @z = (24, 10, 16); is_deeply lines(@z), [ [ -1, # line_plane_intersection([ vertices(@z)->[0], vertices(@z)->[1] ]), line_plane_intersection([ vertices(@z)->[2], vertices(@z)->[0] ]), ] ], 'two edges intersect'; } { my @z = (16, 24, 10); is_deeply lines(@z), [ [ -1, # line_plane_intersection([ vertices(@z)->[1], vertices(@z)->[2] ]), line_plane_intersection([ vertices(@z)->[0], vertices(@z)->[1] ]), ] ], 'two edges intersect'; } { my @z = (10, 16, 24); is_deeply lines(@z), [ [ -1, # line_plane_intersection([ vertices(@z)->[2], vertices(@z)->[0] ]), line_plane_intersection([ vertices(@z)->[1], vertices(@z)->[2] ]), ] ], 'two edges intersect'; } { my @z = (24, 10, 20); is_deeply lines(@z), [ [ -1, # line_plane_intersection([ vertices(@z)->[0], vertices(@z)->[1] ]), $points[2], ] ], 'one vertex on plane and one edge intersects'; } { my @z = (10, 20, 24); is_deeply lines(@z), [ [ -1, # line_plane_intersection([ vertices(@z)->[2], vertices(@z)->[0] ]), $points[1], ] ], 'one vertex on plane and one edge intersects'; } { my @z = (20, 24, 10); is_deeply lines(@z), [ [ -1, # line_plane_intersection([ vertices(@z)->[1], vertices(@z)->[2] ]), $points[0], ] ], 'one vertex on plane and one edge intersects'; } my @lower = intersect(22, 20, 20); my @upper = intersect(20, 20, 10); is $lower[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_BOTTOM, 'bottom edge on layer'; is $upper[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_TOP, 'upper edge on layer'; my $mesh; sub intersect { $mesh = Slic3r::TriangleMesh->new( facets => [], vertices => [], ); push @{$mesh->facets}, [ [0,0,0], @{vertices(@_)} ]; $mesh->analyze; return map Slic3r::TriangleMesh::unpack_line($_), $mesh->intersect_facet($#{$mesh->facets}, $z); } sub vertices { push @{$mesh->vertices}, map [ @{$points[$_]}, $_[$_] ], 0..2; [ ($#{$mesh->vertices}-2) .. $#{$mesh->vertices} ] } sub lines { my @lines = intersect(@_); #$_->a->[X] = sprintf('%.0f', $_->a->[X]) for @lines; #$_->a->[Y] = sprintf('%.0f', $_->a->[Y]) for @lines; $_->[Slic3r::TriangleMesh::I_B][X] = sprintf('%.0f', $_->[Slic3r::TriangleMesh::I_B][X]) for @lines; $_->[Slic3r::TriangleMesh::I_B][Y] = sprintf('%.0f', $_->[Slic3r::TriangleMesh::I_B][Y]) for @lines; return [ map [ -1, $_->[Slic3r::TriangleMesh::I_B] ], @lines ]; } sub line_plane_intersection { my ($line) = @_; @$line = map $mesh->vertices->[$_], @$line; return [ map sprintf('%.0f', $_), map +($line->[1][$_] + ($line->[0][$_] - $line->[1][$_]) * ($z - $line->[1][Z]) / ($line->[0][Z] - $line->[1][Z])), (X,Y) ]; } __END__ Slic3r-version_1.39.1/t/support.t000066400000000000000000000254401324354444700166740ustar00rootroot00000000000000use Test::More; use strict; use warnings; plan skip_all => 'temporarily disabled'; plan tests => 27; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(epsilon scale); use Slic3r::Geometry::Clipper qw(diff); use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; $config->set('support_material', 1); my @contact_z = my @top_z = (); my $test = sub { my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $object_config = $print->print->objects->[0]->config; my $flow = Slic3r::Flow->new_from_width( width => $object_config->support_material_extrusion_width || $object_config->extrusion_width, role => FLOW_ROLE_SUPPORT_MATERIAL, nozzle_diameter => $print->config->nozzle_diameter->[$object_config->support_material_extruder-1] // $print->config->nozzle_diameter->[0], layer_height => $object_config->layer_height, bridge_flow_ratio => 0, ); my $support = Slic3r::Print::SupportMaterial->new( object_config => $print->print->objects->[0]->config, print_config => $print->print->config, flow => $flow, interface_flow => $flow, first_layer_flow => $flow, ); my $support_z = $support->support_layers_z($print->print->objects->[0], \@contact_z, \@top_z, $config->layer_height); my $expected_top_spacing = $support->contact_distance($config->layer_height, $config->nozzle_diameter->[0]); is $support_z->[0], $config->first_layer_height, 'first layer height is honored'; is scalar(grep { $support_z->[$_]-$support_z->[$_-1] <= 0 } 1..$#$support_z), 0, 'no null or negative support layers'; is scalar(grep { $support_z->[$_]-$support_z->[$_-1] > $config->nozzle_diameter->[0] + epsilon } 1..$#$support_z), 0, 'no layers thicker than nozzle diameter'; my $wrong_top_spacing = 0; foreach my $top_z (@top_z) { # find layer index of this top surface my $layer_id = first { abs($support_z->[$_] - $top_z) < epsilon } 0..$#$support_z; # check that first support layer above this top surface (or the next one) is spaced with nozzle diameter $wrong_top_spacing = 1 if ($support_z->[$layer_id+1] - $support_z->[$layer_id]) != $expected_top_spacing && ($support_z->[$layer_id+2] - $support_z->[$layer_id]) != $expected_top_spacing; } ok !$wrong_top_spacing, 'layers above top surfaces are spaced correctly'; }; $config->set('layer_height', 0.2); $config->set('first_layer_height', 0.3); @contact_z = (1.9); @top_z = (1.1); $test->(); $config->set('first_layer_height', 0.4); $test->(); $config->set('layer_height', $config->nozzle_diameter->[0]); $test->(); } { my $config = Slic3r::Config::new_from_defaults; $config->set('raft_layers', 3); $config->set('brim_width', 0); $config->set('skirts', 0); $config->set('support_material_extruder', 2); $config->set('support_material_interface_extruder', 2); $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.4); my $print = Slic3r::Test::init_print('overhang', config => $config); ok my $gcode = Slic3r::Test::gcode($print), 'no conflict between raft/support and brim'; my $tool = 0; Slic3r::GCode::Reader->new->parse($gcode, sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif ($info->{extruding}) { if ($self->Z <= ($config->raft_layers * $config->layer_height)) { fail 'not extruding raft with support material extruder' if $tool != ($config->support_material_extruder-1); } else { fail 'support material exceeds raft layers' if $tool == $config->support_material_extruder-1; # TODO: we should test that full support is generated when we use raft too } } }); } { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('raft_layers', 3); $config->set('support_material_pattern', 'honeycomb'); $config->set('support_material_extrusion_width', 0.6); $config->set('first_layer_extrusion_width', '100%'); $config->set('bridge_speed', 99); $config->set('cooling', [ 0 ]); # prevent speed alteration $config->set('first_layer_speed', '100%'); # prevent speed alteration $config->set('start_gcode', ''); # prevent any unexpected Z move my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $layer_id = -1; # so that first Z move sets this to 0 my @raft = my @first_object_layer = (); my %first_object_layer_speeds = (); # F => 1 Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding} && $info->{dist_XY} > 0) { if ($layer_id <= $config->raft_layers) { # this is a raft layer or the first object layer my $line = Slic3r::Line->new_scale([ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ]); my @path = @{$line->grow(scale($config->support_material_extrusion_width/2))}; if ($layer_id < $config->raft_layers) { # this is a raft layer push @raft, @path; } else { push @first_object_layer, @path; $first_object_layer_speeds{ $args->{F} // $self->F } = 1; } } } elsif ($cmd eq 'G1' && $info->{dist_Z} > 0) { $layer_id++; } }); ok !@{diff(\@first_object_layer, \@raft)}, 'first object layer is completely supported by raft'; is scalar(keys %first_object_layer_speeds), 1, 'only one speed used in first object layer'; ok +(keys %first_object_layer_speeds)[0] == $config->bridge_speed*60, 'bridge speed used in first object layer'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('layer_height', 0.35); $config->set('first_layer_height', 0.3); $config->set('nozzle_diameter', [0.5]); $config->set('support_material_extruder', 2); $config->set('support_material_interface_extruder', 2); my $test = sub { my ($raft_layers) = @_; $config->set('raft_layers', $raft_layers); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my %raft_z = (); # z => 1 my $tool = undef; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif ($info->{extruding} && $info->{dist_XY} > 0) { if ($tool == $config->support_material_extruder-1) { $raft_z{$self->Z} = 1; } } }); is scalar(keys %raft_z), $config->raft_layers, 'correct number of raft layers is generated'; }; $test->(2); $test->(70); $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.35); $test->(3); $test->(70); } { my $config = Slic3r::Config::new_from_defaults; $config->set('brim_width', 0); $config->set('skirts', 0); $config->set('support_material', 1); $config->set('top_solid_layers', 0); # so that we don't have the internal bridge over infill $config->set('bridge_speed', 99); $config->set('cooling', [ 0 ]); $config->set('first_layer_speed', '100%'); my $test = sub { my $print = Slic3r::Test::init_print('overhang', config => $config); my $has_bridge_speed = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($info->{extruding}) { if (($args->{F} // $self->F) == $config->bridge_speed*60) { $has_bridge_speed = 1; } } }); return $has_bridge_speed; }; $config->set('support_material_contact_distance', 0.2); ok $test->(), 'bridge speed is used when support_material_contact_distance > 0'; $config->set('support_material_contact_distance', 0); ok !$test->(), 'bridge speed is not used when support_material_contact_distance == 0'; $config->set('raft_layers', 5); $config->set('support_material_contact_distance', 0.2); ok $test->(), 'bridge speed is used when raft_layers > 0 and support_material_contact_distance > 0'; $config->set('support_material_contact_distance', 0); ok !$test->(), 'bridge speed is not used when raft_layers > 0 and support_material_contact_distance == 0'; } { my $config = Slic3r::Config::new_from_defaults; $config->set('skirts', 0); $config->set('start_gcode', ''); $config->set('raft_layers', 8); $config->set('nozzle_diameter', [0.4, 1]); $config->set('layer_height', 0.1); $config->set('first_layer_height', 0.8); $config->set('support_material_extruder', 2); $config->set('support_material_interface_extruder', 2); $config->set('support_material_contact_distance', 0); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); ok my $gcode = Slic3r::Test::gcode($print), 'first_layer_height is validated with support material extruder nozzle diameter when using raft layers'; my $tool = undef; my @z = (0); my %layer_heights_by_tool = (); # tool => [ lh, lh... ] Slic3r::GCode::Reader->new->parse($gcode, sub { my ($self, $cmd, $args, $info) = @_; if ($cmd =~ /^T(\d+)/) { $tool = $1; } elsif ($cmd eq 'G1' && exists $args->{Z} && $args->{Z} != $self->Z) { push @z, $args->{Z}; } elsif ($info->{extruding} && $info->{dist_XY} > 0) { $layer_heights_by_tool{$tool} ||= []; push @{ $layer_heights_by_tool{$tool} }, $z[-1] - $z[-2]; } }); ok !defined(first { $_ > $config->nozzle_diameter->[0] + epsilon } @{ $layer_heights_by_tool{$config->perimeter_extruder-1} }), 'no object layer is thicker than nozzle diameter'; ok !defined(first { abs($_ - $config->layer_height) < epsilon } @{ $layer_heights_by_tool{$config->support_material_extruder-1} }), 'no support material layer is as thin as object layers'; } __END__ Slic3r-version_1.39.1/t/svg.t000066400000000000000000000014021324354444700157470ustar00rootroot00000000000000use Test::More tests => 2; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; use Slic3r::Test; { my $print = Slic3r::Test::init_print('20mm_cube'); eval { my $fh = IO::Scalar->new(\my $gcode); $print->print->export_svg(output_fh => $fh, quiet => 1); $fh->close; }; die $@ if $@; ok !$@, 'successful SVG export'; } { my $print = Slic3r::Test::init_print('two_hollow_squares'); eval { my $fh = IO::Scalar->new(\my $gcode); $print->print->export_svg(output_fh => $fh, quiet => 1); $fh->close; }; die $@ if $@; ok !$@, 'successful SVG export of object with two islands'; } __END__ Slic3r-version_1.39.1/t/thin.t000066400000000000000000000200541324354444700161160ustar00rootroot00000000000000use Test::More tests => 23; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; use List::Util qw(first sum none); use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon Y); use Slic3r::Test; # Disable this until a more robust implementation is provided. It currently # fails on Linux 32bit because some spurious extrudates are generated. if (0) { my $config = Slic3r::Config::new_from_defaults; $config->set('layer_height', 0.2); $config->set('first_layer_height', '100%'); $config->set('extrusion_width', 0.5); $config->set('first_layer_extrusion_width', '200%'); # check this one too $config->set('skirts', 0); $config->set('thin_walls', 1); my $print = Slic3r::Test::init_print('gt2_teeth', config => $config); my %extrusion_paths = (); # Z => count of continuous extrusions my $extruding = 0; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1') { if ($info->{extruding} && $info->{dist_XY}) { if (!$extruding) { $extrusion_paths{$self->Z} //= 0; $extrusion_paths{$self->Z}++; } $extruding = 1; } else { $extruding = 0; } } }); ok !(first { $_ != 3 } values %extrusion_paths), 'no superfluous thin walls are generated for toothed profile'; } { my $square = Slic3r::Polygon->new_scale( # ccw [100, 100], [200, 100], [200, 200], [100, 200], ); my $hole_in_square = Slic3r::Polygon->new_scale( # cw [140, 140], [140, 160], [160, 160], [160, 140], ); my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); my $res = $expolygon->medial_axis(scale 40, scale 0.5); is scalar(@$res), 1, 'medial axis of a square shape is a single path'; isa_ok $res->[0], 'Slic3r::Polyline', 'medial axis result is a polyline'; ok $res->[0]->first_point->coincides_with($res->[0]->last_point), 'polyline forms a closed loop'; ok $res->[0]->length > $hole_in_square->length && $res->[0]->length < $square->length, 'medial axis loop has reasonable length'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( [100, 100], [120, 100], [120, 200], [100, 200], )); my $res = $expolygon->medial_axis(scale 20, scale 0.5); is scalar(@$res), 1, 'medial axis of a narrow rectangle is a single line'; ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( [100, 100], [120, 100], [120, 200], [105, 200], # extra point in the short side [100, 200], )); my $res2 = $expolygon->medial_axis(scale 1, scale 0.5); is scalar(@$res), 1, 'medial axis of a narrow rectangle with an extra vertex is still a single line'; ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has still a reasonable length'; ok !(grep { abs($_ - scale 150) < scaled_epsilon } map $_->[Y], map @$_, @$res2), "extra vertices don't influence medial axis"; } { my $expolygon = Slic3r::ExPolygon->new( Slic3r::Polygon->new([1185881,829367],[1421988,1578184],[1722442,2303558],[2084981,2999998],[2506843,3662186],[2984809,4285086],[3515250,4863959],[4094122,5394400],[4717018,5872368],[5379210,6294226],[6075653,6656769],[6801033,6957229],[7549842,7193328],[8316383,7363266],[9094809,7465751],[9879211,7500000],[10663611,7465750],[11442038,7363265],[12208580,7193327],[12957389,6957228],[13682769,6656768],[14379209,6294227],[15041405,5872366],[15664297,5394401],[16243171,4863960],[16758641,4301424],[17251579,3662185],[17673439,3000000],[18035980,2303556],[18336441,1578177],[18572539,829368],[18750748,0],[19758422,0],[19727293,236479],[19538467,1088188],[19276136,1920196],[18942292,2726179],[18539460,3499999],[18070731,4235755],[17539650,4927877],[16950279,5571067],[16307090,6160437],[15614974,6691519],[14879209,7160248],[14105392,7563079],[13299407,7896927],[12467399,8159255],[11615691,8348082],[10750769,8461952],[9879211,8500000],[9007652,8461952],[8142729,8348082],[7291022,8159255],[6459015,7896927],[5653029,7563079],[4879210,7160247],[4143447,6691519],[3451331,6160437],[2808141,5571066],[2218773,4927878],[1687689,4235755],[1218962,3499999],[827499,2748020],[482284,1920196],[219954,1088186],[31126,236479],[0,0],[1005754,0]), ); my $res = $expolygon->medial_axis(scale 1.324888, scale 0.25); is scalar(@$res), 1, 'medial axis of a semicircumference is a single line'; # check whether turns are all CCW or all CW my @lines = @{$res->[0]->lines}; my @angles = map { $lines[$_-1]->ccw($lines[$_]->b) } 1..$#lines; ok !!(none { $_ < 0 } @angles) || (none { $_ > 0 } @angles), 'all medial axis segments of a semicircumference have the same orientation'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( [100, 100], [120, 100], [112, 200], [108, 200], )); my $res = $expolygon->medial_axis(scale 20, scale 0.5); is scalar(@$res), 1, 'medial axis of a narrow trapezoid is a single line'; ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( [100, 100], [120, 100], [120, 180], [200, 180], [200, 200], [100, 200], )); my $res = $expolygon->medial_axis(scale 20, scale 0.5); is scalar(@$res), 1, 'medial axis of a L shape is a single polyline'; my $len = unscale($res->[0]->length) + 20; # 20 is the thickness of the expolygon, which is subtracted from the ends ok $len > 80*2 && $len < 100*2, 'medial axis has reasonable length'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( [-203064906,-51459966],[-219312231,-51459966],[-219335477,-51459962],[-219376095,-51459962],[-219412047,-51459966], [-219572094,-51459966],[-219624814,-51459962],[-219642183,-51459962],[-219656665,-51459966],[-220815482,-51459966], [-220815482,-37738966],[-221117540,-37738966],[-221117540,-51762024],[-203064906,-51762024], )); my $polylines = $expolygon->medial_axis(819998, 102499.75); my $perimeter = $expolygon->contour->split_at_first_point->length; ok sum(map $_->length, @$polylines) > $perimeter/2/4*3, 'medial axis has a reasonable length'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale( [50, 100], [1000, 102], [50, 104], )); my $res = $expolygon->medial_axis(scale 4, scale 0.5); is scalar(@$res), 1, 'medial axis of a narrow triangle is a single line'; ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length'; } { # GH #2474 my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( [91294454,31032190],[11294481,31032190],[11294481,29967810],[44969182,29967810],[89909960,29967808],[91294454,29967808] )); my $polylines = $expolygon->medial_axis(1871238, 500000); is scalar(@$polylines), 1, 'medial axis is a single polyline'; my $polyline = $polylines->[0]; my $expected_y = $expolygon->bounding_box->center->y; #;; ok abs(sum(map $_->y, @$polyline) / @$polyline - $expected_y) < scaled_epsilon, #,, 'medial axis is horizontal and is centered'; # order polyline from left to right $polyline->reverse if $polyline->first_point->x > $polyline->last_point->x; my $polyline_bb = $polyline->bounding_box; is $polyline->first_point->x, $polyline_bb->x_min, 'expected x_min'; is $polyline->last_point->x, $polyline_bb->x_max, 'expected x_max'; is_deeply [ map $_->x, @$polyline ], [ sort map $_->x, @$polyline ], 'medial axis is not self-overlapping'; } __END__ Slic3r-version_1.39.1/t/threads.t000066400000000000000000000013431324354444700166060ustar00rootroot00000000000000use Test::More tests => 2; use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use List::Util qw(first); use Slic3r; use Slic3r::Test; { my $print = Slic3r::Test::init_print('20mm_cube'); { my $thread = threads->create(sub { Slic3r::thread_cleanup(); return 1; }); ok $thread->join, "print survives thread spawning"; } } { my $thread = threads->create(sub { { my $print = Slic3r::Test::init_print('20mm_cube'); Slic3r::Test::gcode($print); } Slic3r::thread_cleanup(); return 1; }); ok $thread->join, "process print in a separate thread"; } __END__ Slic3r-version_1.39.1/utils/000077500000000000000000000000001324354444700156635ustar00rootroot00000000000000Slic3r-version_1.39.1/utils/amf-to-stl.pl000077500000000000000000000022521324354444700202070ustar00rootroot00000000000000#!/usr/bin/perl # This script converts an AMF file to STL use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use File::Basename qw(basename); use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; $|++; # Convert all parameters from the local code page to utf8 on Windows. @ARGV = map Slic3r::decode_path($_), @ARGV if $^O eq 'MSWin32'; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'ascii' => \$opt{ascii}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { my $model = Slic3r::Model->load_amf($ARGV[0]); my $output_file = $ARGV[0]; $output_file =~ s/\.[aA][mM][fF](?:\.[xX][mM][lL])?$/\.stl/; printf "Writing to %s\n", basename($output_file); $model->store_stl($output_file, binary => !$opt{ascii}); } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: amf-to-stl.pl [ OPTIONS ] file.amf --help Output this usage screen and exit --ascii Generate ASCII STL files (default: binary) EOF exit ($exit_code || 0); } __END__ Slic3r-version_1.39.1/utils/dump-stl.pl000066400000000000000000000022731324354444700177710ustar00rootroot00000000000000#!/usr/bin/perl # This script dumps a STL file into Perl syntax for writing tests # or dumps a test model into a STL file use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; use Slic3r::Test; use File::Basename qw(basename); $|++; $ARGV[0] or usage(1); if (-e $ARGV[0]) { my $model = Slic3r::Model->load_stl($ARGV[0], basename($ARGV[0])); $model->objects->[0]->add_instance(offset => Slic3r::Pointf->new(0,0)); my $mesh = $model->mesh; $mesh->repair; printf "VERTICES = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->vertices}; printf "FACETS = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->facets}; exit 0; } elsif ((my $model = Slic3r::Test::model($ARGV[0]))) { $ARGV[1] or die "Missing writeable destination as second argument\n"; $model->store_stl($ARGV[1], 1); printf "Model $ARGV[0] written to $ARGV[1]\n"; exit 0; } else { die "No such model exists\n"; } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: dump-stl.pl file.stl dump-stl.pl modelname file.stl EOF exit ($exit_code || 0); } __END__ Slic3r-version_1.39.1/utils/gcode_sectioncut.pl000066400000000000000000000072741324354444700215530ustar00rootroot00000000000000#!/usr/bin/perl # This script generates section cuts from a given G-Code file use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Getopt::Long qw(:config no_auto_abbrev); use IO::All; use List::Util qw(max); use Slic3r; use Slic3r::Geometry qw(X Y); use Slic3r::Geometry::Clipper qw(JT_SQUARE); use Slic3r::Test; use SVG; my %opt = ( layer_height => 0.2, extrusion_width => 0.5, scale => 30, ); { my %options = ( 'help' => sub { usage() }, 'layer-height|h=f' => \$opt{layer_height}, 'extrusion-width|w=f' => \$opt{extrusion_width}, 'scale|s=i' => \$opt{scale}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { my $input_file = $ARGV[0]; my $output_file = $input_file; $output_file =~ s/\.(?:gcode|gco|ngc|g)$/.svg/; # read paths my %paths = (); # z => [ path, path ... ] Slic3r::GCode::Reader->new->parse(io($input_file)->all, sub { my ($self, $cmd, $args, $info) = @_; if ($cmd eq 'G1' && $info->{extruding}) { $paths{ $self->Z } ||= []; push @{ $paths{ $self->Z } }, Slic3r::Line->new( [ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ], ); } }); # calculate print extents my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, values %paths ]); # calculate section line my $section_y = $bounding_box->center->[Y]; my $section_line = [ [ $bounding_box->x_min, $section_y ], [ $bounding_box->x_max, $section_y ], ]; # initialize output my $max_z = max(keys %paths); my $svg = SVG->new( width => $opt{scale} * $bounding_box->size->[X], height => $opt{scale} * $max_z, ); # put everything into a group my $g = $svg->group(style => { 'stroke-width' => 1, 'stroke' => '#444444', 'fill' => 'grey', }); # draw paths foreach my $z (sort keys %paths) { foreach my $line (@{ $paths{$z} }) { my @intersections = @{intersection_pl( [ $section_line ], [ _grow($line, $opt{extrusion_width}/2) ], )}; $g->rectangle( 'x' => $opt{scale} * ($_->[0][X] - $bounding_box->x_min), 'y' => $opt{scale} * ($max_z - $z), 'width' => $opt{scale} * abs($_->[1][X] - $_->[0][X]), 'height' => $opt{scale} * $opt{layer_height}, 'rx' => $opt{scale} * $opt{layer_height} * 0.35, 'ry' => $opt{scale} * $opt{layer_height} * 0.35, ) for @intersections; } } # write output Slic3r::open(\my $fh, '>', $output_file); print $fh $svg->xmlify; close $fh; printf "Section cut SVG written to %s\n", $output_file; } # replace built-in Line->grow method which relies on int_offset() sub _grow { my ($line, $distance) = @_; my $polygon = [ @$line, CORE::reverse @$line[1..($#$line-1)] ]; return @{Math::Clipper::offset([$polygon], $distance, 100000, JT_SQUARE, 2)}; } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: gcode_sectioncut.pl [ OPTIONS ] file.gcode --help Output this usage screen and exit --layer-height, -h Use the specified layer height --extrusion-width, -w Use the specified extrusion width --scale Factor for converting G-code units to SVG units EOF exit ($exit_code || 0); } __END__ Slic3r-version_1.39.1/utils/modifier_helpers/000077500000000000000000000000001324354444700212035ustar00rootroot00000000000000Slic3r-version_1.39.1/utils/modifier_helpers/layer_generator.jscad000066400000000000000000000011371324354444700253750ustar00rootroot00000000000000// title: Layer_generator // written by: Joseph Lenox // Used for generating cubes oriented about the center // for making simple modifier meshes. var width = 100; var layer_height = 0.3; var z = 30; function main() { return cube(size=[width,width,layer_height], center=true).translate([0,0,z]); } function getParameterDefinitions() { return [ { name: 'width', type: 'float', initial: 100, caption: "Width of the cube:" }, { name: 'layer_height', type: 'float', initial: 0.3, caption: "Layer height used:" }, { name: 'z', type: 'float', initial: 0, caption: "Z:" } ]; } Slic3r-version_1.39.1/utils/modifier_helpers/solid_layers.scad000066400000000000000000000020531324354444700245300ustar00rootroot00000000000000// Used to generate a modifier mesh to do something every few layers. // Load into OpenSCAD, tweak the variables below, export as STL and load as // a modifier mesh. Then change settings for the modifier mesh. // Written by Joseph Lenox; in public domain. layer_height = 0.3; // set to layer height in slic3r for "best" results. number_of_solid_layers = 2; N = 4; // N > number_of_solid_layers or else the whole thing will be solid model_height = 300.0; model_width = 300.0; // these two should be at least as big as the model model_depth = 300.0; // but bigger isn't a problem initial_offset=0; // don't generate below this position_on_bed=[0,0,0]; // in case you need to move it around // don't touch below unless you know what you are doing. simple_layers = round(model_height/layer_height); translate(position_on_bed) for (i = [initial_offset:N:simple_layers]) { translate([0,0,i*layer_height]) translate([0,0,(layer_height*number_of_solid_layers)/2]) cube([model_width,model_depth,layer_height*number_of_solid_layers], center=true); } Slic3r-version_1.39.1/utils/pdf-slices.pl000077500000000000000000000056721324354444700202660ustar00rootroot00000000000000#!/usr/bin/perl # This script exports model slices to a PDF file as solid fills, one per page use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Getopt::Long qw(:config no_auto_abbrev); use PDF::API2; use Slic3r; use Slic3r::Geometry qw(scale unscale X Y); use constant mm => 25.4 / 72; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'output|o=s' => \$opt{output_file}, 'layer-height|h=f' => \$opt{layer_height}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { # prepare config my $config = Slic3r::Config->new; $config->set('layer_height', $opt{layer_height}) if $opt{layer_height}; # read model my $model = Slic3r::Model->read_from_file(my $input_file = $ARGV[0]); # init print object my $sprint = Slic3r::Print::Simple->new( print_center => [0,0], ); $sprint->apply_config($config); $sprint->set_model($model); my $print = $sprint->_print; # compute sizes my $bb = $print->bounding_box; my $size = $bb->size; my $mediabox = [ map unscale($_)/mm, @{$size} ]; # init PDF my $pdf = PDF::API2->new(); my $color = $pdf->colorspace_separation('RDG_GLOSS', 'darkblue'); # slice and build output geometry $_->slice for @{$print->objects}; foreach my $object (@{ $print->objects }) { my $shift = $object->_shifted_copies->[0]; $shift->translate(map $_/2, @$size); foreach my $layer (@{ $object->layers }) { my $page = $pdf->page(); $page->mediabox(@$mediabox); my $content = $page->gfx; $content->fillcolor($color, 1); foreach my $expolygon (@{$layer->slices}) { $expolygon = $expolygon->clone; $expolygon->translate(@$shift); $content->poly(map { unscale($_->x)/mm, unscale($_->y)/mm } @{$expolygon->contour}); #) $content->close; foreach my $hole (@{$expolygon->holes}) { $content->poly(map { unscale($_->x)/mm, unscale($_->y)/mm } @$hole); #) $content->close; } $content->fill; # non-zero by default } } } # write output file my $output_file = $opt{output_file}; if (!defined $output_file) { $output_file = $input_file; $output_file =~ s/\.(?:[sS][tT][lL])$/.pdf/; } $pdf->saveas($output_file); printf "PDF file written to %s\n", $output_file; } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: pdf-slices.pl [ OPTIONS ] file.stl --help Output this usage screen and exit --output, -o Write to the specified file --layer-height, -h Use the specified layer height EOF exit ($exit_code || 0); } __END__ Slic3r-version_1.39.1/utils/post-processing/000077500000000000000000000000001324354444700210225ustar00rootroot00000000000000Slic3r-version_1.39.1/utils/post-processing/decimate.pl000077500000000000000000000015441324354444700231410ustar00rootroot00000000000000#!/usr/bin/perl -i~ use strict; use warnings; my %lastpos = (X => 10000, Y => 10000, Z => 10000, E => 10000, F => 10000); my %pos = (X => 0, Y => 0, Z => 0, E => 0, F => 0); my $mindist = 0.33; my $mindistz = 0.005; my $mindistsq = $mindist * $mindist; sub dist { my $sq = 0; for (qw/X Y Z E/) { $sq += ($pos{$_} - $lastpos{$_}) ** 2; } return $sq; } while (<>) { if (m#\bG[01]\b#) { while (m#([XYZEF])(\d+(\.\d+)?)#gi) { $pos{uc $1} = $2; } if ( ( /X/ && /Y/ && (dist() >= $mindistsq) ) || (abs($pos{Z} - $lastpos{Z}) > $mindistz) || (!/X/ || !/Y/) ) { print; %lastpos = %pos; } elsif (($pos{F} - $lastpos{F}) != 0) { printf "G1 F%s\n", $pos{F}; $lastpos{F} = $pos{F}; } } else { if (m#\bG92\b#) { while (m#([XYZEF])(\d+(\.\d+)?)#gi) { $lastpos{uc $1} = $2; } } print; } } Slic3r-version_1.39.1/utils/post-processing/fan_kickstart.py000066400000000000000000000005061324354444700242200ustar00rootroot00000000000000#!/usr/bin/python import sys import re sea = re.compile("M106 S[1-9]+[0-9]*") rep = re.compile("M106 S255\n\g<0>") out = open(sys.argv[1]+"_fixed", 'w') with open(sys.argv[1]) as f: for r in f: if re.search(sea, r) is not None: out.write(re.sub(sea,"M106 S255\n\g<0>",r)) else: out.write(r) Slic3r-version_1.39.1/utils/post-processing/filament-weight.pl000077500000000000000000000016231324354444700244500ustar00rootroot00000000000000#!/usr/bin/perl -i # # Post-processing script for adding weight and cost of required # filament to G-code output. use strict; use warnings; # example densities, adjust according to filament specifications use constant PLA_P => 1.25; # g/cm3 use constant ABS_P => 1.05; # g/cm3 # example costs, adjust according to filament prices use constant PLA_PRICE => 0.05; # EUR/g use constant ABS_PRICE => 0.02; # EUR/g use constant CURRENCY => "EUR"; while (<>) { if (/^(;\s+filament\s+used\s+=\s.*\((\d+(?:\.\d+)?)cm3)\)/) { my $pla_weight = $2 * PLA_P; my $abs_weight = $2 * ABS_P; my $pla_costs = $pla_weight * PLA_PRICE; my $abs_costs = $abs_weight * ABS_PRICE; printf "%s or %.2fg PLA/%.2fg ABS)\n", $1, $pla_weight, $abs_weight; printf "; costs = %s %.2f (PLA), %s %.2f (ABS)\n", CURRENCY, $pla_costs, CURRENCY, $abs_costs; } else { print; } } Slic3r-version_1.39.1/utils/post-processing/flowrate.pl000077500000000000000000000024761324354444700232160ustar00rootroot00000000000000#!/usr/bin/perl -i # # Post-processing script for calculating flow rate for each move use strict; use warnings; use constant PI => 3.141592653589793238; my @filament_diameter = split /,/, $ENV{SLIC3R_FILAMENT_DIAMETER}; my $E = 0; my $T = 0; my ($X, $Y, $F); while (<>) { if (/^G1.*? F([0-9.]+)/) { $F = $1; } if (/^G1 X([0-9.]+) Y([0-9.]+).*? E([0-9.]+)/) { my ($x, $y, $e) = ($1, $2, $3); my $e_length = $e - $E; if ($e_length > 0 && defined $X && defined $Y) { my $dist = sqrt( (($x-$X)**2) + (($y-$Y)**2) ); if ($dist > 0) { my $mm_per_mm = $e_length / $dist; # dE/dXY my $mm3_per_mm = ($filament_diameter[$T] ** 2) * PI/4 * $mm_per_mm; my $vol_speed = $F/60 * $mm3_per_mm; my $comment = sprintf ' ; dXY = %.3fmm ; dE = %.5fmm ; dE/XY = %.5fmm/mm; volspeed = %.5fmm^3/sec', $dist, $e_length, $mm_per_mm, $vol_speed; s/(\R+)/$comment$1/; } } $E = $e; $X = $x; $Y = $y; } if (/^G1 X([0-9.]+) Y([0-9.]+)/) { $X = $1; $Y = $2; } if (/^G1.*? E([0-9.]+)/) { $E = $1; } if (/^G92 E0/) { $E = 0; } if (/^T(\d+)/) { $T = $1; } print; } __END__ Slic3r-version_1.39.1/utils/post-processing/prowl-notification.pl000077500000000000000000000011501324354444700252060ustar00rootroot00000000000000#!/usr/bin/perl # # Example post-processing script for sending a Prowl notification upon # completion. See http://www.prowlapp.com/ for more info. use strict; use warnings; use File::Basename qw(basename); use WebService::Prowl; # set your Prowl API key here my $apikey = ''; my $file = basename $ARGV[0]; my $prowl = WebService::Prowl->new(apikey => $apikey); my %options = (application => 'Slic3r', event =>'Slicing Done!', description => "$file was successfully generated"); printf STDERR "Error sending Prowl notification: %s\n", $prowl->error unless $prowl->add(%options); Slic3r-version_1.39.1/utils/post-processing/z-every-line.pl000077500000000000000000000011151324354444700237060ustar00rootroot00000000000000#!/usr/bin/perl -i use strict; use warnings; my $z = 0; # read stdin and any/all files passed as parameters one line at a time while (<>) { # if we find a Z word, save it $z = $1 if /Z\s*(\d+(\.\d+)?)/; # if we don't have Z, but we do have X and Y if (!/Z/ && /X/ && /Y/ && $z > 0) { # chop off the end of the line (incl. comments), saving chopped section in $1 s/\s*([\r\n\;\(].*)/" Z$z $1"/es; # print start of line, insert our Z value then re-add the chopped end of line # print "$_ Z$z $1"; } #else { # nothing interesting, print line as-is print or die $!; #} } Slic3r-version_1.39.1/utils/send-gcode.pl000066400000000000000000000013151324354444700202300ustar00rootroot00000000000000#!/usr/bin/env perl use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Slic3r; die "Usage: send-gcode.pl SERIALPORT BAUDRATE GCODE_FILE\n" if @ARGV != 3; my $serial = Slic3r::GCode::Sender->new($ARGV[0], $ARGV[1]); 1 until $serial->is_connected; print "Connected to printer\n"; { local $/ = "\n"; Slic3r::open(\my $fh, '<', $ARGV[2]) or die "Unable to open $ARGV[2]: $!\n"; binmode $fh, ':utf8'; while (<$fh>) { $serial->send($_); } close $fh; } while ((my $queue_size = $serial->queue_size) > 0) { printf "Queue size: %d\n", $queue_size; } $serial->disconnect; __END__ Slic3r-version_1.39.1/utils/split_stl.pl000077500000000000000000000027361324354444700202500ustar00rootroot00000000000000#!/usr/bin/perl # This script splits a STL plate into individual files use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use File::Basename qw(basename); use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; $|++; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'ascii' => \$opt{ascii}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { my $model = Slic3r::Model->load_stl($ARGV[0], basename($ARGV[0])); my $basename = $ARGV[0]; $basename =~ s/\.[sS][tT][lL]$//; my $part_count = 0; my $mesh = $model->objects->[0]->volumes->[0]->mesh; foreach my $new_mesh (@{$mesh->split}) { $new_mesh->repair; my $new_model = Slic3r::Model->new; $new_model ->add_object() ->add_volume(mesh => $new_mesh); $new_model->add_default_instances; my $output_file = sprintf '%s_%02d.stl', $basename, ++$part_count; printf "Writing to %s\n", basename($output_file); $new_model->store_stl($output_file, !$opt{ascii}); } } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: split_stl.pl [ OPTIONS ] file.stl --help Output this usage screen and exit --ascii Generate ASCII STL files (default: binary) EOF exit ($exit_code || 0); } __END__ Slic3r-version_1.39.1/utils/stl-to-amf.pl000077500000000000000000000035461324354444700202160ustar00rootroot00000000000000#!/usr/bin/perl # This script converts a STL file to AMF use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use File::Basename qw(basename); use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; $|++; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'distinct-materials' => \$opt{distinct_materials}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { my @models = map Slic3r::Model->load_stl($_, basename($_)), @ARGV; my $output_file = $ARGV[0]; $output_file =~ s/\.[sS][tT][lL]$/.amf.xml/; my $new_model = Slic3r::Model->new; if ($opt{distinct_materials} && @models > 1) { my $new_object = $new_model->add_object; for my $m (0 .. $#models) { my $model = $models[$m]; $new_model->set_material($m, { Name => basename($ARGV[$m]) }); $new_object->add_volume( material_id => $m, facets => $model->objects->[0]->volumes->[0]->facets, vertices => $model->objects->[0]->vertices, ); } } else { foreach my $model (@models) { $new_model->add_object( vertices => $model->objects->[0]->vertices, )->add_volume( facets => $model->objects->[0]->volumes->[0]->facets, ); } } printf "Writing to %s\n", basename($output_file); $new_model->store_amf($output_file); } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: amf-to-stl.pl [ OPTIONS ] file.stl [ file2.stl [ file3.stl ] ] --help Output this usage screen and exit --distinct-materials Assign each STL file to a different material EOF exit ($exit_code || 0); } __END__ Slic3r-version_1.39.1/utils/view-mesh.pl000066400000000000000000000034371324354444700201330ustar00rootroot00000000000000#!/usr/bin/perl # This script displays 3D preview of a mesh use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; use Slic3r::GUI; use Slic3r::GUI::3DScene; $|++; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'cut=f' => \$opt{cut}, 'enable-moving' => \$opt{enable_moving}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { my $model = Slic3r::Model->read_from_file($ARGV[0]); $_->center_around_origin for @{$model->objects}; # and align to Z = 0 my $app = Slic3r::ViewMesh->new; $app->{canvas}->enable_picking(1); $app->{canvas}->enable_moving($opt{enable_moving}); $app->{canvas}->load_object($model, 0); $app->{canvas}->set_auto_bed_shape; $app->{canvas}->zoom_to_volumes; $app->{canvas}->SetCuttingPlane($opt{cut}) if defined $opt{cut}; $app->MainLoop; } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: view-mesh.pl [ OPTIONS ] file.stl --help Output this usage screen and exit --cut Z Display the cutting plane at the given Z EOF exit ($exit_code || 0); } package Slic3r::ViewMesh; use Wx qw(:sizer); use base qw(Wx::App); sub OnInit { my $self = shift; my $frame = Wx::Frame->new(undef, -1, 'Mesh Viewer', [-1, -1], [500, 400]); my $panel = Wx::Panel->new($frame, -1); $self->{canvas} = Slic3r::GUI::3DScene->new($panel); my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($self->{canvas}, 1, wxEXPAND, 0); $panel->SetSizer($sizer); $sizer->SetSizeHints($panel); $frame->Show(1); } __END__ Slic3r-version_1.39.1/utils/view-toolpaths.pl000077500000000000000000000047171324354444700212210ustar00rootroot00000000000000#!/usr/bin/perl # This script displays 3D preview of a mesh use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; use Slic3r::GUI; use Slic3r::GUI::3DScene; $|++; my %opt = (); { my %options = ( 'help' => sub { usage() }, 'load=s' => \$opt{load}, '3D' => \$opt{d3}, 'duplicate=i' => \$opt{duplicate}, ); GetOptions(%options) or usage(1); $ARGV[0] or usage(1); } { # load model my $model = Slic3r::Model->read_from_file($ARGV[0]); # load config my $config = Slic3r::Config::new_from_defaults; if ($opt{load}) { $config->apply(Slic3r::Config::load($opt{load})); } # init print my $sprint = Slic3r::Print::Simple->new; $sprint->duplicate($opt{duplicate} // 1); $sprint->apply_config($config); $sprint->set_model($model); $sprint->process; # visualize toolpaths $Slic3r::ViewToolpaths::print = $sprint->_print; $Slic3r::ViewToolpaths::d3 = $opt{d3}; my $app = Slic3r::ViewToolpaths->new; $app->MainLoop; } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: view-toolpaths.pl [ OPTIONS ] file.stl --help Output this usage screen and exit --load CONFIG Loads the supplied config file EOF exit ($exit_code || 0); } package Slic3r::ViewToolpaths; use Wx qw(:sizer); use base qw(Wx::App Class::Accessor); our $print; our $d3; sub OnInit { my $self = shift; my $frame = Wx::Frame->new(undef, -1, 'Toolpaths', [-1, -1], [500, 500]); my $panel = Wx::Panel->new($frame, -1); my $canvas; if ($d3) { $canvas = Slic3r::GUI::3DScene->new($panel); $canvas->set_bed_shape($print->config->bed_shape); $canvas->load_print_toolpaths($print); foreach my $object (@{$print->objects}) { #$canvas->load_print_object_slices($object); $canvas->load_print_object_toolpaths($object); #$canvas->load_object($object->model_object); } $canvas->zoom_to_volumes; } else { $canvas = Slic3r::GUI::Plater::2DToolpaths->new($panel, $print); } my $sizer = Wx::BoxSizer->new(wxVERTICAL); $sizer->Add($canvas, 1, wxEXPAND, 0); $panel->SetSizer($sizer); $frame->Show(1); } __END__ Slic3r-version_1.39.1/utils/wireframe.pl000066400000000000000000000140641324354444700202060ustar00rootroot00000000000000#!/usr/bin/perl # This script exports experimental G-code for wireframe printing # (inspired by the brilliant WirePrint concept) use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; use local::lib "$FindBin::Bin/../local-lib"; } use Getopt::Long qw(:config no_auto_abbrev); use Slic3r; use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(scale unscale X Y PI); my %opt = ( step_height => 5, nozzle_angle => 30, nozzle_width => 10, first_layer_height => 0.3, ); { my %options = ( 'help' => sub { usage() }, 'output|o=s' => \$opt{output_file}, 'step-height|h=f' => \$opt{step_height}, 'nozzle-angle|a=f' => \$opt{nozzle_angle}, 'nozzle-width|w=f' => \$opt{nozzle_width}, 'first-layer-height=f' => \$opt{first_layer_height}, ); GetOptions(%options) or usage(1); $opt{output_file} or usage(1); $ARGV[0] or usage(1); } { # load model my $model = Slic3r::Model->read_from_file($ARGV[0]); $model->center_instances_around_point(Slic3r::Pointf->new(100,100)); my $mesh = $model->mesh; $mesh->translate(0, 0, -$mesh->bounding_box->z_min); # get slices my @z = (); my $z_max = $mesh->bounding_box->z_max; for (my $z = $opt{first_layer_height}; $z <= $z_max; $z += $opt{step_height}) { push @z, $z; } my @slices = @{$mesh->slice(\@z)}; my $flow = Slic3r::Flow->new( width => 0.35, height => 0.35, nozzle_diameter => 0.35, bridge => 1, ); my $config = Slic3r::Config::Print->new; $config->set('gcode_comments', 1); open my $fh, '>', $opt{output_file}; my $gcodegen = Slic3r::GCode->new( enable_loop_clipping => 0, # better bonding ); $gcodegen->apply_print_config($config); $gcodegen->set_extruders([0]); print $fh $gcodegen->set_extruder(0); print $fh $gcodegen->writer->preamble; my $e = $gcodegen->writer->extruder->e_per_mm3 * $flow->mm3_per_mm; foreach my $layer_id (0..$#z) { my $z = $z[$layer_id]; foreach my $island (@{$slices[$layer_id]}) { foreach my $polygon (@$island) { if ($layer_id > 0) { # find the lower polygon that we want to connect to this one my $lower = $slices[$layer_id-1]->[0]->contour; # 't was easy, wasn't it? my $lower_z = $z[$layer_id-1]; { my @points = (); # keep all points with strong angles { my @pp = @$polygon; foreach my $i (0..$#pp) { push @points, $pp[$i-1] if abs($pp[$i-1]->ccw_angle($pp[$i-2], $pp[$i]) - PI) > PI/3; } } $polygon = Slic3r::Polygon->new(@points); } #$polygon = Slic3r::Polygon->new(@{$polygon->split_at_first_point->equally_spaced_points(scale $opt{nozzle_width})}); # find vertical lines my @vertical = (); foreach my $point (@{$polygon}) { push @vertical, Slic3r::Line->new($point->projection_onto_polygon($lower), $point); } next if !@vertical; my @points = (); foreach my $line (@vertical) { push @points, Slic3r::Pointf3->new( unscale($line->a->x), unscale($line->a->y), #)) $lower_z, ); push @points, Slic3r::Pointf3->new( unscale($line->b->x), unscale($line->b->y), #)) $z, ); } # reappend first point as destination of the last diagonal segment push @points, Slic3r::Pointf3->new( unscale($vertical[0]->a->x), unscale($vertical[0]->a->y), #)) $lower_z, ); # move to the position of the first vertical line print $fh $gcodegen->writer->travel_to_xyz(shift @points); # extrude segments foreach my $point (@points) { print $fh $gcodegen->writer->extrude_to_xyz($point, $e * $gcodegen->writer->get_position->distance_to($point)); } } } print $fh $gcodegen->writer->travel_to_z($z); foreach my $polygon (@$island) { #my $polyline = $polygon->split_at_vertex(Slic3r::Point->new_scale(@{$gcodegen->writer->get_position}[0,1])); my $polyline = $polygon->split_at_first_point; print $fh $gcodegen->writer->travel_to_xy(Slic3r::Pointf->new_unscale(@{ $polyline->first_point }), "move to first contour point"); foreach my $line (@{$polyline->lines}) { my $point = Slic3r::Pointf->new_unscale(@{ $line->b }); print $fh $gcodegen->writer->extrude_to_xy($point, $e * unscale($line->length)); } } } } close $fh; } sub usage { my ($exit_code) = @_; print <<"EOF"; Usage: wireframe.pl [ OPTIONS ] file.stl --help Output this usage screen and exit --output, -o Write to the specified file --step-height, -h Use the specified step height --nozzle-angle, -a Max nozzle angle --nozzle-width, -w External nozzle diameter EOF exit ($exit_code || 0); } __END__ Slic3r-version_1.39.1/utils/zsh/000077500000000000000000000000001324354444700164675ustar00rootroot00000000000000Slic3r-version_1.39.1/utils/zsh/README.markdown000066400000000000000000000012541324354444700211720ustar00rootroot00000000000000# ZSH Completions for Slic3r To enable zsh(1) completions for Slic3r, add the following to your ``~/.zshrc`` file, replacing ``/path/to/Slic3r/`` with the actual path to your Slic3r directory: typeset -U fpath if [[ -d /path/to/Slic3r/utils/zsh/functions ]]; then fpath=(/path/to/Slic3r/utils/zsh/functions $fpath) fi autoload -Uz compinit compinit zstyle ':completion:*' verbose true zstyle ':completion:*:descriptions' format '%B%d%b' zstyle ':completion:*:messages' format '%d' zstyle ':completion:*:warnings' format 'No matches for %d' zstyle ':completion:*' group-name '%d' See the zshcompsys(1) man page for further details. Slic3r-version_1.39.1/utils/zsh/functions/000077500000000000000000000000001324354444700204775ustar00rootroot00000000000000Slic3r-version_1.39.1/utils/zsh/functions/_slic3r000066400000000000000000000233051324354444700217630ustar00rootroot00000000000000#compdef -P slic3r(|.pl|.exe) # # Slic3r completions configuration for zsh(1). # Currently undocumented options: # --debug, --gui, --ignore-nonexistent-config # --acceleration, --perimeter-acceleration, --infill-acceleration _arguments -S \ '(- *)--help[output usage screen and exit]' \ '(- *)--version[output the version of Slic3r and exit]' \ '--save[save configuration to file]:config output file:_files -g "*.(#i)ini(-.)"' \ '*--load[load configuration from file]:config input file:_files -g "*.(#i)ini(-.)"' \ '(--output -o)'{--output,-o}'[specify output file]:output file:_files -g "*.(#i)(gcode|svg)(-.)"' \ '(--threads -j)'{--threads,-j}'[specify number of threads to use]:number of threads' \ \ '--output-filename-format[specify output filename format]:output filename format' \ '*--post-process[specify post-processing script]:post-processing script file:_files' \ '--export-svg[export SVG containing slices instead of G-code]' \ '(--merge -m)'{--merge,-m}'[merge multiple input files into a single print]' \ \ '*--nozzle-diameter[specify nozzle diameter]:nozzle diameter in mm' \ '--print-center[specify print center coordinates]:print center coordinates in mm,mm' \ '--z-offset[specify Z-axis offset]:Z-axis offset in mm' \ '--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup repetier makerware sailfish mach3 machinekit smoothie no-extrusion)' \ '(--use-relative-e-distances --no-use-relative-e-distances)'--{no-,}use-relative-e-distances'[disable/enable relative E values]' \ '--extrusion-axis[specify letter associated with the extrusion axis]:extrusion axis letter' \ '(--gcode-arcs --no-gcode-arcs)'--{no-,}gcode-arcs'[disable/enable G2/G3 commands for native arcs]' \ '(--gcode-comments --no-gcode-comments)'--{no-,}gcode-comments'[disable/enable verbose G-code comments]' \ \ '*--filament-diameter[specify raw filament diameter]:raw filament diameter in mm' \ '*--extrusion-multiplier[specify multiplier for amount of plastic extruded]:extrusion multiplier' \ '*--temperature[specify extrusion temperature]:extrusion temperature in Celsius' \ '*--first-layer-temperature[specify extrusion temperature for the first layer]:first layer extrusion temperature in Celsius' \ '--bed-temperature[specify heated bed temperature]:heated bed temperature in Celsius' \ '--first-layer-bed-temperature[specify heated bed temperature for the first layer]:first layer heated bed temperature in Celsius' \ \ '--perimeter-extruder[specify extruder to use for printing perimeters]:extruder number' \ '--infill-extruder[specify extruder to use for printing infill]:extruder number' \ '--support-material-extruder[specify extruder to use for printing support material]:extruder number' \ \ '--travel-speed[specify speed of non-print moves]:speed of non-print moves in mm/s' \ '--perimeter-speed[specify speed of print moves for perimeters]:speed of print moves for perimeters in mm/s' \ '--external-perimeter-speed[specify speed of print moves for external perimeters]:speed of print moves for external perimeters in mm/s or % of --perimeter-speed' \ '--small-perimeter-speed[specify speed of print moves for small perimeters]:speed of print moves for small perimeters in mm/s or % of --perimeter-speed' \ '--infill-speed[specify speed of infill print moves]:speed of infill print moves in mm/s' \ '--solid-infill-speed[specify speed of solid surface print moves]:speed of solid surface print moves in mm/s or % of --infill-speed' \ '--top-solid-infill-speed[specify speed of top surface print moves]:speed of top surface print moves in mm/s or % of --solid-infill-speed' \ '--bridge-speed[specify speed of bridge print moves]:speed of bridge print moves in mm/s' \ '--first-layer-speed[specify speed of bottom layer print moves]:speed of bottom layer print moves in mm/s or % of normal speeds' \ \ '--layer-height[specify layer height]:layer height in mm' \ '--first-layer-height[specify layer height for bottom layer]:layer height for bottom layer in mm or % of --layer-height' \ '--infill-every-layers[specify infill for every N layers]:N layers' \ \ '--perimeters[specify number of perimeters]:number of perimeters' \ '--solid-layers[specify number of solid layers to do for top/bottom surfaces]:number of layers for top/bottom surfaces' \ '--fill-density[specify infill density]:infill density in percent' \ '--fill-angle[specify infill angle]:infill angle in degrees' \ '--fill-pattern[specify pattern used for infill]:infill pattern:(rectilinear line concentric honeycomb hilbertcurve archimedeanchords octagramspiral)' \ '--solid-fill-pattern[specify pattern used for solid layers]:solid fill pattern:(rectilinear concentric hilbertcurve archimedeanchords octagramspiral)' \ '--start-gcode[load initial G-code from file]:start G-code file:_files -g "*.(#i)(gcode)(-.)"' \ '--end-gcode[load final G-code from file]:end G-code file:_files -g "*.(#i)(gcode)(-.)"' \ '--layer-gcode[load layer-change G-code from file]:layer-change G-code file:_files -g "*.(#i)(gcode)(-.)"' \ '(--support-material --no-support-material)'--{no-,}support-material'[disable/enable generation of support material for overhangs]' \ '--support-material-threshold[specify support material threshold]:maximum slope angle for generating support material' \ '--support-material-pattern[specify pattern used for support material]:support material pattern:(rectilinear honeycomb)' \ '--support-material-spacing[specify spacing between support material lines]:spacing between support material lines in mm' \ '--support-material-angle[specify support material angle]:support material angle in degrees' \ '(--randomize-start --no-randomize-start)'--{no-,}randomize-start'[disable/enable randomization of starting point across layers]' \ '(--extra-perimeters --no-extra-perimeters)'--{no-,}extra-perimeters'[disable/enable generation of extra perimeters when needed]' \ \ '--retract-length[specify filament retraction length when pausing extrusion]:filament retraction length in mm' \ '--retract-speed[specify filament retraction speed]:filament retraction speed in mm/s' \ '--retract-restart-extra[specify filament length to extrude for compensating retraction]: filament lenght in mm' \ '--retract-before-travel[specify minimum travel length for activating retraction]:minimum travel length for activating retraction in mm' \ '--retract-lift[specify Z-axis lift for use when retracting]:Z-axis lift in mm' \ \ '(--cooling --no-cooling)'--{no-,}cooling'[disable/enable fan and cooling control]' \ '--min-fan-speed[specify minimum fan speed]:minimum fan speed in percent' \ '--max-fan-speed[specify maximum fan speed]:maximum fan speed in percent' \ '--bridge-fan-speed[specify fan speed to use for bridging]:bridging fan speed in percent' \ '--fan-below-layer-time[specify maximum layer print time before activating fan]:maximum layer print time in seconds' \ '--slowdown-below-layer-time[specify maximum layer print time before slowing down printing]:maximum layer print time in seconds' \ '--min-print-speed[specify minimum print speed]:minimum print speed in mm/s' \ '--disable-fan-first-layers[specify number of bottom layers to print before activating fan]:number of bottom layers' \ '(--fan-always-on --no-fan-always-on)'--{no-,}fan-always-on'[disable/enable deactivation of fan]' \ \ '--skirts[specify number of skirts]:number of skirts' \ '--skirt-distance[specify distance between innermost skirt and object]:distance between innermost skirt and object in mm' \ '--skirt-height[specify number of skirt layers]:number of skirt layers' \ '--brim-width[specify brim width]:width of brim in mm' \ \ '--scale[specify object scaling factor]:object scaling factor in percent' \ '--rotate[specify object rotation angle]:object rotation angle in degrees' \ '(--duplicate-grid)--duplicate[specify number of duplicates for auto-arrange]:number of duplicates for auto-arrange' \ '(--duplicate-grid)--bed-size[specify bed size for auto-arrange]:bed size for auto-arrange in mm,mm' \ '(--duplicate --bed-size)--duplicate-grid[specify number of duplicates for grid arrangement]:number of duplicates for grid arrangement as x,y' \ '--duplicate-distance[specify distance between duplicates]:distance between duplicates in mm' \ \ '(--complete-objects --no-complete-objects)'--{no-,}complete-objects'[disable/enable completion of each object before starting a new one]' \ '--extruder-clearance-radius[specify radius above which extruder will not collide with anything]:radius in mm' \ '--extruder-clearance-height[specify maximum vertical extruder depth]:maximum vertical extruder depth in mm' \ \ '--notes[specify notes to be added as comments to the output file]:notes' \ \ '--extrusion-width[specify extrusion width]:extrusion width in mm or % of --layer-height' \ '--first-layer-extrusion-width[specify extrusion width for first layer]:first layer extrusion width in mm or % og --layer-height' \ '--perimeters-extrusion-width[specify extrusion width for perimeters]:perimeter extrusion width in mm or % of --layer-height' \ '--infill-extrusion-width[specify extrusion width for infill]:infill extrusion width in mm or % of --layer-height' \ '--support-material-extrusion-width[specify extrusion width for support material]:support material extrusion width in mm or % of --layer-height' \ '--bridge-flow-ratio[specify multiplier for extrusion when bridging]:bridge extrusion multiplier' \ \ '*:input file:_files -g "*.(#i)(stl|obj|amf|xml|prusa)(-.)"' # Local Variables: *** # mode:sh *** # End: *** Slic3r-version_1.39.1/xs/000077500000000000000000000000001324354444700151555ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/CMakeLists.txt000066400000000000000000000502521324354444700177210ustar00rootroot00000000000000# Enable C++11 language standard. set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Add our own cmake module path. list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/) if (CMAKE_SYSTEM_NAME STREQUAL "Linux") # Workaround for an old CMake, which does not understand CMAKE_CXX_STANDARD. add_compile_options(-std=c++11 -Wall) endif() if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUXX) # Adding -fext-numeric-literals to enable GCC extensions on definitions of quad float literals, which are required by Boost. add_compile_options(-fext-numeric-literals) endif() # Where all the bundled libraries reside? set(LIBDIR ${CMAKE_CURRENT_SOURCE_DIR}/src/) # For the bundled boost libraries (boost::nowide) include_directories(${LIBDIR}) # For libslic3r.h include_directories(${LIBDIR}/libslic3r) #set(CMAKE_INCLUDE_CURRENT_DIR ON) if(WIN32) # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking. add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB) # -D_ITERATOR_DEBUG_LEVEL) endif() add_definitions(-DwxUSE_UNICODE -D_UNICODE -DUNICODE) add_library(libslic3r STATIC ${LIBDIR}/libslic3r/BoundingBox.cpp ${LIBDIR}/libslic3r/BoundingBox.hpp ${LIBDIR}/libslic3r/BridgeDetector.cpp ${LIBDIR}/libslic3r/BridgeDetector.hpp ${LIBDIR}/libslic3r/ClipperUtils.cpp ${LIBDIR}/libslic3r/ClipperUtils.hpp ${LIBDIR}/libslic3r/Config.cpp ${LIBDIR}/libslic3r/Config.hpp ${LIBDIR}/libslic3r/EdgeGrid.cpp ${LIBDIR}/libslic3r/EdgeGrid.hpp ${LIBDIR}/libslic3r/ExPolygon.cpp ${LIBDIR}/libslic3r/ExPolygon.hpp ${LIBDIR}/libslic3r/ExPolygonCollection.cpp ${LIBDIR}/libslic3r/ExPolygonCollection.hpp ${LIBDIR}/libslic3r/Extruder.cpp ${LIBDIR}/libslic3r/Extruder.hpp ${LIBDIR}/libslic3r/ExtrusionEntity.cpp ${LIBDIR}/libslic3r/ExtrusionEntity.hpp ${LIBDIR}/libslic3r/ExtrusionEntityCollection.cpp ${LIBDIR}/libslic3r/ExtrusionEntityCollection.hpp ${LIBDIR}/libslic3r/ExtrusionSimulator.cpp ${LIBDIR}/libslic3r/ExtrusionSimulator.hpp ${LIBDIR}/libslic3r/Fill/Fill.cpp ${LIBDIR}/libslic3r/Fill/Fill.hpp ${LIBDIR}/libslic3r/Fill/Fill3DHoneycomb.cpp ${LIBDIR}/libslic3r/Fill/Fill3DHoneycomb.hpp ${LIBDIR}/libslic3r/Fill/FillBase.cpp ${LIBDIR}/libslic3r/Fill/FillBase.hpp ${LIBDIR}/libslic3r/Fill/FillConcentric.cpp ${LIBDIR}/libslic3r/Fill/FillConcentric.hpp ${LIBDIR}/libslic3r/Fill/FillHoneycomb.cpp ${LIBDIR}/libslic3r/Fill/FillHoneycomb.hpp ${LIBDIR}/libslic3r/Fill/FillPlanePath.cpp ${LIBDIR}/libslic3r/Fill/FillPlanePath.hpp ${LIBDIR}/libslic3r/Fill/FillRectilinear.cpp ${LIBDIR}/libslic3r/Fill/FillRectilinear.hpp ${LIBDIR}/libslic3r/Fill/FillRectilinear2.cpp ${LIBDIR}/libslic3r/Fill/FillRectilinear2.hpp ${LIBDIR}/libslic3r/Fill/FillRectilinear3.cpp ${LIBDIR}/libslic3r/Fill/FillRectilinear3.hpp ${LIBDIR}/libslic3r/Flow.cpp ${LIBDIR}/libslic3r/Flow.hpp ${LIBDIR}/libslic3r/Format/AMF.cpp ${LIBDIR}/libslic3r/Format/AMF.hpp ${LIBDIR}/libslic3r/Format/OBJ.cpp ${LIBDIR}/libslic3r/Format/OBJ.hpp ${LIBDIR}/libslic3r/Format/objparser.cpp ${LIBDIR}/libslic3r/Format/objparser.hpp ${LIBDIR}/libslic3r/Format/PRUS.cpp ${LIBDIR}/libslic3r/Format/PRUS.hpp ${LIBDIR}/libslic3r/Format/STL.cpp ${LIBDIR}/libslic3r/Format/STL.hpp ${LIBDIR}/libslic3r/GCode/Analyzer.cpp ${LIBDIR}/libslic3r/GCode/Analyzer.hpp ${LIBDIR}/libslic3r/GCode/CoolingBuffer.cpp ${LIBDIR}/libslic3r/GCode/CoolingBuffer.hpp ${LIBDIR}/libslic3r/GCode/PressureEqualizer.cpp ${LIBDIR}/libslic3r/GCode/PressureEqualizer.hpp ${LIBDIR}/libslic3r/GCode/PrintExtents.cpp ${LIBDIR}/libslic3r/GCode/PrintExtents.hpp ${LIBDIR}/libslic3r/GCode/SpiralVase.cpp ${LIBDIR}/libslic3r/GCode/SpiralVase.hpp ${LIBDIR}/libslic3r/GCode/ToolOrdering.cpp ${LIBDIR}/libslic3r/GCode/ToolOrdering.hpp ${LIBDIR}/libslic3r/GCode/WipeTower.hpp ${LIBDIR}/libslic3r/GCode/WipeTowerPrusaMM.cpp ${LIBDIR}/libslic3r/GCode/WipeTowerPrusaMM.hpp ${LIBDIR}/libslic3r/GCode.cpp ${LIBDIR}/libslic3r/GCode.hpp ${LIBDIR}/libslic3r/GCodeReader.cpp ${LIBDIR}/libslic3r/GCodeReader.hpp ${LIBDIR}/libslic3r/GCodeSender.cpp ${LIBDIR}/libslic3r/GCodeSender.hpp ${LIBDIR}/libslic3r/GCodeTimeEstimator.cpp ${LIBDIR}/libslic3r/GCodeTimeEstimator.hpp ${LIBDIR}/libslic3r/GCodeWriter.cpp ${LIBDIR}/libslic3r/GCodeWriter.hpp ${LIBDIR}/libslic3r/Geometry.cpp ${LIBDIR}/libslic3r/Geometry.hpp ${LIBDIR}/libslic3r/Int128.hpp # ${LIBDIR}/libslic3r/KdTree.hpp ${LIBDIR}/libslic3r/Layer.cpp ${LIBDIR}/libslic3r/Layer.hpp ${LIBDIR}/libslic3r/LayerRegion.cpp ${LIBDIR}/libslic3r/libslic3r.h ${LIBDIR}/libslic3r/Line.cpp ${LIBDIR}/libslic3r/Line.hpp ${LIBDIR}/libslic3r/Model.cpp ${LIBDIR}/libslic3r/Model.hpp ${LIBDIR}/libslic3r/MotionPlanner.cpp ${LIBDIR}/libslic3r/MotionPlanner.hpp ${LIBDIR}/libslic3r/MultiPoint.cpp ${LIBDIR}/libslic3r/MultiPoint.hpp ${LIBDIR}/libslic3r/MutablePriorityQueue.hpp ${LIBDIR}/libslic3r/PerimeterGenerator.cpp ${LIBDIR}/libslic3r/PerimeterGenerator.hpp ${LIBDIR}/libslic3r/PlaceholderParser.cpp ${LIBDIR}/libslic3r/PlaceholderParser.hpp ${LIBDIR}/libslic3r/Point.cpp ${LIBDIR}/libslic3r/Point.hpp ${LIBDIR}/libslic3r/Polygon.cpp ${LIBDIR}/libslic3r/Polygon.hpp ${LIBDIR}/libslic3r/Polyline.cpp ${LIBDIR}/libslic3r/Polyline.hpp ${LIBDIR}/libslic3r/PolylineCollection.cpp ${LIBDIR}/libslic3r/PolylineCollection.hpp ${LIBDIR}/libslic3r/Print.cpp ${LIBDIR}/libslic3r/Print.hpp ${LIBDIR}/libslic3r/PrintConfig.cpp ${LIBDIR}/libslic3r/PrintConfig.hpp ${LIBDIR}/libslic3r/PrintObject.cpp ${LIBDIR}/libslic3r/PrintRegion.cpp ${LIBDIR}/libslic3r/Slicing.cpp ${LIBDIR}/libslic3r/Slicing.hpp ${LIBDIR}/libslic3r/SlicingAdaptive.cpp ${LIBDIR}/libslic3r/SlicingAdaptive.hpp ${LIBDIR}/libslic3r/SupportMaterial.cpp ${LIBDIR}/libslic3r/SupportMaterial.hpp ${LIBDIR}/libslic3r/Surface.cpp ${LIBDIR}/libslic3r/Surface.hpp ${LIBDIR}/libslic3r/SurfaceCollection.cpp ${LIBDIR}/libslic3r/SurfaceCollection.hpp ${LIBDIR}/libslic3r/SVG.cpp ${LIBDIR}/libslic3r/SVG.hpp ${LIBDIR}/libslic3r/TriangleMesh.cpp ${LIBDIR}/libslic3r/TriangleMesh.hpp # ${LIBDIR}/libslic3r/utils.cpp ${LIBDIR}/libslic3r/Utils.hpp ) add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/AppConfig.cpp ${LIBDIR}/slic3r/GUI/AppConfig.hpp ${LIBDIR}/slic3r/GUI/3DScene.cpp ${LIBDIR}/slic3r/GUI/3DScene.hpp ${LIBDIR}/slic3r/GUI/GLShader.cpp ${LIBDIR}/slic3r/GUI/GLShader.hpp ${LIBDIR}/slic3r/GUI/Preset.cpp ${LIBDIR}/slic3r/GUI/Preset.hpp ${LIBDIR}/slic3r/GUI/PresetBundle.cpp ${LIBDIR}/slic3r/GUI/PresetBundle.hpp ${LIBDIR}/slic3r/GUI/PresetHints.cpp ${LIBDIR}/slic3r/GUI/PresetHints.hpp ${LIBDIR}/slic3r/GUI/GUI.cpp ${LIBDIR}/slic3r/GUI/GUI.hpp ) add_library(admesh STATIC ${LIBDIR}/admesh/connect.cpp ${LIBDIR}/admesh/normals.cpp ${LIBDIR}/admesh/shared.cpp ${LIBDIR}/admesh/stl.h ${LIBDIR}/admesh/stl_io.cpp ${LIBDIR}/admesh/stlinit.cpp ${LIBDIR}/admesh/util.cpp ) add_library(clipper STATIC ${LIBDIR}/clipper.cpp ${LIBDIR}/clipper.hpp ) add_library(polypartition STATIC ${LIBDIR}/polypartition.cpp ${LIBDIR}/polypartition.h ) add_library(poly2tri STATIC ${LIBDIR}/poly2tri/common/shapes.cc ${LIBDIR}/poly2tri/common/shapes.h ${LIBDIR}/poly2tri/common/utils.h ${LIBDIR}/poly2tri/poly2tri.h ${LIBDIR}/poly2tri/sweep/advancing_front.cc ${LIBDIR}/poly2tri/sweep/advancing_front.h ${LIBDIR}/poly2tri/sweep/cdt.cc ${LIBDIR}/poly2tri/sweep/cdt.h ${LIBDIR}/poly2tri/sweep/sweep.cc ${LIBDIR}/poly2tri/sweep/sweep.h ${LIBDIR}/poly2tri/sweep/sweep_context.cc ${LIBDIR}/poly2tri/sweep/sweep_context.h ) add_library(nowide STATIC ${LIBDIR}/boost/nowide/args.hpp ${LIBDIR}/boost/nowide/cenv.hpp ${LIBDIR}/boost/nowide/config.hpp ${LIBDIR}/boost/nowide/convert.hpp ${LIBDIR}/boost/nowide/cstdio.hpp ${LIBDIR}/boost/nowide/cstdlib.hpp ${LIBDIR}/boost/nowide/filebuf.hpp ${LIBDIR}/boost/nowide/fstream.hpp ${LIBDIR}/boost/nowide/integration/filesystem.hpp ${LIBDIR}/boost/nowide/iostream.cpp ${LIBDIR}/boost/nowide/iostream.hpp ${LIBDIR}/boost/nowide/stackstring.hpp ${LIBDIR}/boost/nowide/system.hpp ${LIBDIR}/boost/nowide/utf8_codecvt.hpp ${LIBDIR}/boost/nowide/windows.hpp ) add_library(Shiny STATIC ${LIBDIR}/Shiny/Shiny.h ${LIBDIR}/Shiny/ShinyConfig.h ${LIBDIR}/Shiny/ShinyData.h ${LIBDIR}/Shiny/ShinyMacros.h ${LIBDIR}/Shiny/ShinyManager.c ${LIBDIR}/Shiny/ShinyManager.h ${LIBDIR}/Shiny/ShinyNode.c ${LIBDIR}/Shiny/ShinyNode.h ${LIBDIR}/Shiny/ShinyNodePool.c ${LIBDIR}/Shiny/ShinyNodePool.h ${LIBDIR}/Shiny/ShinyNodeState.c ${LIBDIR}/Shiny/ShinyNodeState.h ${LIBDIR}/Shiny/ShinyOutput.c ${LIBDIR}/Shiny/ShinyOutput.h ${LIBDIR}/Shiny/ShinyPrereqs.h ${LIBDIR}/Shiny/ShinyTools.c ${LIBDIR}/Shiny/ShinyTools.h ${LIBDIR}/Shiny/ShinyVersion.h ${LIBDIR}/Shiny/ShinyZone.c ${LIBDIR}/Shiny/ShinyZone.h ) # Generate the Slic3r Perl module (XS) typemap file. set(MyTypemap ${CMAKE_CURRENT_BINARY_DIR}/typemap) add_custom_command( OUTPUT ${MyTypemap} DEPENDS ${CMAKE_CURRENT_LIST_DIR}/xsp/my.map COMMAND ${PERL_EXECUTABLE} -MExtUtils::Typemaps -MExtUtils::Typemaps::Basic -e "$typemap = ExtUtils::Typemaps->new(file => \"${CMAKE_CURRENT_LIST_DIR}/xsp/my.map\"); $typemap->merge(typemap => ExtUtils::Typemaps::Basic->new); $typemap->write(file => \"${MyTypemap}\")" VERBATIM ) # Generate the Slic3r Perl module (XS) main.xs file. set(XS_MAIN_XS ${CMAKE_CURRENT_BINARY_DIR}/main.xs) set(XSP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/xsp) #FIXME list the dependecies explicitely, add dependency on the typemap. set(XS_XSP_FILES ${XSP_DIR}/BoundingBox.xsp ${XSP_DIR}/BridgeDetector.xsp ${XSP_DIR}/Clipper.xsp ${XSP_DIR}/Config.xsp ${XSP_DIR}/ExPolygon.xsp ${XSP_DIR}/ExPolygonCollection.xsp ${XSP_DIR}/ExtrusionEntityCollection.xsp ${XSP_DIR}/ExtrusionLoop.xsp ${XSP_DIR}/ExtrusionMultiPath.xsp ${XSP_DIR}/ExtrusionPath.xsp ${XSP_DIR}/ExtrusionSimulator.xsp ${XSP_DIR}/Filler.xsp ${XSP_DIR}/Flow.xsp ${XSP_DIR}/GCode.xsp ${XSP_DIR}/GCodeSender.xsp ${XSP_DIR}/Geometry.xsp ${XSP_DIR}/GUI.xsp ${XSP_DIR}/GUI_AppConfig.xsp ${XSP_DIR}/GUI_3DScene.xsp ${XSP_DIR}/GUI_Preset.xsp ${XSP_DIR}/Layer.xsp ${XSP_DIR}/Line.xsp ${XSP_DIR}/Model.xsp ${XSP_DIR}/MotionPlanner.xsp ${XSP_DIR}/PerimeterGenerator.xsp ${XSP_DIR}/PlaceholderParser.xsp ${XSP_DIR}/Point.xsp ${XSP_DIR}/Polygon.xsp ${XSP_DIR}/Polyline.xsp ${XSP_DIR}/PolylineCollection.xsp ${XSP_DIR}/Print.xsp ${XSP_DIR}/Surface.xsp ${XSP_DIR}/SurfaceCollection.xsp ${XSP_DIR}/TriangleMesh.xsp ${XSP_DIR}/XS.xsp ) foreach (file ${XS_XSP_FILES}) if (MSVC) # Visual Studio C compiler has issues with FILE pragmas containing quotes. set(INCLUDE_COMMANDS "${INCLUDE_COMMANDS}INCLUDE_COMMAND: $^X -MExtUtils::XSpp::Cmd -e xspp -- -t ${CMAKE_CURRENT_LIST_DIR}/xsp/typemap.xspt ${file}\n") else () set(INCLUDE_COMMANDS "${INCLUDE_COMMANDS}INCLUDE_COMMAND: $^X -MExtUtils::XSpp::Cmd -e xspp -- -t \"${CMAKE_CURRENT_LIST_DIR}/xsp/typemap.xspt\" \"${file}\"\n") endif () endforeach () configure_file(main.xs.in ${XS_MAIN_XS} @ONLY) # Insert INCLUDE_COMMANDS into main.xs # Generate the Slic3r Perl module (XS) XS.cpp file. #FIXME add the dependency on main.xs and typemap. set(XS_MAIN_CPP ${CMAKE_CURRENT_BINARY_DIR}/XS.cpp) add_custom_command( OUTPUT ${XS_MAIN_CPP} DEPENDS ${MyTypemap} ${XS_XSP_FILES} ${CMAKE_CURRENT_LIST_DIR}/xsp/typemap.xspt COMMAND COMMAND xsubpp -typemap typemap -output ${XS_MAIN_CPP} -hiertype ${XS_MAIN_XS} ) # Define the Perl XS shared library. if(APPLE) set(XS_SHARED_LIBRARY_TYPE MODULE) else() set(XS_SHARED_LIBRARY_TYPE SHARED) endif() add_library(XS ${XS_SHARED_LIBRARY_TYPE} ${XS_MAIN_CPP} ${LIBDIR}/libslic3r/utils.cpp ${LIBDIR}/slic3r/GUI/wxPerlIface.cpp ${LIBDIR}/perlglue.cpp ${LIBDIR}/ppport.h ${LIBDIR}/xsinit.h ${CMAKE_CURRENT_LIST_DIR}/xsp/my.map # mytype.map is empty. Is it required by Build.PL or the Perl xspp module? ${CMAKE_CURRENT_LIST_DIR}/xsp/mytype.map # Used by Perl xsubpp to generate XS.cpp ${CMAKE_CURRENT_LIST_DIR}/xsp/typemap.xspt ) if(APPLE) set_target_properties(XS PROPERTIES BUNDLE TRUE) # Ignore undefined symbols of the perl interpreter, they will be found in the caller image. target_link_libraries(XS "-undefined dynamic_lookup") endif() target_link_libraries(XS libslic3r libslic3r_gui admesh clipper nowide polypartition poly2tri) if(SLIC3R_PROFILE) target_link_libraries(XS Shiny) endif() # Add the OpenGL and GLU libraries. if (SLIC3R_GUI) if (MSVC) target_link_libraries(XS OpenGL32.Lib GlU32.Lib) elseif (MINGW) target_link_libraries(XS -lopengl32) elseif (APPLE) target_link_libraries(XS "-framework OpenGL") else () target_link_libraries(XS -lGL -lGLU) endif () endif () target_include_directories(XS PRIVATE src src/libslic3r) # Local include directories target_compile_definitions(XS PRIVATE -DSLIC3RXS) set_target_properties(XS PROPERTIES PREFIX "") # Prevent cmake from generating libXS.so instead of XS.so if (APPLE) # add_compile_options(-stdlib=libc++) # add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE) target_link_libraries(XS "-framework IOKit" "-framework CoreFoundation" -lc++) elseif (MSVC) target_link_libraries(XS ) else () target_link_libraries(XS -lstdc++) endif () # Windows specific stuff if (WIN32) target_compile_definitions(XS PRIVATE -DNOGDI -DNOMINMAX -DHAS_BOOL) endif () ## Configuration flags if (SLIC3R_GUI) message("Slic3r will be built with GUI support") target_compile_definitions(XS PRIVATE -DSLIC3R_GUI) endif () if (SLIC3R_PROFILE) message("Slic3r will be built with a Shiny invasive profiler") add_definitions(-DSLIC3R_PROFILE) endif () if (SLIC3R_HAS_BROKEN_CROAK) target_compile_definitions(XS PRIVATE -DSLIC3R_HAS_BROKEN_CROAK) endif () if (CMAKE_BUILD_TYPE MATCHES DEBUG) target_compile_definitions(XS PRIVATE -DSLIC3R_DEBUG -DDEBUG -D_DEBUG) else () target_compile_definitions(XS PRIVATE -DNDEBUG) endif () # Perl specific stuff find_package(PerlLibs REQUIRED) set(PerlEmbed_DEBUG 1) find_package(PerlEmbed REQUIRED) target_include_directories(XS PRIVATE ${PERL_INCLUDE_PATH}) target_compile_options(XS PRIVATE ${PerlEmbed_CCFLAGS}) # If the Perl is compiled with optimization off, disable optimization over the whole project. if (WIN32 AND ";${PerlEmbed_CCFLAGS};" MATCHES ";[-/]Od;") message(STATUS "Perl compiled without optimization. Disabling optimization for the Slic3r build.") message("Old CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}") message("Old CMAKE_CXX_FLAGS_RELWITHDEBINFO: ${CMAKE_CXX_FLAGS_RELEASE}") message("Old CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_RELEASE "/MD /Od /Zi /EHsc /DNDEBUG") set(CMAKE_C_FLAGS_RELEASE "/MD /Od /Zi /DNDEBUG") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /EHsc /DNDEBUG") set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Od /Zi /DNDEBUG") set(CMAKE_CXX_FLAGS "/MD /Od /Zi /EHsc /DNDEBUG") set(CMAKE_C_FLAGS "/MD /Od /Zi /DNDEBUG") endif() # The following line will add -fPIC on Linux to make the XS.so rellocable. add_definitions(${PerlEmbed_CCCDLFLAGS}) if (WIN32) target_link_libraries(XS ${PERL_LIBRARY}) endif() ## REQUIRED packages # Find and configure boost if(SLIC3R_STATIC) # Use static boost libraries. set(Boost_USE_STATIC_LIBS ON) # Use boost libraries linked statically to the C++ runtime. # set(Boost_USE_STATIC_RUNTIME ON) endif() find_package(Boost REQUIRED COMPONENTS system filesystem thread log locale regex) if(Boost_FOUND) include_directories(${Boost_INCLUDE_DIRS}) target_link_libraries(XS ${Boost_LIBRARIES}) if (APPLE) # BOOST_ASIO_DISABLE_KQUEUE : prevents a Boost ASIO bug on OS X: https://svn.boost.org/trac/boost/ticket/5339 add_definitions(-DBOOST_ASIO_DISABLE_KQUEUE) endif() if(NOT SLIC3R_STATIC) add_definitions(-DBOOST_LOG_DYN_LINK) endif() endif() # Find and configure intel-tbb if(SLIC3R_STATIC) set(TBB_STATIC 1) endif() set(TBB_DEBUG 1) find_package(TBB REQUIRED) include_directories(${TBB_INCLUDE_DIRS}) add_definitions(${TBB_DEFINITIONS}) if(MSVC) # Suppress implicit linking of the TBB libraries by the Visual Studio compiler. add_definitions(-D__TBB_NO_IMPLICIT_LINKAGE) endif() # The Intel TBB library will use the std::exception_ptr feature of C++11. add_definitions(-DTBB_USE_CAPTURED_EXCEPTION=0) target_link_libraries(XS ${TBB_LIBRARIES}) # Find and configure wxWidgets if (SLIC3R_PRUSACONTROL) set(wxWidgets_UseAlienWx 1) if (wxWidgets_UseAlienWx) set(AlienWx_DEBUG 1) find_package(AlienWx REQUIRED COMPONENTS base core adv) include_directories(${AlienWx_INCLUDE_DIRS}) #add_compile_options(${AlienWx_CXX_FLAGS}) add_definitions(${AlienWx_DEFINITIONS}) set(wxWidgets_LIBRARIES ${AlienWx_LIBRARIES}) else () find_package(wxWidgets REQUIRED COMPONENTS base core adv) include(${wxWidgets_USE_FILE}) endif () add_definitions(-DSLIC3R_GUI -DSLIC3R_PRUS) target_link_libraries(XS ${wxWidgets_LIBRARIES}) endif() ## OPTIONAL packages # Find eigen3 or use bundled version if (NOT SLIC3R_STATIC) find_package(Eigen3) endif () if (NOT Eigen3_FOUND) set(Eigen3_FOUND 1) set(EIGEN3_INCLUDE_DIR ${LIBDIR}/eigen/) endif () include_directories(${EIGEN3_INCLUDE_DIR}) # Find expat or use bundled version # Always use the system libexpat on Linux. if (NOT SLIC3R_STATIC OR CMAKE_SYSTEM_NAME STREQUAL "Linux") find_package(EXPAT) endif () if (NOT EXPAT_FOUND) add_library(expat STATIC ${LIBDIR}/expat/xmlparse.c ${LIBDIR}/expat/xmlrole.c ${LIBDIR}/expat/xmltok.c ) set(EXPAT_FOUND 1) set(EXPAT_INCLUDE_DIRS ${LIBDIR}/expat/) set(EXPAT_LIBRARIES expat) endif () include_directories(${EXPAT_INCLUDE_DIRS}) target_link_libraries(XS ${EXPAT_LIBRARIES}) # Find glew or use bundled version if (NOT SLIC3R_STATIC) find_package(GLEW) endif () if (NOT GLEW_FOUND) add_library(glew STATIC ${LIBDIR}/glew/src/glew.c) set(GLEW_FOUND 1) set(GLEW_INCLUDE_DIRS ${LIBDIR}/glew/include/) set(GLEW_LIBRARIES glew) add_definitions(-DGLEW_STATIC) endif () include_directories(${GLEW_INCLUDE_DIRS}) target_link_libraries(XS ${GLEW_LIBRARIES}) # Install the XS.pm and XS.{so,dll,bundle} into the local-lib directory. set(PERL_LOCAL_LIB_DIR "${PROJECT_SOURCE_DIR}/local-lib/lib/perl5/${PerlEmbed_ARCHNAME}") add_custom_command( TARGET XS POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${PERL_LOCAL_LIB_DIR}/auto/Slic3r/XS/" COMMAND ${CMAKE_COMMAND} -E copy "$" "${PERL_LOCAL_LIB_DIR}/auto/Slic3r/XS/" COMMAND ${CMAKE_COMMAND} -E make_directory "${PERL_LOCAL_LIB_DIR}/Slic3r/" COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/xs/lib/Slic3r/XS.pm" "${PERL_LOCAL_LIB_DIR}/Slic3r/" ) if(APPLE) add_custom_command( TARGET XS POST_BUILD COMMAND ${CMAKE_COMMAND} -E rename "${PERL_LOCAL_LIB_DIR}/auto/Slic3r/XS/XS" "${PERL_LOCAL_LIB_DIR}/auto/Slic3r/XS/XS.bundle" ) endif() # Create a slic3r executable add_executable(slic3r ${PROJECT_SOURCE_DIR}/src/slic3r.cpp) target_include_directories(XS PRIVATE src src/libslic3r) target_link_libraries(slic3r libslic3r libslic3r_gui admesh ${Boost_LIBRARIES} clipper ${EXPAT_LIBRARIES} ${GLEW_LIBRARIES} polypartition poly2tri ${TBB_LIBRARIES} ${wxWidgets_LIBRARIES}) if(SLIC3R_PROFILE) target_link_libraries(Shiny) endif() if (APPLE) target_link_libraries(slic3r "-framework IOKit" "-framework CoreFoundation" -lc++) elseif (NOT MSVC) target_link_libraries(slic3r -lstdc++) endif () # Installation install(TARGETS XS DESTINATION lib/slic3r-prusa3d/auto/Slic3r/XS) install(FILES lib/Slic3r/XS.pm DESTINATION lib/slic3r-prusa3d/Slic3r) Slic3r-version_1.39.1/xs/lib/000077500000000000000000000000001324354444700157235ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/lib/Slic3r/000077500000000000000000000000001324354444700170625ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/lib/Slic3r/XS.pm000066400000000000000000000173371324354444700177650ustar00rootroot00000000000000package Slic3r::XS; use warnings; use strict; our $VERSION = '0.01'; # We have to load these modules in order to have Wx.pm find the correct paths # for wxWidgets dlls on MSW. # We avoid loading these on OS X because Wx::Load() initializes a Wx App # automatically and it steals focus even when we're not running Slic3r in GUI mode. # TODO: only load these when compiling with GUI support BEGIN { if ($^O eq 'MSWin32') { eval "use Wx"; # eval "use Wx::Html"; eval "use Wx::Print"; # because of some Wx bug, thread creation fails if we don't have this (looks like Wx::Printout is hard-coded in some thread cleanup code) } } use Carp qw(); use XSLoader; XSLoader::load(__PACKAGE__, $VERSION); package Slic3r::Line; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::Point; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::Point3; use overload '@{}' => sub { [ $_[0]->x, $_[0]->y, $_[0]->z ] }, #, 'fallback' => 1; sub pp { my ($self) = @_; return [ @$self ]; } package Slic3r::Pointf; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::Pointf3; use overload '@{}' => sub { [ $_[0]->x, $_[0]->y, $_[0]->z ] }, #, 'fallback' => 1; sub pp { my ($self) = @_; return [ @$self ]; } package Slic3r::ExPolygon; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::Polyline; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::Polyline::Collection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::Polygon; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::ExPolygon::Collection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::ExtrusionPath::Collection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; sub new { my ($class, @paths) = @_; my $self = $class->_new; $self->append(@paths); return $self; } package Slic3r::ExtrusionLoop; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; sub new_from_paths { my ($class, @paths) = @_; my $loop = $class->new; $loop->append($_) for @paths; return $loop; } package Slic3r::ExtrusionMultiPath; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; package Slic3r::ExtrusionPath; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; sub new { my ($class, %args) = @_; return $class->_new( $args{polyline}, # required $args{role}, # required $args{mm3_per_mm} // die("Missing required mm3_per_mm in ExtrusionPath constructor"), $args{width} // -1, $args{height} // -1, ); } sub clone { my ($self, %args) = @_; return __PACKAGE__->_new( $args{polyline} // $self->polyline, $args{role} // $self->role, $args{mm3_per_mm} // $self->mm3_per_mm, $args{width} // $self->width, $args{height} // $self->height, ); } package Slic3r::ExtrusionSimulator; sub new { my ($class, %args) = @_; return $class->_new(); } package Slic3r::Filler; sub fill_surface { my ($self, $surface, %args) = @_; $self->set_density($args{density}) if defined($args{density}); $self->set_dont_connect($args{dont_connect}) if defined($args{dont_connect}); $self->set_dont_adjust($args{dont_adjust}) if defined($args{dont_adjust}); $self->set_complete($args{complete}) if defined($args{complete}); return $self->_fill_surface($surface); } package Slic3r::Flow; sub new { my ($class, %args) = @_; my $self = $class->_new( @args{qw(width height nozzle_diameter)}, ); $self->set_bridge($args{bridge} // 0); return $self; } sub new_from_width { my ($class, %args) = @_; return $class->_new_from_width( @args{qw(role width nozzle_diameter layer_height bridge_flow_ratio)}, ); } package Slic3r::Surface; sub new { my ($class, %args) = @_; # defensive programming: make sure no negative bridge_angle is supplied die "Error: invalid negative bridge_angle\n" if defined $args{bridge_angle} && $args{bridge_angle} < 0; return $class->_new( $args{expolygon} // (die "Missing required expolygon\n"), $args{surface_type} // (die "Missing required surface_type\n"), $args{thickness} // -1, $args{thickness_layers} // 1, $args{bridge_angle} // -1, $args{extra_perimeters} // 0, ); } sub clone { my ($self, %args) = @_; return (ref $self)->_new( delete $args{expolygon} // $self->expolygon, delete $args{surface_type} // $self->surface_type, delete $args{thickness} // $self->thickness, delete $args{thickness_layers} // $self->thickness_layers, delete $args{bridge_angle} // $self->bridge_angle, delete $args{extra_perimeters} // $self->extra_perimeters, ); } package Slic3r::Surface::Collection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; sub new { my ($class, @surfaces) = @_; my $self = $class->_new; $self->append($_) for @surfaces; return $self; } package Slic3r::Print::SupportMaterial2; sub new { my ($class, %args) = @_; return $class->_new( $args{print_config}, # required $args{object_config}, # required $args{first_layer_flow}, # required $args{flow}, # required $args{interface_flow}, # required $args{soluble_interface} // 0 ); } package Slic3r::GUI::_3DScene::GLShader; sub CLONE_SKIP { 1 } package Slic3r::GUI::_3DScene::GLVolume::Collection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; sub CLONE_SKIP { 1 } package Slic3r::GUI::PresetCollection; use overload '@{}' => sub { $_[0]->arrayref }, 'fallback' => 1; sub CLONE_SKIP { 1 } package main; for my $class (qw( Slic3r::BridgeDetector Slic3r::Config Slic3r::Config::Full Slic3r::Config::GCode Slic3r::Config::Print Slic3r::Config::PrintObject Slic3r::Config::PrintRegion Slic3r::Config::Static Slic3r::ExPolygon Slic3r::ExPolygon::Collection Slic3r::ExtrusionLoop Slic3r::ExtrusionMultiPath Slic3r::ExtrusionPath Slic3r::ExtrusionPath::Collection Slic3r::ExtrusionSimulator Slic3r::Filler Slic3r::Flow Slic3r::GCode Slic3r::GCode::PlaceholderParser Slic3r::Geometry::BoundingBox Slic3r::Geometry::BoundingBoxf Slic3r::Geometry::BoundingBoxf3 Slic3r::GUI::_3DScene::GLVolume Slic3r::GUI::Preset Slic3r::GUI::PresetCollection Slic3r::Layer Slic3r::Layer::Region Slic3r::Layer::Support Slic3r::Line Slic3r::Linef3 Slic3r::Model Slic3r::Model::Instance Slic3r::Model::Material Slic3r::Model::Object Slic3r::Model::Volume Slic3r::Point Slic3r::Point3 Slic3r::Pointf Slic3r::Pointf3 Slic3r::Polygon Slic3r::Polyline Slic3r::Polyline::Collection Slic3r::Print Slic3r::Print::Object Slic3r::Print::Region Slic3r::Print::State Slic3r::Surface Slic3r::Surface::Collection Slic3r::Print::SupportMaterial2 Slic3r::TriangleMesh )) { no strict 'refs'; my $ref_class = $class . "::Ref"; eval "package $ref_class; our \@ISA = '$class'; sub DESTROY {};"; } 1; Slic3r-version_1.39.1/xs/main.xs.in000066400000000000000000000007211324354444700170620ustar00rootroot00000000000000#include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" #undef do_open #undef do_close #ifdef __cplusplus } #endif #ifdef _WIN32 #undef XS_EXTERNAL #define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name) #endif /* MSVC */ MODULE = Slic3r::XS PACKAGE = Slic3r::XS @INCLUDE_COMMANDS@Slic3r-version_1.39.1/xs/src/000077500000000000000000000000001324354444700157445ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/Shiny/000077500000000000000000000000001324354444700170365ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/Shiny/Shiny.h000066400000000000000000000025221324354444700203020ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_H #define SHINY_H /*---------------------------------------------------------------------------*/ #include "ShinyMacros.h" #ifdef SLIC3R_PROFILE #include "ShinyManager.h" #endif /* SLIC3R_PROFILE */ #endif /* SHINY_H */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyConfig.h000066400000000000000000000036201324354444700214300ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_CONFIG_H #define SHINY_CONFIG_H /*---------------------------------------------------------------------------*/ /* if SHINY_LOOKUP_RATE is defined to TRUE then Shiny will record the success of its hash function. This is useful for debugging. Default is FALSE. */ #ifndef SHINY_LOOKUP_RATE // #define SHINY_LOOKUP_RATE FALSE #endif /* if SHINY_HAS_ENABLED is defined to TRUE then Shiny can be enabled and disabled at runtime. TODO: bla bla... */ #ifndef SHINY_HAS_ENABLED // #define SHINY_HAS_ENABLED FALSE #endif /* TODO: */ #define SHINY_OUTPUT_MODE_FLAT 0x1 /* TODO: */ #define SHINY_OUTPUT_MODE_TREE 0x2 /* TODO: */ #define SHINY_OUTPUT_MODE_BOTH 0x3 /* TODO: */ #ifndef SHINY_OUTPUT_MODE #define SHINY_OUTPUT_MODE SHINY_OUTPUT_MODE_BOTH #endif #endif /* SHINY_CONFIG_H */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyData.h000066400000000000000000000057301324354444700211000ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_DATA_H #define SHINY_DATA_H #include "ShinyPrereqs.h" #ifdef __cplusplus extern "C" { #endif /*---------------------------------------------------------------------------*/ typedef struct { uint32_t entryCount; shinytick_t selfTicks; } ShinyLastData; /*---------------------------------------------------------------------------*/ typedef struct { shinytick_t cur; float avg; } ShinyTickData; typedef struct { uint32_t cur; float avg; } ShinyCountData; typedef struct { ShinyCountData entryCount; ShinyTickData selfTicks; ShinyTickData childTicks; } ShinyData; SHINY_INLINE shinytick_t ShinyData_totalTicksCur(const ShinyData *self) { return self->selfTicks.cur + self->childTicks.cur; } SHINY_INLINE float ShinyData_totalTicksAvg(const ShinyData *self) { return self->selfTicks.avg + self->childTicks.avg; } SHINY_INLINE void ShinyData_computeAverage(ShinyData *self, float a_damping) { self->entryCount.avg = self->entryCount.cur + a_damping * (self->entryCount.avg - self->entryCount.cur); self->selfTicks.avg = self->selfTicks.cur + a_damping * (self->selfTicks.avg - self->selfTicks.cur); self->childTicks.avg = self->childTicks.cur + a_damping * (self->childTicks.avg - self->childTicks.cur); } SHINY_INLINE void ShinyData_copyAverage(ShinyData *self) { self->entryCount.avg = (float) self->entryCount.cur; self->selfTicks.avg = (float) self->selfTicks.cur; self->childTicks.avg = (float) self->childTicks.cur; } SHINY_INLINE void ShinyData_clearAll(ShinyData *self) { self->entryCount.cur = 0; self->entryCount.avg = 0; self->selfTicks.cur = 0; self->selfTicks.avg = 0; self->childTicks.cur = 0; self->childTicks.avg = 0; } SHINY_INLINE void ShinyData_clearCurrent(ShinyData *self) { self->entryCount.cur = 0; self->selfTicks.cur = 0; self->childTicks.cur = 0; } #if __cplusplus } /* end of extern "C" */ #endif #endif /* SHINY_DATA_H */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyMacros.h000066400000000000000000000214131324354444700214470ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_MACROS_H #define SHINY_MACROS_H #ifdef SLIC3R_PROFILE #include "ShinyManager.h" /*---------------------------------------------------------------------------*/ /* public preprocessors */ #define PROFILE_UPDATE() \ ShinyManager_update(&Shiny_instance) #define PROFILE_SET_DAMPING(floatfrom0to1) \ Shiny_instance.damping = (floatfrom0to1); #define PROFILE_GET_DAMPING() \ (Shiny_instance.damping) #define PROFILE_OUTPUT(filename) \ ShinyManager_output(&Shiny_instance, (filename)) #define PROFILE_OUTPUT_STREAM(stream) \ ShinyManager_outputToStream(&Shiny_instance, (stream)) #ifdef __cplusplus #define PROFILE_GET_TREE_STRING() \ ShinyManager_outputTreeToString(&Shiny_instance) #define PROFILE_GET_FLAT_STRING() \ ShinyManager_outputFlatToString(&Shiny_instance) #endif /* __cplusplus */ #define PROFILE_DESTROY() \ ShinyManager_destroy(&Shiny_instance) #define PROFILE_CLEAR() \ ShinyManager_clear(&Shiny_instance) #define PROFILE_SORT_ZONES() \ ShinyManager_sortZones(&Shiny_instance) /*---------------------------------------------------------------------------*/ /* public preprocessors */ #define PROFILE_GET_TOTAL_TICKS_CUR() \ ShinyData_totalTicksCur(&Shiny_instance.rootZone.data) #define PROFILE_GET_TOTAL_TICKS() \ ShinyData_totalTicksAvg(&Shiny_instance.rootZone.data) #define PROFILE_GET_PROFILED_TICKS_CUR() \ (Shiny_instance.rootZone.data.selfTicks.cur) #define PROFILE_GET_PROFILED_TICKS() \ (Shiny_instance.rootZone.data.selfTicks.avg) #define PROFILE_GET_UNPROFILED_TICKS_CUR() \ (Shiny_instance.rootZone.data.childTicks.cur) #define PROFILE_GET_UNPROFILED_TICKS() \ (Shiny_instance.rootZone.data.childTicks.avg) #define PROFILE_GET_SHARED_TOTAL_TICKS_CUR(name) \ ShinyData_totalTicksCur(&(_PROFILE_ID_ZONE_SHARED(name).data)) #define PROFILE_GET_SHARED_TOTAL_TICKS(name) \ ShinyData_totalTicksAvg(&(_PROFILE_ID_ZONE_SHARED(name).data)) #define PROFILE_GET_SHARED_SELF_TICKS_CUR(name) \ (_PROFILE_ID_ZONE_SHARED(name).data.selfTicks.cur) #define PROFILE_GET_SHARED_SELF_TICKS(name) \ (_PROFILE_ID_ZONE_SHARED(name).data.selfTicks.avg) /*---------------------------------------------------------------------------*/ /* public preprocessors */ #define PROFILE_IS_SHARED_SELF_BELOW(name, floatfrom0to1) \ ShinyManager_isZoneSelfTimeBelow( \ &Shiny_instance, _PROFILE_ID_ZONE_SHARED(name), floatfrom0to1) #define PROFILE_IS_SHARED_TOTAL_BELOW(name, floatfrom0to1) \ ShinyManager_isZoneTotalTimeBelow( \ &Shiny_instance, _PROFILE_ID_ZONE_SHARED(name), floatfrom0to1) /*---------------------------------------------------------------------------*/ /* public preprocessors */ #define PROFILE_END() \ ShinyManager_endCurNode(&Shiny_instance) /*---------------------------------------------------------------------------*/ /* public preprocessors */ #define PROFILE_BEGIN( name ) \ \ static _PROFILE_ZONE_DEFINE(_PROFILE_ID_ZONE(name), #name); \ _PROFILE_ZONE_BEGIN(_PROFILE_ID_ZONE(name)) /*---------------------------------------------------------------------------*/ /* public preprocessors */ #ifdef __cplusplus #define PROFILE_BLOCK( name ) \ \ _PROFILE_BLOCK_DEFINE(_PROFILE_ID_BLOCK()); \ PROFILE_BEGIN(name) #endif /* __cplusplus */ /*---------------------------------------------------------------------------*/ /* public preprocessors */ #ifdef __cplusplus #define PROFILE_FUNC() \ \ _PROFILE_BLOCK_DEFINE(_PROFILE_ID_BLOCK()); \ static _PROFILE_ZONE_DEFINE(_PROFILE_ID_ZONE_FUNC(), __FUNCTION__); \ _PROFILE_ZONE_BEGIN(_PROFILE_ID_ZONE_FUNC()) #endif /* __cplusplus */ /*---------------------------------------------------------------------------*/ /* public preprocessors */ #define PROFILE_CODE( code ) \ \ do { \ static _PROFILE_ZONE_DEFINE(_PROFILE_ID_ZONE_CODE(), #code); \ _PROFILE_ZONE_BEGIN(_PROFILE_ID_ZONE_CODE()); \ { code; } \ PROFILE_END(); \ } while(0) /*---------------------------------------------------------------------------*/ /* public preprocessors */ #define PROFILE_SHARED_EXTERN( name ) \ \ _PROFILE_ZONE_DECLARE(extern, _PROFILE_ID_ZONE_SHARED(name)) /*---------------------------------------------------------------------------*/ /* public preprocessors */ #define PROFILE_SHARED_DEFINE( name ) \ \ _PROFILE_ZONE_DEFINE(_PROFILE_ID_ZONE_SHARED(name), #name) /*---------------------------------------------------------------------------*/ /* public preprocessors */ #define PROFILE_SHARED_BEGIN( name ) \ \ _PROFILE_ZONE_BEGIN(_PROFILE_ID_ZONE_SHARED(name)) /*---------------------------------------------------------------------------*/ /* public preprocessors */ #ifdef __cplusplus #define PROFILE_SHARED_BLOCK( name ) \ \ _PROFILE_BLOCK_DEFINE(_PROFILE_ID_BLOCK()); \ _PROFILE_ZONE_BEGIN(_PROFILE_ID_ZONE_SHARED(name)) #endif /* __cplusplus */ /*---------------------------------------------------------------------------*/ /* public preprocessors */ #ifdef SHINY_HAS_ENABLED #define PROFILE_SET_ENABLED( boolean ) \ Shiny_instance.enabled = boolean #endif /*---------------------------------------------------------------------------*/ /* internal preprocessors */ #define _PROFILE_ID_ZONE( name ) __ShinyZone_##name #define _PROFILE_ID_ZONE_FUNC() __ShinyZoneFunc #define _PROFILE_ID_ZONE_CODE() __ShinyZoneCode #define _PROFILE_ID_ZONE_SHARED( name ) name##__ShinyZoneShared #define _PROFILE_ID_BLOCK() __ShinyBlock /*---------------------------------------------------------------------------*/ /* internal preprocessor */ #define _PROFILE_ZONE_DEFINE( id, string ) \ \ ShinyZone id = { \ NULL, SHINY_ZONE_STATE_HIDDEN, string, \ { { 0, 0 }, { 0, 0 }, { 0, 0 } } \ } /*---------------------------------------------------------------------------*/ /* internal preprocessor */ #define _PROFILE_ZONE_DECLARE( prefix, id ) \ \ prefix ShinyZone id /*---------------------------------------------------------------------------*/ /* internal preprocessor */ #define _PROFILE_BLOCK_DEFINE( id ) \ \ ShinyEndNodeOnDestruction SHINY_UNUSED id /*---------------------------------------------------------------------------*/ /* internal preprocessor */ #define _PROFILE_ZONE_BEGIN( id ) \ \ do { \ static ShinyNodeCache cache = &_ShinyNode_dummy; \ ShinyManager_lookupAndBeginNode(&Shiny_instance, &cache, &id); \ } while(0) /*---------------------------------------------------------------------------*/ #else /* SLIC3R_PROFILE */ #define PROFILE_UPDATE() #define PROFILE_SET_DAMPING(x) #define PROFILE_GET_DAMPING() 0.0f #define PROFILE_OUTPUT(x) #define PROFILE_OUTPUT_STREAM(x) #define PROFILE_CLEAR() #define PROFILE_GET_TREE_STRING() std::string() #define PROFILE_GET_FLAT_STRING() std::string() #define PROFILE_DESTROY() #define PROFILE_BEGIN(name) #define PROFILE_BLOCK(name) #define PROFILE_FUNC() #define PROFILE_CODE(code) do { code; } while (0) #define PROFILE_SHARED_GLOBAL(name) #define PROFILE_SHARED_MEMBER(name) #define PROFILE_SHARED_DEFINE(name) #define PROFILE_SHARED_BEGIN(name) #define PROFILE_SHARED_BLOCK(name) #define PROFILE_SET_ENABLED(boolean) #endif /* SLIC3R_PROFILE */ #endif /* SHINY_MACROS_H */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyManager.c000066400000000000000000000277261324354444700216050ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef SLIC3R_PROFILE #include "ShinyManager.h" #include #include #include #include /*---------------------------------------------------------------------------*/ #define TABLE_SIZE_INIT 256 /*---------------------------------------------------------------------------*/ ShinyManager Shiny_instance = { #if SHINY_HAS_ENABLED == TRUE /* enabled = */ false, #endif /* _lastTick = */ 0, /* _curNode = */ &Shiny_instance.rootNode, /* _tableMask = */ 0, /* _nodeTable = */ _ShinyManager_dummyNodeTable, #if SHINY_LOOKUP_RATE == TRUE /* _lookupCount = */ 0, /* _lookupSuccessCount = */ 0, #endif /* _tableSize = */ 1, /* nodeCount = */ 1, /* zoneCount = */ 1, /* _lastZone = */ &Shiny_instance.rootZone, /* _lastNodePool = */ NULL, /* _firstNodePool = */ NULL, /* rootNode = */ { /* _last = */ { 0, 0 }, /* zone = */ &Shiny_instance.rootZone, /* parent = */ &Shiny_instance.rootNode, /* nextSibling = */ NULL, /* firstChild = */ NULL, /* lastChild = */ NULL, /* childCount = */ 0, /* entryLevel = */ 0, /* _cache = */ NULL, /* data = */ { { 0, 0 }, { 0, 0 }, { 0, 0 } } }, /* rootZone = */ { /* next = */ NULL, /* _state = */ SHINY_ZONE_STATE_HIDDEN, /* name = */ "", /* data = */ { { 0, 0 }, { 0, 0 }, { 0, 0 } } }, /* damping = */ 0.f, // Damping disabled, every PROFILE_UPDATE will be performed from scratch. Original value: 0.9f /* _initialized = */ FALSE, /* _firstUpdate = */ TRUE }; ShinyNode* _ShinyManager_dummyNodeTable[] = { NULL }; /*---------------------------------------------------------------------------*/ #if SHINY_COMPILER == SHINY_COMPILER_MSVC # pragma warning (push) # pragma warning (disable: 4311) #endif /* primary hash function */ SHINY_INLINE uint32_t hash_value(void* a_pParent, void* a_pZone) { uint32_t a = (uint32_t) a_pParent + (uint32_t) a_pZone; // uint32_t a = *reinterpret_cast(&a_pParent) + *reinterpret_cast(&a_pZone); a = (a+0x7ed55d16) + (a<<12); a = (a^0xc761c23c) ^ (a>>19); return a; } /* * secondary hash used as index offset: force it to be odd * so it's relatively prime to the power-of-two table size */ SHINY_INLINE uint32_t hash_offset(uint32_t a) { return ((a << 8) + (a >> 4)) | 1; } #if SHINY_COMPILER == SHINY_COMPILER_MSVC # pragma warning (pop) #endif /*---------------------------------------------------------------------------*/ void ShinyManager_preLoad(ShinyManager *self) { if (!self->_initialized) { _ShinyManager_init(self); _ShinyManager_createNodeTable(self, TABLE_SIZE_INIT); _ShinyManager_createNodePool(self, TABLE_SIZE_INIT / 2); } } /*---------------------------------------------------------------------------*/ void ShinyManager_update(ShinyManager *self) { #if SHINY_HAS_ENABLED == TRUE if (!enabled) return; #endif _ShinyManager_appendTicksToCurNode(self); ShinyZone_preUpdateChain(&self->rootZone); if (self->_firstUpdate || self->damping == 0) { self->_firstUpdate = FALSE; ShinyNode_updateTreeClean(&self->rootNode); ShinyZone_updateChainClean(&self->rootZone); } else { ShinyNode_updateTree(&self->rootNode, self->damping); ShinyZone_updateChain(&self->rootZone, self->damping); } } /*---------------------------------------------------------------------------*/ void ShinyManager_updateClean(ShinyManager *self) { #if SHINY_HAS_ENABLED == TRUE if (!enabled) return; #endif _ShinyManager_appendTicksToCurNode(self); ShinyZone_preUpdateChain(&self->rootZone); self->_firstUpdate = FALSE; ShinyNode_updateTreeClean(&self->rootNode); ShinyZone_updateChainClean(&self->rootZone); } /*---------------------------------------------------------------------------*/ void ShinyManager_clear(ShinyManager *self) { ShinyManager_destroy(self); ShinyManager_preLoad(self); } /*---------------------------------------------------------------------------*/ void ShinyManager_destroy(ShinyManager *self) { ShinyManager_destroyNodes(self); ShinyManager_resetZones(self); _ShinyManager_uninit(self); } /*---------------------------------------------------------------------------*/ ShinyNode* _ShinyManager_lookupNode(ShinyManager *self, ShinyNodeCache *a_cache, ShinyZone *a_zone) { uint32_t nHash = hash_value(self->_curNode, a_zone); uint32_t nIndex = nHash & self->_tableMask; ShinyNode* pNode = self->_nodeTable[nIndex]; _ShinyManager_incLookup(self); _ShinyManager_incLookupSuccess(self); if (pNode) { uint32_t nStep; if (ShinyNode_isEqual(pNode, self->_curNode, a_zone)) return pNode; /* found it! */ /* hash collision: */ /* compute a secondary hash function for stepping */ nStep = hash_offset(nHash); for (;;) { _ShinyManager_incLookup(self); nIndex = (nIndex + nStep) & self->_tableMask; pNode = self->_nodeTable[nIndex]; if (!pNode) break; /* found empty slot */ else if (ShinyNode_isEqual(pNode, self->_curNode, a_zone)) return pNode; /* found it! */ } /* loop is guaranteed to end because the hash table is never full */ } if (a_zone->_state == SHINY_ZONE_STATE_HIDDEN) { /* zone is not initialized */ ShinyZone_init(a_zone, self->_lastZone); self->_lastZone = a_zone; self->zoneCount++; if (self->_initialized == FALSE) { /* first time init */ _ShinyManager_init(self); _ShinyManager_createNodeTable(self, TABLE_SIZE_INIT); _ShinyManager_createNodePool(self, TABLE_SIZE_INIT / 2); /* initialization has invalidated nIndex * we must compute nIndex again */ return _ShinyManager_createNode(self, a_cache, a_zone); } } /* Althouth nodeCount is not updated * it includes rootNode so it adds up. * * check if we need to grow the table * we keep it at most 1/2 full to be very fast */ if (self->_tableSize < 2 * self->nodeCount) { _ShinyManager_resizeNodeTable(self, 2 * self->_tableSize); _ShinyManager_resizeNodePool(self, self->nodeCount - 1); /* resize has invalidated nIndex * we must compute nIndex again */ return _ShinyManager_createNode(self, a_cache, a_zone); } self->nodeCount++; { ShinyNode* pNewNode = ShinyNodePool_newItem(self->_lastNodePool); ShinyNode_init(pNewNode, self->_curNode, a_zone, a_cache); self->_nodeTable[nIndex] = pNewNode; return pNewNode; } } /*---------------------------------------------------------------------------*/ void _ShinyManager_insertNode(ShinyManager *self, ShinyNode* a_pNode) { uint32_t nHash = hash_value(a_pNode->parent, a_pNode->zone); uint32_t nIndex = nHash & self->_tableMask; if (self->_nodeTable[nIndex]) { uint32_t nStep = hash_offset(nHash); while (self->_nodeTable[nIndex]) nIndex = (nIndex + nStep) & self->_tableMask; } self->_nodeTable[nIndex] = a_pNode; } /*---------------------------------------------------------------------------*/ ShinyNode* _ShinyManager_createNode(ShinyManager *self, ShinyNodeCache* a_cache, ShinyZone* a_pZone) { ShinyNode* pNewNode = ShinyNodePool_newItem(self->_lastNodePool); ShinyNode_init(pNewNode, self->_curNode, a_pZone, a_cache); self->nodeCount++; _ShinyManager_insertNode(self, pNewNode); return pNewNode; } /*---------------------------------------------------------------------------*/ void _ShinyManager_createNodePool(ShinyManager *self, uint32_t a_nCount) { self->_firstNodePool = ShinyNodePool_create(a_nCount); self->_lastNodePool = self->_firstNodePool; } /*---------------------------------------------------------------------------*/ void _ShinyManager_resizeNodePool(ShinyManager *self, uint32_t a_nCount) { ShinyNodePool* pPool = ShinyNodePool_create(a_nCount); self->_lastNodePool->nextPool = pPool; self->_lastNodePool = pPool; } /*---------------------------------------------------------------------------*/ void _ShinyManager_createNodeTable(ShinyManager *self, uint32_t a_nCount) { self->_tableSize = a_nCount; self->_tableMask = a_nCount - 1; self->_nodeTable = (ShinyNodeTable*) malloc(sizeof(ShinyNode) * a_nCount); memset(self->_nodeTable, 0, a_nCount * sizeof(ShinyNode*)); } /*---------------------------------------------------------------------------*/ void _ShinyManager_resizeNodeTable(ShinyManager *self, uint32_t a_nCount) { ShinyNodePool* pPool; free(self->_nodeTable); _ShinyManager_createNodeTable(self, a_nCount); pPool = self->_firstNodePool; while (pPool) { ShinyNode *pIter = ShinyNodePool_firstItem(pPool); while (pIter != pPool->_nextItem) _ShinyManager_insertNode(self, pIter++); pPool = pPool->nextPool; } } /*---------------------------------------------------------------------------*/ void ShinyManager_resetZones(ShinyManager *self) { ShinyZone_resetChain(&self->rootZone); self->_lastZone = &self->rootZone; self->zoneCount = 1; } /*---------------------------------------------------------------------------*/ void ShinyManager_destroyNodes(ShinyManager *self) { if (self->_firstNodePool) { ShinyNodePool_destroy(self->_firstNodePool); self->_firstNodePool = NULL; } if (self->_nodeTable != _ShinyManager_dummyNodeTable) { free(self->_nodeTable); self->_nodeTable = _ShinyManager_dummyNodeTable; self->_tableSize = 1; self->_tableMask = 0; } self->_curNode = &self->rootNode; self->nodeCount = 1; _ShinyManager_init(self); } /*---------------------------------------------------------------------------*/ const char* ShinyManager_getOutputErrorString(ShinyManager *self) { if (self->_firstUpdate) return "!!! Profile data must first be updated !!!"; else if (!self->_initialized) return "!!! No profiles where executed !!!"; else return NULL; } /*---------------------------------------------------------------------------*/ #if SHINY_COMPILER == SHINY_COMPILER_MSVC # pragma warning (push) # pragma warning (disable: 4996) #endif int ShinyManager_output(ShinyManager *self, const char *a_filename) { if (!a_filename) { ShinyManager_outputToStream(self, stdout); } else { FILE *file = fopen(a_filename, "w"); if (!file) return FALSE; ShinyManager_outputToStream(self, file); fclose(file); } return TRUE; } #if SHINY_COMPILER == SHINY_COMPILER_MSVC # pragma warning (pop) #endif /*---------------------------------------------------------------------------*/ void ShinyManager_outputToStream(ShinyManager *self, FILE *a_stream) { const char *error = ShinyManager_getOutputErrorString(self); if (error) { fwrite(error, 1, strlen(error), a_stream); fwrite("\n\n", 1, 2, a_stream); return; } #if SHINY_OUTPUT_MODE & SHINY_OUTPUT_MODE_FLAT ShinyManager_sortZones(self); { int size = ShinyPrintZonesSize(self->zoneCount); char *buffer = (char*) malloc(size); ShinyPrintZones(buffer, &self->rootZone); fwrite(buffer, 1, size - 1, a_stream); fwrite("\n\n", 1, 2, a_stream); free(buffer); } #endif #if SHINY_OUTPUT_MODE & SHINY_OUTPUT_MODE_TREE { int size = ShinyPrintNodesSize(self->nodeCount); char *buffer = (char*) malloc(size); ShinyPrintNodes(buffer, &self->rootNode); fwrite(buffer, 1, size - 1, a_stream); fwrite("\n\n", 1, 2, a_stream); free(buffer); } #endif } #endif /* SLIC3R_PROFILE */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyManager.h000066400000000000000000000177341324354444700216100ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_MANAGER_H #define SHINY_MANAGER_H #include "ShinyZone.h" #include "ShinyNode.h" #include "ShinyNodePool.h" #include "ShinyTools.h" #include "ShinyOutput.h" #include #ifdef __cplusplus extern "C" { #endif /*---------------------------------------------------------------------------*/ typedef struct { #ifdef SHINY_HAS_ENABLED bool enabled; #endif shinytick_t _lastTick; ShinyNode* _curNode; uint32_t _tableMask; /* = _tableSize - 1 */ ShinyNodeTable* _nodeTable; #ifdef SHINY_LOOKUP_RATE uint64_t _lookupCount; uint64_t _lookupSuccessCount; #endif uint32_t _tableSize; uint32_t nodeCount; uint32_t zoneCount; ShinyZone* _lastZone; ShinyNodePool* _lastNodePool; ShinyNodePool* _firstNodePool; ShinyNode rootNode; ShinyZone rootZone; float damping; int _initialized; int _firstUpdate; } ShinyManager; /*---------------------------------------------------------------------------*/ extern ShinyNode* _ShinyManager_dummyNodeTable[]; extern ShinyManager Shiny_instance; /*---------------------------------------------------------------------------*/ SHINY_INLINE void _ShinyManager_appendTicksToCurNode(ShinyManager *self) { shinytick_t curTick; ShinyGetTicks(&curTick); ShinyNode_appendTicks(self->_curNode, curTick - self->_lastTick); self->_lastTick = curTick; } SHINY_API ShinyNode* _ShinyManager_lookupNode(ShinyManager *self, ShinyNodeCache* a_cache, ShinyZone* a_zone); SHINY_API void _ShinyManager_createNodeTable(ShinyManager *self, uint32_t a_count); SHINY_API void _ShinyManager_resizeNodeTable(ShinyManager *self, uint32_t a_count); SHINY_API void _ShinyManager_createNodePool(ShinyManager *self, uint32_t a_count); SHINY_API void _ShinyManager_resizeNodePool(ShinyManager *self, uint32_t a_count); SHINY_API ShinyNode* _ShinyManager_createNode(ShinyManager *self, ShinyNodeCache* a_cache, ShinyZone* a_pZone); SHINY_API void _ShinyManager_insertNode(ShinyManager *self, ShinyNode* a_pNode); SHINY_INLINE void _ShinyManager_init(ShinyManager *self) { self->_initialized = TRUE; self->rootNode._last.entryCount = 1; self->rootNode._last.selfTicks = 0; ShinyGetTicks(&self->_lastTick); } SHINY_INLINE void _ShinyManager_uninit(ShinyManager *self) { self->_initialized = FALSE; ShinyNode_clear(&self->rootNode); self->rootNode.parent = &self->rootNode; self->rootNode.zone = &self->rootZone; } #ifdef SHINY_LOOKUP_RATE SHINY_INLINE void _ShinyManager_incLookup(ShinyManager *self) { self->_lookupCount++; } SHINY_INLINE void _ShinyManager_incLookupSuccess(ShinyManager *self) { self->_lookupSuccessCount++; } SHINY_INLINE float ShinyManager_lookupRate(const ShinyManager *self) { return ((float) self->_lookupSuccessCount) / ((float) self->_lookupCount); } #else SHINY_INLINE void _ShinyManager_incLookup(ShinyManager * self) { self = self; } SHINY_INLINE void _ShinyManager_incLookupSuccess(ShinyManager * self) { self = self; } SHINY_INLINE float ShinyManager_lookupRate(const ShinyManager * self) { self = self; return -1; } #endif SHINY_API void ShinyManager_resetZones(ShinyManager *self); SHINY_API void ShinyManager_destroyNodes(ShinyManager *self); SHINY_INLINE float ShinyManager_tableUsage(const ShinyManager *self) { return ((float) self->nodeCount) / ((float) self->_tableSize); } SHINY_INLINE uint32_t ShinyManager_allocMemInBytes(const ShinyManager *self) { return self->_tableSize * sizeof(ShinyNode*) + (self->_firstNodePool)? ShinyNodePool_memoryUsageChain(self->_firstNodePool) : 0; } SHINY_INLINE void ShinyManager_beginNode(ShinyManager *self, ShinyNode* a_node) { ShinyNode_beginEntry(a_node); _ShinyManager_appendTicksToCurNode(self); self->_curNode = a_node; } SHINY_INLINE void ShinyManager_lookupAndBeginNode(ShinyManager *self, ShinyNodeCache* a_cache, ShinyZone* a_zone) { #ifdef SHINY_HAS_ENABLED if (!self->enabled) return; #endif if (self->_curNode != (*a_cache)->parent) *a_cache = _ShinyManager_lookupNode(self, a_cache, a_zone); ShinyManager_beginNode(self, *a_cache); } SHINY_INLINE void ShinyManager_endCurNode(ShinyManager *self) { #ifdef SHINY_HAS_ENABLED if (!self->enabled) return; #endif _ShinyManager_appendTicksToCurNode(self); self->_curNode = self->_curNode->parent; } /**/ SHINY_API void ShinyManager_preLoad(ShinyManager *self); SHINY_API void ShinyManager_updateClean(ShinyManager *self); SHINY_API void ShinyManager_update(ShinyManager *self); SHINY_API void ShinyManager_clear(ShinyManager *self); SHINY_API void ShinyManager_destroy(ShinyManager *self); SHINY_INLINE void ShinyManager_sortZones(ShinyManager *self) { if (self->rootZone.next) self->_lastZone = ShinyZone_sortChain(&self->rootZone.next); } SHINY_API const char* ShinyManager_getOutputErrorString(ShinyManager *self); SHINY_API int ShinyManager_output(ShinyManager *self, const char *a_filename); SHINY_API void ShinyManager_outputToStream(ShinyManager *self, FILE *stream); #if __cplusplus } /* end of extern "C" */ SHINY_INLINE std::string ShinyManager_outputTreeToString(ShinyManager *self) { const char* error = ShinyManager_getOutputErrorString(self); if (error) return error; else return ShinyNodesToString(&self->rootNode, self->nodeCount); } SHINY_INLINE std::string ShinyManager_outputFlatToString(ShinyManager *self) { const char* error = ShinyManager_getOutputErrorString(self); if (error) return error; ShinyManager_sortZones(self); return ShinyZonesToString(&self->rootZone, self->zoneCount); } extern "C" { /* end of c++ */ #endif SHINY_INLINE int ShinyManager_isZoneSelfTimeBelow(ShinyManager *self, ShinyZone* a_zone, float a_percentage) { return a_percentage * (float) self->rootZone.data.childTicks.cur <= (float) a_zone->data.selfTicks.cur; } SHINY_INLINE int ShinyManager_isZoneTotalTimeBelow(ShinyManager *self, ShinyZone* a_zone, float a_percentage) { return a_percentage * (float) self->rootZone.data.childTicks.cur <= (float) ShinyData_totalTicksCur(&a_zone->data); } /**/ SHINY_INLINE void ShinyManager_enumerateNodes(ShinyManager *self, void (*a_func)(const ShinyNode*)) { ShinyNode_enumerateNodes(&self->rootNode, a_func); } SHINY_INLINE void ShinyManager_enumerateZones(ShinyManager *self, void (*a_func)(const ShinyZone*)) { ShinyZone_enumerateZones(&self->rootZone, a_func); } #if __cplusplus } /* end of extern "C" */ template void ShinyManager_enumerateNodes(ShinyManager *self, T* a_this, void (T::*a_func)(const ShinyNode*)) { ShinyNode_enumerateNodes(&self->rootNode, a_this, a_func); } template void ShinyManager_enumerateZones(ShinyManager *self, T* a_this, void (T::*a_func)(const ShinyZone*)) { ShinyZone_enumerateZones(&self->rootZone, a_this, a_func); } extern "C" { /* end of c++ */ #endif /*---------------------------------------------------------------------------*/ #if __cplusplus } /* end of extern "C" */ class ShinyEndNodeOnDestruction { public: SHINY_INLINE ~ShinyEndNodeOnDestruction() { ShinyManager_endCurNode(&Shiny_instance); } }; #endif #endif /* SHINY_MANAGER_H */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyNode.c000066400000000000000000000065611324354444700211120ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef SLIC3R_PROFILE #include "ShinyNode.h" #include "ShinyZone.h" #include "ShinyNodeState.h" #include /*---------------------------------------------------------------------------*/ ShinyNode _ShinyNode_dummy = { /* _last = */ { 0, 0 }, /* zone = */ NULL, /* parent = */ NULL, /* nextSibling = */ NULL, /* firstChild = */ NULL, /* lastChild = */ NULL }; /*---------------------------------------------------------------------------*/ void ShinyNode_updateTree(ShinyNode* first, float a_damping) { ShinyNodeState *top = NULL; ShinyNode *node = first; for (;;) { do { top = ShinyNodeState_push(top, node); node = node->firstChild; } while (node); for (;;) { node = ShinyNodeState_finishAndGetNext(top, a_damping); top = ShinyNodeState_pop(top); if (node) break; else if (!top) return; } } } /*---------------------------------------------------------------------------*/ void ShinyNode_updateTreeClean(ShinyNode* first) { ShinyNodeState *top = NULL; ShinyNode *node = first; for (;;) { do { top = ShinyNodeState_push(top, node); node = node->firstChild; } while (node); for (;;) { node = ShinyNodeState_finishAndGetNextClean(top); top = ShinyNodeState_pop(top); if (node) break; else if (!top) return; } } } /*---------------------------------------------------------------------------*/ const ShinyNode* ShinyNode_findNextInTree(const ShinyNode* self) { if (self->firstChild) { return self->firstChild; } else if (self->nextSibling) { return self->nextSibling; } else { ShinyNode* pParent = self->parent; while (!ShinyNode_isRoot(pParent)) { if (pParent->nextSibling) return pParent->nextSibling; else pParent = pParent->parent; } return NULL; } } /*---------------------------------------------------------------------------*/ void ShinyNode_clear(ShinyNode* self) { memset(self, 0, sizeof(ShinyNode)); } /*---------------------------------------------------------------------------*/ void ShinyNode_enumerateNodes(const ShinyNode* a_node, void (*a_func)(const ShinyNode*)) { a_func(a_node); if (a_node->firstChild) ShinyNode_enumerateNodes(a_node->firstChild, a_func); if (a_node->nextSibling) ShinyNode_enumerateNodes(a_node->nextSibling, a_func); } #endif /* SLIC3R_PROFILE */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyNode.h000066400000000000000000000075121324354444700211140ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_NODE_H #define SHINY_NODE_H #include "ShinyData.h" #include "ShinyTools.h" #ifdef __cplusplus extern "C" { #endif /*---------------------------------------------------------------------------*/ typedef struct _ShinyNode { ShinyLastData _last; struct _ShinyZone* zone; struct _ShinyNode* parent; struct _ShinyNode* nextSibling; struct _ShinyNode* firstChild; struct _ShinyNode* lastChild; uint32_t childCount; uint32_t entryLevel; ShinyNodeCache* _cache; ShinyData data; } ShinyNode; /*---------------------------------------------------------------------------*/ extern ShinyNode _ShinyNode_dummy; /*---------------------------------------------------------------------------*/ SHINY_INLINE void ShinyNode_addChild(ShinyNode* self, ShinyNode* a_child) { if (self->childCount++) { self->lastChild->nextSibling = a_child; self->lastChild = a_child; } else { self->lastChild = a_child; self->firstChild = a_child; } } SHINY_INLINE void ShinyNode_init(ShinyNode* self, ShinyNode* a_parent, struct _ShinyZone* a_zone, ShinyNodeCache* a_cache) { /* NOTE: all member variables are assumed to be zero when allocated */ self->zone = a_zone; self->parent = a_parent; self->entryLevel = a_parent->entryLevel + 1; ShinyNode_addChild(a_parent, self); self->_cache = a_cache; } SHINY_API void ShinyNode_updateTree(ShinyNode* self, float a_damping); SHINY_API void ShinyNode_updateTreeClean(ShinyNode* self); SHINY_INLINE void ShinyNode_destroy(ShinyNode* self) { *(self->_cache) = &_ShinyNode_dummy; } SHINY_INLINE void ShinyNode_appendTicks(ShinyNode* self, shinytick_t a_elapsedTicks) { self->_last.selfTicks += a_elapsedTicks; } SHINY_INLINE void ShinyNode_beginEntry(ShinyNode* self) { self->_last.entryCount++; } SHINY_INLINE int ShinyNode_isRoot(ShinyNode* self) { return (self->entryLevel == 0); } SHINY_INLINE int ShinyNode_isDummy(ShinyNode* self) { return (self == &_ShinyNode_dummy); } SHINY_INLINE int ShinyNode_isEqual(ShinyNode* self, const ShinyNode* a_parent, const struct _ShinyZone* a_zone) { return (self->parent == a_parent && self->zone == a_zone); } SHINY_API const ShinyNode* ShinyNode_findNextInTree(const ShinyNode* self); SHINY_API void ShinyNode_clear(ShinyNode* self); SHINY_API void ShinyNode_enumerateNodes(const ShinyNode* a_node, void (*a_func)(const ShinyNode*)); #if __cplusplus } /* end of extern "C" */ template void ShinyNode_enumerateNodes(const ShinyNode* a_node, T* a_this, void (T::*a_func)(const ShinyNode*)) { (a_this->*a_func)(a_node); if (a_node->firstChild) ShinyNode_enumerateNodes(a_node->firstChild, a_this, a_func); if (a_node->nextSibling) ShinyNode_enumerateNodes(a_node->nextSibling, a_this, a_func); } #endif /* __cplusplus */ #endif /* SHINY_NODE_H */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyNodePool.c000066400000000000000000000047271324354444700217460ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef SLIC3R_PROFILE #include "ShinyNodePool.h" #include "ShinyTools.h" #include #include /*---------------------------------------------------------------------------*/ ShinyNodePool* ShinyNodePool_create(uint32_t a_items) { ShinyNodePool* pPool = (ShinyNodePool*) malloc(sizeof(ShinyNodePool) + sizeof(ShinyNode) * (a_items - 1)); pPool->nextPool = NULL; pPool->_nextItem = &pPool->_items[0]; pPool->endOfItems = &pPool->_items[a_items]; memset(&pPool->_items[0], 0, a_items * sizeof(ShinyNode)); return pPool; } /*---------------------------------------------------------------------------*/ uint32_t ShinyNodePool_memoryUsageChain(ShinyNodePool *first) { uint32_t bytes = (uint32_t) ((char*) first->endOfItems - (char*) first); ShinyNodePool *pool = first->nextPool; while (pool) { bytes += (uint32_t) ((char*) pool->endOfItems - (char*) pool); pool = pool->nextPool; } return bytes; } /*---------------------------------------------------------------------------*/ void ShinyNodePool_destroy(ShinyNodePool *self) { ShinyNode* firstNode = ShinyNodePool_firstItem(self); ShinyNode* lastNode = self->_nextItem; while (firstNode != lastNode) ShinyNode_destroy(firstNode++); /* TODO: make this into a loop or a tail recursion */ if (self->nextPool) ShinyNodePool_destroy(self->nextPool); free(self); } #endif /* SLIC3R_PROFILE */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyNodePool.h000066400000000000000000000037341324354444700217500ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_NODE_POOL_H #define SHINY_NODE_POOL_H #include "ShinyNode.h" #ifdef __cplusplus extern "C" { #endif /*---------------------------------------------------------------------------*/ typedef struct _ShinyNodePool { struct _ShinyNodePool* nextPool; ShinyNode *_nextItem; ShinyNode *endOfItems; ShinyNode _items[1]; } ShinyNodePool; /*---------------------------------------------------------------------------*/ SHINY_INLINE ShinyNode* ShinyNodePool_firstItem(ShinyNodePool *self) { return &(self->_items[0]); } SHINY_INLINE ShinyNode* ShinyNodePool_newItem(ShinyNodePool *self) { return self->_nextItem++; } ShinyNodePool* ShinyNodePool_create(uint32_t a_items); void ShinyNodePool_destroy(ShinyNodePool *self); uint32_t ShinyNodePool_memoryUsageChain(ShinyNodePool *first); #if __cplusplus } /* end of extern "C" */ #endif #endif /* SHINY_NODE_POOL_H */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyNodeState.c000066400000000000000000000066121324354444700221100ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef SLIC3R_PROFILE #include "ShinyNodeState.h" #include "ShinyNode.h" #include "ShinyZone.h" #include /*---------------------------------------------------------------------------*/ ShinyNodeState* ShinyNodeState_push(ShinyNodeState *a_top, ShinyNode *a_node) { ShinyZone *zone = a_node->zone; ShinyNodeState *self = (ShinyNodeState*) malloc(sizeof(ShinyNodeState)); self->node = a_node; self->_prev = a_top; a_node->data.selfTicks.cur = a_node->_last.selfTicks; a_node->data.entryCount.cur = a_node->_last.entryCount; zone->data.selfTicks.cur += a_node->_last.selfTicks; zone->data.entryCount.cur += a_node->_last.entryCount; a_node->data.childTicks.cur = 0; a_node->_last.selfTicks = 0; a_node->_last.entryCount = 0; self->zoneUpdating = zone->_state != SHINY_ZONE_STATE_UPDATING; if (self->zoneUpdating) { zone->_state = SHINY_ZONE_STATE_UPDATING; } else { zone->data.childTicks.cur -= a_node->data.selfTicks.cur; } return self; } /*---------------------------------------------------------------------------*/ ShinyNodeState* ShinyNodeState_pop(ShinyNodeState *a_top) { ShinyNodeState *prev = a_top->_prev; free(a_top); return prev; } /*---------------------------------------------------------------------------*/ ShinyNode* ShinyNodeState_finishAndGetNext(ShinyNodeState *self, float a_damping) { ShinyNode *node = self->node; ShinyZone *zone = node->zone; if (self->zoneUpdating) { zone->data.childTicks.cur += node->data.childTicks.cur; zone->_state = SHINY_ZONE_STATE_INITIALIZED; } ShinyData_computeAverage(&node->data, a_damping); if (!ShinyNode_isRoot(node)) node->parent->data.childTicks.cur += node->data.selfTicks.cur + node->data.childTicks.cur; return node->nextSibling; } /*---------------------------------------------------------------------------*/ ShinyNode* ShinyNodeState_finishAndGetNextClean(ShinyNodeState *self) { ShinyNode *node = self->node; ShinyZone *zone = node->zone; if (self->zoneUpdating) { zone->data.childTicks.cur += node->data.childTicks.cur; zone->_state = SHINY_ZONE_STATE_INITIALIZED; } ShinyData_copyAverage(&node->data); if (!ShinyNode_isRoot(node)) node->parent->data.childTicks.cur += node->data.selfTicks.cur + node->data.childTicks.cur; return node->nextSibling; } #endif /* SLIC3R_PROFILE */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyNodeState.h000066400000000000000000000035601324354444700221140ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_NODE_STATE_H #define SHINY_NODE_STATE_H #include "ShinyNode.h" #ifdef __cplusplus extern "C" { #endif /*---------------------------------------------------------------------------*/ typedef struct _ShinyNodeState { ShinyNode *node; int zoneUpdating; struct _ShinyNodeState *_prev; } ShinyNodeState; /*---------------------------------------------------------------------------*/ ShinyNodeState* ShinyNodeState_push(ShinyNodeState *a_top, ShinyNode *a_node); ShinyNodeState* ShinyNodeState_pop(ShinyNodeState *a_top); ShinyNode* ShinyNodeState_finishAndGetNext(ShinyNodeState *self, float a_damping); ShinyNode* ShinyNodeState_finishAndGetNextClean(ShinyNodeState *self); #if __cplusplus } /* end of extern "C" */ #endif #endif /* SHINY_NODE_STATE_H */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyOutput.c000066400000000000000000000131571324354444700215240ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef SLIC3R_PROFILE #include "ShinyOutput.h" #include #if SHINY_COMPILER == SHINY_COMPILER_MSVC # pragma warning(disable: 4996) # define snprintf _snprintf # define TRAILING 0 #else # define TRAILING 1 #endif /*---------------------------------------------------------------------------*/ #define OUTPUT_WIDTH_CALL 6 #define OUTPUT_WIDTH_TIME 6 #define OUTPUT_WIDTH_PERC 4 #define OUTPUT_WIDTH_SUM 120 #define OUTPUT_WIDTH_DATA (1+OUTPUT_WIDTH_CALL + 1 + 2*(OUTPUT_WIDTH_TIME+4+OUTPUT_WIDTH_PERC+1) + 1) #define OUTPUT_WIDTH_NAME (OUTPUT_WIDTH_SUM - OUTPUT_WIDTH_DATA) /*---------------------------------------------------------------------------*/ SHINY_INLINE char* printHeader(char *output, const char *a_title) { snprintf(output, OUTPUT_WIDTH_SUM + TRAILING, "%-*s %*s %*s %*s", OUTPUT_WIDTH_NAME, a_title, OUTPUT_WIDTH_CALL, "calls", OUTPUT_WIDTH_TIME+4+OUTPUT_WIDTH_PERC+1, "self time", OUTPUT_WIDTH_TIME+4+OUTPUT_WIDTH_PERC+1, "total time"); return output + OUTPUT_WIDTH_SUM; } /*---------------------------------------------------------------------------*/ SHINY_INLINE char* printData(char *output, const ShinyData *a_data, float a_topercent) { float totalTicksAvg = ShinyData_totalTicksAvg(a_data); const ShinyTimeUnit *selfUnit = ShinyGetTimeUnit(a_data->selfTicks.avg); const ShinyTimeUnit *totalUnit = ShinyGetTimeUnit(totalTicksAvg); snprintf(output, OUTPUT_WIDTH_DATA + TRAILING, " %*.1f %*.0f %-2s %*.0f%% %*.0f %-2s %*.0f%%", OUTPUT_WIDTH_CALL, a_data->entryCount.avg, OUTPUT_WIDTH_TIME, a_data->selfTicks.avg * selfUnit->invTickFreq, selfUnit->suffix, OUTPUT_WIDTH_PERC, a_data->selfTicks.avg * a_topercent, OUTPUT_WIDTH_TIME, totalTicksAvg * totalUnit->invTickFreq, totalUnit->suffix, OUTPUT_WIDTH_PERC, totalTicksAvg * a_topercent); return output + OUTPUT_WIDTH_DATA; } /*---------------------------------------------------------------------------*/ SHINY_INLINE char* printNode(char* output, const ShinyNode *a_node, float a_topercent) { int offset = a_node->entryLevel * 2; snprintf(output, OUTPUT_WIDTH_NAME + TRAILING, "%*s%-*s", offset, "", OUTPUT_WIDTH_NAME - offset, a_node->zone->name); output += OUTPUT_WIDTH_NAME; output = printData(output, &a_node->data, a_topercent); return output; } /*---------------------------------------------------------------------------*/ SHINY_INLINE char* printZone(char* output, const ShinyZone *a_zone, float a_topercent) { snprintf(output, OUTPUT_WIDTH_NAME + TRAILING, "%-*s", OUTPUT_WIDTH_NAME, a_zone->name); output += OUTPUT_WIDTH_NAME; output = printData(output, &a_zone->data, a_topercent); return output; } /*---------------------------------------------------------------------------*/ int ShinyPrintNodesSize(uint32_t a_count) { return (1 + a_count) * (OUTPUT_WIDTH_SUM + 1); } /*---------------------------------------------------------------------------*/ int ShinyPrintZonesSize(uint32_t a_count) { return (1 + a_count) * (OUTPUT_WIDTH_SUM + 1); } /*---------------------------------------------------------------------------*/ void ShinyPrintANode(char* output, const ShinyNode *a_node, const ShinyNode *a_root) { float fTicksToPc = 100.0f / a_root->data.childTicks.avg; output = printNode(output, a_node, fTicksToPc); (*output++) = '\0'; } /*---------------------------------------------------------------------------*/ void ShinyPrintAZone(char* output, const ShinyZone *a_zone, const ShinyZone *a_root) { float fTicksToPc = 100.0f / a_root->data.childTicks.avg; output = printZone(output, a_zone, fTicksToPc); (*output++) = '\0'; } /*---------------------------------------------------------------------------*/ void ShinyPrintNodes(char* output, const ShinyNode *a_root) { float fTicksToPc = 100.0f / a_root->data.childTicks.avg; const ShinyNode *node = a_root; output = printHeader(output, "call tree"); (*output++) = '\n'; for (;;) { output = printNode(output, node, fTicksToPc); node = ShinyNode_findNextInTree(node); if (node) { (*output++) = '\n'; } else { (*output++) = '\0'; return; } } } /*---------------------------------------------------------------------------*/ void ShinyPrintZones(char* output, const ShinyZone *a_root) { float fTicksToPc = 100.0f / a_root->data.childTicks.avg; const ShinyZone *zone = a_root; output = printHeader(output, "sorted list"); (*output++) = '\n'; for (;;) { output = printZone(output, zone, fTicksToPc); zone = zone->next; if (zone) { (*output++) = '\n'; } else { (*output++) = '\0'; return; } } } #endif /* SLIC3R_PROFILE */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyOutput.h000066400000000000000000000045241324354444700215270ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_OUTPUT_H #define SHINY_OUTPUT_H #include "ShinyNode.h" #include "ShinyZone.h" #ifdef __cplusplus extern "C" { #endif /*---------------------------------------------------------------------------*/ SHINY_API int ShinyPrintNodesSize(uint32_t a_count); SHINY_API int ShinyPrintZonesSize(uint32_t a_count); SHINY_API void ShinyPrintANode(char* output, const ShinyNode *a_node, const ShinyNode *a_root); SHINY_API void ShinyPrintAZone(char* output, const ShinyZone *a_zone, const ShinyZone *a_root); SHINY_API void ShinyPrintNodes(char* output, const ShinyNode *a_root); SHINY_API void ShinyPrintZones(char* output, const ShinyZone *a_root); /*---------------------------------------------------------------------------*/ #if __cplusplus } /* end of extern "C" */ #include SHINY_INLINE std::string ShinyNodesToString(const ShinyNode *a_root, uint32_t a_count) { std::string str; str.resize(ShinyPrintNodesSize(a_count) - 1); ShinyPrintNodes(&str[0], a_root); return str; } SHINY_INLINE std::string ShinyZonesToString(const ShinyZone *a_root, uint32_t a_count) { std::string str; str.resize(ShinyPrintZonesSize(a_count) - 1); ShinyPrintZones(&str[0], a_root); return str; } #endif /* __cplusplus */ #endif /* SHINY_OUTPUT_H */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyPrereqs.h000066400000000000000000000066631324354444700216560ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_PREREQS_H #define SHINY_PREREQS_H /*---------------------------------------------------------------------------*/ #ifndef FALSE #define FALSE 0x0 #endif #ifndef TRUE #define TRUE 0x1 #endif #ifndef NULL #define NULL 0 #endif #include "ShinyConfig.h" #include "ShinyVersion.h" #ifdef __cplusplus extern "C" { #endif /*---------------------------------------------------------------------------*/ #define SHINY_PLATFORM_WIN32 0x1 #define SHINY_PLATFORM_POSIX 0x2 #if defined (_WIN32) # define SHINY_PLATFORM SHINY_PLATFORM_WIN32 #else /* ASSUME: POSIX-compliant OS */ # define SHINY_PLATFORM SHINY_PLATFORM_POSIX #endif /*---------------------------------------------------------------------------*/ #define SHINY_COMPILER_MSVC 0x1 #define SHINY_COMPILER_GNUC 0x2 #define SHINY_COMPILER_OTHER 0x3 #if defined (_MSC_VER) # define SHINY_COMPILER SHINY_COMPILER_MSVC #elif defined (__GNUG__) # define SHINY_COMPILER SHINY_COMPILER_GNUC #else # define SHINY_COMPILER SHINY_COMPILER_OTHER #endif /*---------------------------------------------------------------------------*/ #if SHINY_COMPILER == SHINY_COMPILER_GNUC #include #include #endif /*---------------------------------------------------------------------------*/ struct _ShinyNode; struct _ShinyZone; typedef struct _ShinyNode* ShinyNodeCache; typedef struct _ShinyNode* ShinyNodeTable; /*---------------------------------------------------------------------------*/ #define SHINY_API /*---------------------------------------------------------------------------*/ #if SHINY_COMPILER == SHINY_COMPILER_MSVC # define SHINY_INLINE __inline # define SHINY_UNUSED #elif SHINY_COMPILER == SHINY_COMPILER_GNUC # define SHINY_INLINE inline # define SHINY_UNUSED __attribute__((unused)) #elif SHINY_COMPILER == SHINY_COMPILER_OTHER # define SHINY_INLINE inline # define SHINY_UNUSED #endif /*---------------------------------------------------------------------------*/ #if SHINY_COMPILER == SHINY_COMPILER_MSVC typedef int int32_t; typedef unsigned int uint32_t; typedef __int64 int64_t; typedef unsigned __int64 uint64_t; /* #elif defined(__CYGWIN__) typedef u_int32_t uint32_t; typedef u_int64_t uint64_t; */ #endif typedef uint64_t shinytick_t; #if __cplusplus } /* end of extern "C" */ #endif #endif /* SHINY_PREREQS_H */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyTools.c000066400000000000000000000063451324354444700213250ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef SLIC3R_PROFILE #include "ShinyTools.h" #if SHINY_PLATFORM == SHINY_PLATFORM_WIN32 #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX #define NOMINMAX #endif /* NOMINMAX */ #include #elif SHINY_PLATFORM == SHINY_PLATFORM_POSIX #include #endif /*---------------------------------------------------------------------------*/ const ShinyTimeUnit* ShinyGetTimeUnit(float ticks) { static ShinyTimeUnit units[4] = { 0 }; if (units[0].tickFreq == 0) { /* auto initialize first time */ units[0].tickFreq = ShinyGetTickFreq() / 1.0f; units[0].invTickFreq = ShinyGetTickInvFreq() * 1.0f; units[0].suffix = "s"; units[1].tickFreq = ShinyGetTickFreq() / 1000.0f; units[1].invTickFreq = ShinyGetTickInvFreq() * 1000.0f; units[1].suffix = "ms"; units[2].tickFreq = ShinyGetTickFreq() / 1000000.0f; units[2].invTickFreq = ShinyGetTickInvFreq() * 1000000.0f; units[2].suffix = "us"; units[3].tickFreq = ShinyGetTickFreq() / 1000000000.0f; units[3].invTickFreq = ShinyGetTickInvFreq() * 1000000000.0f; units[3].suffix = "ns"; } if (units[0].tickFreq < ticks) return &units[0]; else if (units[1].tickFreq < ticks) return &units[1]; else if (units[2].tickFreq < ticks) return &units[2]; else return &units[3]; } /*---------------------------------------------------------------------------*/ #if SHINY_PLATFORM == SHINY_PLATFORM_WIN32 void ShinyGetTicks(shinytick_t *p) { QueryPerformanceCounter((LARGE_INTEGER*)(p)); } shinytick_t ShinyGetTickFreq(void) { static shinytick_t freq = 0; if (freq == 0) QueryPerformanceFrequency((LARGE_INTEGER*)(&freq)); return freq; } float ShinyGetTickInvFreq(void) { static float invfreq = 0; if (invfreq == 0) invfreq = 1.0f / ShinyGetTickFreq(); return invfreq; } /*---------------------------------------------------------------------------*/ #elif SHINY_PLATFORM == SHINY_PLATFORM_POSIX void ShinyGetTicks(shinytick_t *p) { timeval time; gettimeofday(&time, NULL); *p = time.tv_sec * 1000000 + time.tv_usec; } shinytick_t ShinyGetTickFreq(void) { return 1000000; } float ShinyGetTickInvFreq(void) { return 1.0f / 1000000.0f; } #endif #endif /* SLIC3R_PROFILE */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyTools.h000066400000000000000000000033521324354444700213250ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_TOOLS_H #define SHINY_TOOLS_H #include "ShinyPrereqs.h" #ifdef __cplusplus extern "C" { #endif /*---------------------------------------------------------------------------*/ typedef struct { float tickFreq; float invTickFreq; const char* suffix; } ShinyTimeUnit; /*---------------------------------------------------------------------------*/ SHINY_API const ShinyTimeUnit* ShinyGetTimeUnit(float ticks); SHINY_API void ShinyGetTicks(shinytick_t *p); SHINY_API shinytick_t ShinyGetTickFreq(void); SHINY_API float ShinyGetTickInvFreq(void); #if __cplusplus } /* end of extern "C" */ #endif #endif /* SHINY_TOOLS_H */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyVersion.h000066400000000000000000000030531324354444700216500ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_VERSION_H #define SHINY_VERSION_H /*---------------------------------------------------------------------------*/ #define SHINY_VERSION "2.6 RC1" #define SHINY_SHORTNAME "Shiny" #define SHINY_FULLNAME "Shiny Profiler" #define SHINY_COPYRIGHT "Copyright (C) 2007-2010 Aidin Abedi" #define SHINY_DESCRIPTION "Shiny is a state of the art profiler designed to help finding bottlenecks in your project." #endif /* SHINY_VERSION_H */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyZone.c000066400000000000000000000114731324354444700211360ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef SLIC3R_PROFILE #include "ShinyZone.h" #include /*---------------------------------------------------------------------------*/ void ShinyZone_preUpdateChain(ShinyZone *first) { ShinyZone* zone = first; while (zone) { ShinyData_clearCurrent(&(zone->data)); zone = zone->next; } } /*---------------------------------------------------------------------------*/ void ShinyZone_updateChain(ShinyZone *first, float a_damping) { ShinyZone* zone = first; do { ShinyData_computeAverage(&(zone->data), a_damping); zone = zone->next; } while (zone); } /*---------------------------------------------------------------------------*/ void ShinyZone_updateChainClean(ShinyZone *first) { ShinyZone* zone = first; do { ShinyData_copyAverage(&(zone->data)); zone = zone->next; } while (zone); } /*---------------------------------------------------------------------------*/ void ShinyZone_resetChain(ShinyZone *first) { ShinyZone* zone = first, *temp; do { zone->_state = SHINY_ZONE_STATE_HIDDEN; temp = zone->next; zone->next = NULL; zone = temp; } while (zone); } /*---------------------------------------------------------------------------*/ /* A Linked-List Memory Sort by Philip J. Erdelsky pje@efgh.com http://www.alumni.caltech.edu/~pje/ Modified by Aidin Abedi */ ShinyZone* ShinyZone_sortChain(ShinyZone **first) /* return ptr to last zone */ { ShinyZone *p = *first; unsigned base; unsigned long block_size; struct tape { ShinyZone *first, *last; unsigned long count; } tape[4]; /* Distribute the records alternately to tape[0] and tape[1]. */ tape[0].count = tape[1].count = 0L; tape[0].first = NULL; base = 0; while (p != NULL) { ShinyZone *next = p->next; p->next = tape[base].first; tape[base].first = p; tape[base].count++; p = next; base ^= 1; } /* If the list is empty or contains only a single record, then */ /* tape[1].count == 0L and this part is vacuous. */ for (base = 0, block_size = 1L; tape[base+1].count != 0L; base ^= 2, block_size <<= 1) { int dest; struct tape *tape0, *tape1; tape0 = tape + base; tape1 = tape + base + 1; dest = base ^ 2; tape[dest].count = tape[dest+1].count = 0; for (; tape0->count != 0; dest ^= 1) { unsigned long n0, n1; struct tape *output_tape = tape + dest; n0 = n1 = block_size; while (1) { ShinyZone *chosen_record; struct tape *chosen_tape; if (n0 == 0 || tape0->count == 0) { if (n1 == 0 || tape1->count == 0) break; chosen_tape = tape1; n1--; } else if (n1 == 0 || tape1->count == 0) { chosen_tape = tape0; n0--; } else if (ShinyZone_compare(tape1->first, tape0->first) > 0) { chosen_tape = tape1; n1--; } else { chosen_tape = tape0; n0--; } chosen_tape->count--; chosen_record = chosen_tape->first; chosen_tape->first = chosen_record->next; if (output_tape->count == 0) output_tape->first = chosen_record; else output_tape->last->next = chosen_record; output_tape->last = chosen_record; output_tape->count++; } } } if (tape[base].count > 1L) { ShinyZone* last = tape[base].last; *first = tape[base].first; last->next = NULL; return last; } else { return NULL; } } /*---------------------------------------------------------------------------*/ void ShinyZone_clear(ShinyZone* self) { memset(self, 0, sizeof(ShinyZone)); } /*---------------------------------------------------------------------------*/ void ShinyZone_enumerateZones(const ShinyZone* a_zone, void (*a_func)(const ShinyZone*)) { a_func(a_zone); if (a_zone->next) ShinyZone_enumerateZones(a_zone->next, a_func); } #endif /* SLIC3R_PROFILE */ Slic3r-version_1.39.1/xs/src/Shiny/ShinyZone.h000066400000000000000000000055231324354444700211420ustar00rootroot00000000000000/* The MIT License Copyright (c) 2007-2010 Aidin Abedi http://code.google.com/p/shinyprofiler/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef SHINY_ZONE_H #define SHINY_ZONE_H #include "ShinyData.h" #include #ifdef __cplusplus extern "C" { #endif /*---------------------------------------------------------------------------*/ #define SHINY_ZONE_STATE_HIDDEN 0 #define SHINY_ZONE_STATE_INITIALIZED 1 #define SHINY_ZONE_STATE_UPDATING 2 /*---------------------------------------------------------------------------*/ typedef struct _ShinyZone { struct _ShinyZone* next; int _state; const char* name; ShinyData data; } ShinyZone; /*---------------------------------------------------------------------------*/ SHINY_INLINE void ShinyZone_init(ShinyZone *self, ShinyZone* a_prev) { self->_state = SHINY_ZONE_STATE_INITIALIZED; a_prev->next = self; } SHINY_INLINE void ShinyZone_uninit(ShinyZone *self) { self->_state = SHINY_ZONE_STATE_HIDDEN; self->next = NULL; } SHINY_API void ShinyZone_preUpdateChain(ShinyZone *first); SHINY_API void ShinyZone_updateChain(ShinyZone *first, float a_damping); SHINY_API void ShinyZone_updateChainClean(ShinyZone *first); SHINY_API void ShinyZone_resetChain(ShinyZone *first); SHINY_API ShinyZone* ShinyZone_sortChain(ShinyZone **first); SHINY_INLINE float ShinyZone_compare(ShinyZone *a, ShinyZone *b) { return a->data.selfTicks.avg - b->data.selfTicks.avg; } SHINY_API void ShinyZone_clear(ShinyZone* self); SHINY_API void ShinyZone_enumerateZones(const ShinyZone* a_zone, void (*a_func)(const ShinyZone*)); #if __cplusplus } /* end of extern "C" */ template void ShinyZone_enumerateZones(const ShinyZone* a_zone, T* a_this, void (T::*a_func)(const ShinyZone*)) { (a_this->*a_func)(a_zone); if (a_zone->next) ShinyZone_enumerateZones(a_zone->next, a_this, a_func); } #endif /* __cplusplus */ #endif /* SHINY_ZONE_H */ Slic3r-version_1.39.1/xs/src/admesh/000077500000000000000000000000001324354444700172055ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/admesh/connect.cpp000066400000000000000000001006561324354444700213520ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include #include #include "stl.h" static void stl_match_neighbors_exact(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_match_neighbors_nearby(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_record_neighbors(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_initialize_facet_check_exact(stl_file *stl); static void stl_initialize_facet_check_nearby(stl_file *stl); static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b); static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b, float tolerance); static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, void (*match_neighbors)(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b)); static int stl_get_hash_for_edge(int M, stl_hash_edge *edge); static int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_free_edges(stl_file *stl); static void stl_remove_facet(stl_file *stl, int facet_number); static void stl_change_vertices(stl_file *stl, int facet_num, int vnot, stl_vertex new_vertex); static void stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b, int *facet1, int *vertex1, int *facet2, int *vertex2, stl_vertex *new_vertex1, stl_vertex *new_vertex2); static void stl_remove_degenerate(stl_file *stl, int facet); extern int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); static void stl_update_connects_remove_1(stl_file *stl, int facet_num); void stl_check_facets_exact(stl_file *stl) { /* This function builds the neighbors list. No modifications are made * to any of the facets. The edges are said to match only if all six * floats of the first edge matches all six floats of the second edge. */ stl_hash_edge edge; stl_facet facet; int i; int j; if (stl->error) return; stl->stats.connected_edges = 0; stl->stats.connected_facets_1_edge = 0; stl->stats.connected_facets_2_edge = 0; stl->stats.connected_facets_3_edge = 0; stl_initialize_facet_check_exact(stl); for(i = 0; i < stl->stats.number_of_facets; i++) { facet = stl->facet_start[i]; // Positive and negative zeros are possible in the floats, which are considered equal by the FP unit. // When using a memcmp on raw floats, those numbers report to be different. // Unify all +0 and -0 to +0 to make the floats equal under memcmp. { uint32_t *f = (uint32_t*)&facet; for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats if (*f == 0x80000000) // Negative zero, switch to positive zero. *f = 0; } /* If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. */ if( !memcmp(&facet.vertex[0], &facet.vertex[1], sizeof(stl_vertex)) || !memcmp(&facet.vertex[1], &facet.vertex[2], sizeof(stl_vertex)) || !memcmp(&facet.vertex[0], &facet.vertex[2], sizeof(stl_vertex))) { stl->stats.degenerate_facets += 1; stl_remove_facet(stl, i); i--; continue; } for(j = 0; j < 3; j++) { edge.facet_number = i; edge.which_edge = j; stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); insert_hash_edge(stl, edge, stl_match_neighbors_exact); } } stl_free_edges(stl); #if 0 printf("Number of faces: %d, number of manifold edges: %d, number of connected edges: %d, number of unconnected edges: %d\r\n", stl->stats.number_of_facets, stl->stats.number_of_facets * 3, stl->stats.connected_edges, stl->stats.number_of_facets * 3 - stl->stats.connected_edges); #endif } static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b) { if (stl->error) return; { float diff_x = ABS(a->x - b->x); float diff_y = ABS(a->y - b->y); float diff_z = ABS(a->z - b->z); float max_diff = STL_MAX(diff_x, diff_y); max_diff = STL_MAX(diff_z, max_diff); stl->stats.shortest_edge = STL_MIN(max_diff, stl->stats.shortest_edge); } // Ensure identical vertex ordering of equal edges. // This method is numerically robust. if ((a->x != b->x) ? (a->x < b->x) : ((a->y != b->y) ? (a->y < b->y) : (a->z < b->z))) { memcpy(&edge->key[0], a, sizeof(stl_vertex)); memcpy(&edge->key[3], b, sizeof(stl_vertex)); } else { memcpy(&edge->key[0], b, sizeof(stl_vertex)); memcpy(&edge->key[3], a, sizeof(stl_vertex)); edge->which_edge += 3; /* this edge is loaded backwards */ } } static void stl_initialize_facet_check_exact(stl_file *stl) { int i; if (stl->error) return; stl->stats.malloced = 0; stl->stats.freed = 0; stl->stats.collisions = 0; stl->M = 81397; for(i = 0; i < stl->stats.number_of_facets ; i++) { /* initialize neighbors list to -1 to mark unconnected edges */ stl->neighbors_start[i].neighbor[0] = -1; stl->neighbors_start[i].neighbor[1] = -1; stl->neighbors_start[i].neighbor[2] = -1; } stl->heads = (stl_hash_edge**)calloc(stl->M, sizeof(*stl->heads)); if(stl->heads == NULL) perror("stl_initialize_facet_check_exact"); stl->tail = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); if(stl->tail == NULL) perror("stl_initialize_facet_check_exact"); stl->tail->next = stl->tail; for(i = 0; i < stl->M; i++) { stl->heads[i] = stl->tail; } } static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, void (*match_neighbors)(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b)) { stl_hash_edge *link; stl_hash_edge *new_edge; stl_hash_edge *temp; int chain_number; if (stl->error) return; chain_number = stl_get_hash_for_edge(stl->M, &edge); link = stl->heads[chain_number]; if(link == stl->tail) { /* This list doesn't have any edges currently in it. Add this one. */ new_edge = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); if(new_edge == NULL) perror("insert_hash_edge"); stl->stats.malloced++; *new_edge = edge; new_edge->next = stl->tail; stl->heads[chain_number] = new_edge; return; } else if(!stl_compare_function(&edge, link)) { /* This is a match. Record result in neighbors list. */ match_neighbors(stl, &edge, link); /* Delete the matched edge from the list. */ stl->heads[chain_number] = link->next; free(link); stl->stats.freed++; return; } else { /* Continue through the rest of the list */ for(;;) { if(link->next == stl->tail) { /* This is the last item in the list. Insert a new edge. */ new_edge = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); if(new_edge == NULL) perror("insert_hash_edge"); stl->stats.malloced++; *new_edge = edge; new_edge->next = stl->tail; link->next = new_edge; stl->stats.collisions++; return; } else if(!stl_compare_function(&edge, link->next)) { /* This is a match. Record result in neighbors list. */ match_neighbors(stl, &edge, link->next); /* Delete the matched edge from the list. */ temp = link->next; link->next = link->next->next; free(temp); stl->stats.freed++; return; } else { /* This is not a match. Go to the next link */ link = link->next; stl->stats.collisions++; } } } } static int stl_get_hash_for_edge(int M, stl_hash_edge *edge) { return ((edge->key[0] / 23 + edge->key[1] / 19 + edge->key[2] / 17 + edge->key[3] /13 + edge->key[4] / 11 + edge->key[5] / 7 ) % M); } static int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b) { if(edge_a->facet_number == edge_b->facet_number) { return 1; /* Don't match edges of the same facet */ } else { return memcmp(edge_a, edge_b, SIZEOF_EDGE_SORT); } } void stl_check_facets_nearby(stl_file *stl, float tolerance) { stl_hash_edge edge[3]; stl_facet facet; int i; int j; if (stl->error) return; if( (stl->stats.connected_facets_1_edge == stl->stats.number_of_facets) && (stl->stats.connected_facets_2_edge == stl->stats.number_of_facets) && (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets)) { /* No need to check any further. All facets are connected */ return; } stl_initialize_facet_check_nearby(stl); for(i = 0; i < stl->stats.number_of_facets; i++) { facet = stl->facet_start[i]; // Positive and negative zeros are possible in the floats, which are considered equal by the FP unit. // When using a memcmp on raw floats, those numbers report to be different. // Unify all +0 and -0 to +0 to make the floats equal under memcmp. { uint32_t *f = (uint32_t*)&facet; for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats if (*f == 0x80000000) // Negative zero, switch to positive zero. *f = 0; } for(j = 0; j < 3; j++) { if(stl->neighbors_start[i].neighbor[j] == -1) { edge[j].facet_number = i; edge[j].which_edge = j; if(stl_load_edge_nearby(stl, &edge[j], &facet.vertex[j], &facet.vertex[(j + 1) % 3], tolerance)) { /* only insert edges that have different keys */ insert_hash_edge(stl, edge[j], stl_match_neighbors_nearby); } } } } stl_free_edges(stl); } static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b, float tolerance) { // Index of a grid cell spaced by tolerance. uint32_t vertex1[3] = { (uint32_t)((a->x - stl->stats.min.x) / tolerance), (uint32_t)((a->y - stl->stats.min.y) / tolerance), (uint32_t)((a->z - stl->stats.min.z) / tolerance) }; uint32_t vertex2[3] = { (uint32_t)((b->x - stl->stats.min.x) / tolerance), (uint32_t)((b->y - stl->stats.min.y) / tolerance), (uint32_t)((b->z - stl->stats.min.z) / tolerance) }; if( (vertex1[0] == vertex2[0]) && (vertex1[1] == vertex2[1]) && (vertex1[2] == vertex2[2])) { /* Both vertices hash to the same value */ return 0; } // Ensure identical vertex ordering of edges, which vertices land into equal grid cells. // This method is numerically robust. if ((vertex1[0] != vertex2[0]) ? (vertex1[0] < vertex2[0]) : ((vertex1[1] != vertex2[1]) ? (vertex1[1] < vertex2[1]) : (vertex1[2] < vertex2[2]))) { memcpy(&edge->key[0], vertex1, sizeof(stl_vertex)); memcpy(&edge->key[3], vertex2, sizeof(stl_vertex)); } else { memcpy(&edge->key[0], vertex2, sizeof(stl_vertex)); memcpy(&edge->key[3], vertex1, sizeof(stl_vertex)); edge->which_edge += 3; /* this edge is loaded backwards */ } return 1; } static void stl_free_edges(stl_file *stl) { int i; stl_hash_edge *temp; if (stl->error) return; if(stl->stats.malloced != stl->stats.freed) { for(i = 0; i < stl->M; i++) { for(temp = stl->heads[i]; stl->heads[i] != stl->tail; temp = stl->heads[i]) { stl->heads[i] = stl->heads[i]->next; free(temp); stl->stats.freed++; } } } free(stl->heads); free(stl->tail); } static void stl_initialize_facet_check_nearby(stl_file *stl) { int i; if (stl->error) return; stl->stats.malloced = 0; stl->stats.freed = 0; stl->stats.collisions = 0; /* tolerance = STL_MAX(stl->stats.shortest_edge, tolerance);*/ /* tolerance = STL_MAX((stl->stats.bounding_diameter / 500000.0), tolerance);*/ /* tolerance *= 0.5;*/ stl->M = 81397; stl->heads = (stl_hash_edge**)calloc(stl->M, sizeof(*stl->heads)); if(stl->heads == NULL) perror("stl_initialize_facet_check_nearby"); stl->tail = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); if(stl->tail == NULL) perror("stl_initialize_facet_check_nearby"); stl->tail->next = stl->tail; for(i = 0; i < stl->M; i++) { stl->heads[i] = stl->tail; } } static void stl_record_neighbors(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b) { int i; int j; if (stl->error) return; /* Facet a's neighbor is facet b */ stl->neighbors_start[edge_a->facet_number].neighbor[edge_a->which_edge % 3] = edge_b->facet_number; /* sets the .neighbor part */ stl->neighbors_start[edge_a->facet_number]. which_vertex_not[edge_a->which_edge % 3] = (edge_b->which_edge + 2) % 3; /* sets the .which_vertex_not part */ /* Facet b's neighbor is facet a */ stl->neighbors_start[edge_b->facet_number].neighbor[edge_b->which_edge % 3] = edge_a->facet_number; /* sets the .neighbor part */ stl->neighbors_start[edge_b->facet_number]. which_vertex_not[edge_b->which_edge % 3] = (edge_a->which_edge + 2) % 3; /* sets the .which_vertex_not part */ if( ((edge_a->which_edge < 3) && (edge_b->which_edge < 3)) || ((edge_a->which_edge > 2) && (edge_b->which_edge > 2))) { /* these facets are oriented in opposite directions. */ /* their normals are probably messed up. */ stl->neighbors_start[edge_a->facet_number]. which_vertex_not[edge_a->which_edge % 3] += 3; stl->neighbors_start[edge_b->facet_number]. which_vertex_not[edge_b->which_edge % 3] += 3; } /* Count successful connects */ /* Total connects */ stl->stats.connected_edges += 2; /* Count individual connects */ i = ((stl->neighbors_start[edge_a->facet_number].neighbor[0] == -1) + (stl->neighbors_start[edge_a->facet_number].neighbor[1] == -1) + (stl->neighbors_start[edge_a->facet_number].neighbor[2] == -1)); j = ((stl->neighbors_start[edge_b->facet_number].neighbor[0] == -1) + (stl->neighbors_start[edge_b->facet_number].neighbor[1] == -1) + (stl->neighbors_start[edge_b->facet_number].neighbor[2] == -1)); if(i == 2) { stl->stats.connected_facets_1_edge +=1; } else if(i == 1) { stl->stats.connected_facets_2_edge +=1; } else { stl->stats.connected_facets_3_edge +=1; } if(j == 2) { stl->stats.connected_facets_1_edge +=1; } else if(j == 1) { stl->stats.connected_facets_2_edge +=1; } else { stl->stats.connected_facets_3_edge +=1; } } static void stl_match_neighbors_exact(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b) { if (stl->error) return; stl_record_neighbors(stl, edge_a, edge_b); } static void stl_match_neighbors_nearby(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b) { int facet1; int facet2; int vertex1; int vertex2; int vnot1; int vnot2; stl_vertex new_vertex1; stl_vertex new_vertex2; if (stl->error) return; stl_record_neighbors(stl, edge_a, edge_b); stl_which_vertices_to_change(stl, edge_a, edge_b, &facet1, &vertex1, &facet2, &vertex2, &new_vertex1, &new_vertex2); if(facet1 != -1) { if(facet1 == edge_a->facet_number) { vnot1 = (edge_a->which_edge + 2) % 3; } else { vnot1 = (edge_b->which_edge + 2) % 3; } if(((vnot1 + 2) % 3) == vertex1) { vnot1 += 3; } stl_change_vertices(stl, facet1, vnot1, new_vertex1); } if(facet2 != -1) { if(facet2 == edge_a->facet_number) { vnot2 = (edge_a->which_edge + 2) % 3; } else { vnot2 = (edge_b->which_edge + 2) % 3; } if(((vnot2 + 2) % 3) == vertex2) { vnot2 += 3; } stl_change_vertices(stl, facet2, vnot2, new_vertex2); } stl->stats.edges_fixed += 2; } static void stl_change_vertices(stl_file *stl, int facet_num, int vnot, stl_vertex new_vertex) { int first_facet; int direction; int next_edge; int pivot_vertex; if (stl->error) return; first_facet = facet_num; direction = 0; for(;;) { if(vnot > 2) { if(direction == 0) { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; direction = 1; } else { pivot_vertex = (vnot + 1) % 3; next_edge = vnot % 3; direction = 0; } } else { if(direction == 0) { pivot_vertex = (vnot + 1) % 3; next_edge = vnot; } else { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; } } #if 0 if (stl->facet_start[facet_num].vertex[pivot_vertex].x == new_vertex.x && stl->facet_start[facet_num].vertex[pivot_vertex].y == new_vertex.y && stl->facet_start[facet_num].vertex[pivot_vertex].z == new_vertex.z) printf("Changing vertex %f,%f,%f: Same !!!\r\n", new_vertex.x, new_vertex.y, new_vertex.z); else { if (stl->facet_start[facet_num].vertex[pivot_vertex].x != new_vertex.x) printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", stl->facet_start[facet_num].vertex[pivot_vertex].x, *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex].x), new_vertex.x, *reinterpret_cast(&new_vertex.x)); if (stl->facet_start[facet_num].vertex[pivot_vertex].y != new_vertex.y) printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", stl->facet_start[facet_num].vertex[pivot_vertex].y, *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex].y), new_vertex.y, *reinterpret_cast(&new_vertex.y)); if (stl->facet_start[facet_num].vertex[pivot_vertex].z != new_vertex.z) printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", stl->facet_start[facet_num].vertex[pivot_vertex].z, *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex].z), new_vertex.z, *reinterpret_cast(&new_vertex.z)); } #endif stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex; vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; facet_num = stl->neighbors_start[facet_num].neighbor[next_edge]; if(facet_num == -1) { break; } if(facet_num == first_facet) { /* back to the beginning */ printf("\ Back to the first facet changing vertices: probably a mobius part.\n\ Try using a smaller tolerance or don't do a nearby check\n"); return; } } } static void stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b, int *facet1, int *vertex1, int *facet2, int *vertex2, stl_vertex *new_vertex1, stl_vertex *new_vertex2) { int v1a; /* pair 1, facet a */ int v1b; /* pair 1, facet b */ int v2a; /* pair 2, facet a */ int v2b; /* pair 2, facet b */ /* Find first pair */ if(edge_a->which_edge < 3) { v1a = edge_a->which_edge; v2a = (edge_a->which_edge + 1) % 3; } else { v2a = edge_a->which_edge % 3; v1a = (edge_a->which_edge + 1) % 3; } if(edge_b->which_edge < 3) { v1b = edge_b->which_edge; v2b = (edge_b->which_edge + 1) % 3; } else { v2b = edge_b->which_edge % 3; v1b = (edge_b->which_edge + 1) % 3; } /* Of the first pair, which vertex, if any, should be changed */ if(!memcmp(&stl->facet_start[edge_a->facet_number].vertex[v1a], &stl->facet_start[edge_b->facet_number].vertex[v1b], sizeof(stl_vertex))) { /* These facets are already equal. No need to change. */ *facet1 = -1; } else { if( (stl->neighbors_start[edge_a->facet_number].neighbor[v1a] == -1) && (stl->neighbors_start[edge_a->facet_number]. neighbor[(v1a + 2) % 3] == -1)) { /* This vertex has no neighbors. This is a good one to change */ *facet1 = edge_a->facet_number; *vertex1 = v1a; *new_vertex1 = stl->facet_start[edge_b->facet_number].vertex[v1b]; } else { *facet1 = edge_b->facet_number; *vertex1 = v1b; *new_vertex1 = stl->facet_start[edge_a->facet_number].vertex[v1a]; } } /* Of the second pair, which vertex, if any, should be changed */ if(!memcmp(&stl->facet_start[edge_a->facet_number].vertex[v2a], &stl->facet_start[edge_b->facet_number].vertex[v2b], sizeof(stl_vertex))) { /* These facets are already equal. No need to change. */ *facet2 = -1; } else { if( (stl->neighbors_start[edge_a->facet_number].neighbor[v2a] == -1) && (stl->neighbors_start[edge_a->facet_number]. neighbor[(v2a + 2) % 3] == -1)) { /* This vertex has no neighbors. This is a good one to change */ *facet2 = edge_a->facet_number; *vertex2 = v2a; *new_vertex2 = stl->facet_start[edge_b->facet_number].vertex[v2b]; } else { *facet2 = edge_b->facet_number; *vertex2 = v2b; *new_vertex2 = stl->facet_start[edge_a->facet_number].vertex[v2a]; } } } static void stl_remove_facet(stl_file *stl, int facet_number) { int neighbor[3]; int vnot[3]; int i; int j; if (stl->error) return; stl->stats.facets_removed += 1; /* Update list of connected edges */ j = ((stl->neighbors_start[facet_number].neighbor[0] == -1) + (stl->neighbors_start[facet_number].neighbor[1] == -1) + (stl->neighbors_start[facet_number].neighbor[2] == -1)); if(j == 2) { stl->stats.connected_facets_1_edge -= 1; } else if(j == 1) { stl->stats.connected_facets_2_edge -= 1; stl->stats.connected_facets_1_edge -= 1; } else if(j == 0) { stl->stats.connected_facets_3_edge -= 1; stl->stats.connected_facets_2_edge -= 1; stl->stats.connected_facets_1_edge -= 1; } stl->facet_start[facet_number] = stl->facet_start[stl->stats.number_of_facets - 1]; /* I could reallocate at this point, but it is not really necessary. */ stl->neighbors_start[facet_number] = stl->neighbors_start[stl->stats.number_of_facets - 1]; stl->stats.number_of_facets -= 1; for(i = 0; i < 3; i++) { neighbor[i] = stl->neighbors_start[facet_number].neighbor[i]; vnot[i] = stl->neighbors_start[facet_number].which_vertex_not[i]; } for(i = 0; i < 3; i++) { if(neighbor[i] != -1) { if(stl->neighbors_start[neighbor[i]].neighbor[(vnot[i] + 1)% 3] != stl->stats.number_of_facets) { printf("\ in stl_remove_facet: neighbor = %d numfacets = %d this is wrong\n", stl->neighbors_start[neighbor[i]].neighbor[(vnot[i] + 1)% 3], stl->stats.number_of_facets); return; } stl->neighbors_start[neighbor[i]].neighbor[(vnot[i] + 1)% 3] = facet_number; } } } void stl_remove_unconnected_facets(stl_file *stl) { /* A couple of things need to be done here. One is to remove any */ /* completely unconnected facets (0 edges connected) since these are */ /* useless and could be completely wrong. The second thing that needs to */ /* be done is to remove any degenerate facets that were created during */ /* stl_check_facets_nearby(). */ int i; if (stl->error) return; /* remove degenerate facets */ for(i = 0; i < stl->stats.number_of_facets; i++) { if( !memcmp(&stl->facet_start[i].vertex[0], &stl->facet_start[i].vertex[1], sizeof(stl_vertex)) || !memcmp(&stl->facet_start[i].vertex[1], &stl->facet_start[i].vertex[2], sizeof(stl_vertex)) || !memcmp(&stl->facet_start[i].vertex[0], &stl->facet_start[i].vertex[2], sizeof(stl_vertex))) { stl_remove_degenerate(stl, i); i--; } } if(stl->stats.connected_facets_1_edge < stl->stats.number_of_facets) { /* remove completely unconnected facets */ for(i = 0; i < stl->stats.number_of_facets; i++) { if( (stl->neighbors_start[i].neighbor[0] == -1) && (stl->neighbors_start[i].neighbor[1] == -1) && (stl->neighbors_start[i].neighbor[2] == -1)) { /* This facet is completely unconnected. Remove it. */ stl_remove_facet(stl, i); i--; } } } } static void stl_remove_degenerate(stl_file *stl, int facet) { int edge1; int edge2; int edge3; int neighbor1; int neighbor2; int neighbor3; int vnot1; int vnot2; int vnot3; if (stl->error) return; if( !memcmp(&stl->facet_start[facet].vertex[0], &stl->facet_start[facet].vertex[1], sizeof(stl_vertex)) && !memcmp(&stl->facet_start[facet].vertex[1], &stl->facet_start[facet].vertex[2], sizeof(stl_vertex))) { /* all 3 vertices are equal. Just remove the facet. I don't think*/ /* this is really possible, but just in case... */ printf("removing a facet in stl_remove_degenerate\n"); stl_remove_facet(stl, facet); return; } if(!memcmp(&stl->facet_start[facet].vertex[0], &stl->facet_start[facet].vertex[1], sizeof(stl_vertex))) { edge1 = 1; edge2 = 2; edge3 = 0; } else if(!memcmp(&stl->facet_start[facet].vertex[1], &stl->facet_start[facet].vertex[2], sizeof(stl_vertex))) { edge1 = 0; edge2 = 2; edge3 = 1; } else if(!memcmp(&stl->facet_start[facet].vertex[2], &stl->facet_start[facet].vertex[0], sizeof(stl_vertex))) { edge1 = 0; edge2 = 1; edge3 = 2; } else { /* No degenerate. Function shouldn't have been called. */ return; } neighbor1 = stl->neighbors_start[facet].neighbor[edge1]; neighbor2 = stl->neighbors_start[facet].neighbor[edge2]; if(neighbor1 == -1) { stl_update_connects_remove_1(stl, neighbor2); } if(neighbor2 == -1) { stl_update_connects_remove_1(stl, neighbor1); } neighbor3 = stl->neighbors_start[facet].neighbor[edge3]; vnot1 = stl->neighbors_start[facet].which_vertex_not[edge1]; vnot2 = stl->neighbors_start[facet].which_vertex_not[edge2]; vnot3 = stl->neighbors_start[facet].which_vertex_not[edge3]; if(neighbor1 >= 0){ stl->neighbors_start[neighbor1].neighbor[(vnot1 + 1) % 3] = neighbor2; stl->neighbors_start[neighbor1].which_vertex_not[(vnot1 + 1) % 3] = vnot2; } if(neighbor2 >= 0){ stl->neighbors_start[neighbor2].neighbor[(vnot2 + 1) % 3] = neighbor1; stl->neighbors_start[neighbor2].which_vertex_not[(vnot2 + 1) % 3] = vnot1; } stl_remove_facet(stl, facet); if(neighbor3 >= 0) { stl_update_connects_remove_1(stl, neighbor3); stl->neighbors_start[neighbor3].neighbor[(vnot3 + 1) % 3] = -1; } } void stl_update_connects_remove_1(stl_file *stl, int facet_num) { int j; if (stl->error) return; /* Update list of connected edges */ j = ((stl->neighbors_start[facet_num].neighbor[0] == -1) + (stl->neighbors_start[facet_num].neighbor[1] == -1) + (stl->neighbors_start[facet_num].neighbor[2] == -1)); if(j == 0) { /* Facet has 3 neighbors */ stl->stats.connected_facets_3_edge -= 1; } else if(j == 1) { /* Facet has 2 neighbors */ stl->stats.connected_facets_2_edge -= 1; } else if(j == 2) { /* Facet has 1 neighbor */ stl->stats.connected_facets_1_edge -= 1; } } void stl_fill_holes(stl_file *stl) { stl_facet facet; stl_facet new_facet; int neighbors_initial[3]; stl_hash_edge edge; int first_facet; int direction; int facet_num; int vnot; int next_edge; int pivot_vertex; int next_facet; int i; int j; int k; if (stl->error) return; /* Insert all unconnected edges into hash list */ stl_initialize_facet_check_nearby(stl); for(i = 0; i < stl->stats.number_of_facets; i++) { facet = stl->facet_start[i]; for(j = 0; j < 3; j++) { if(stl->neighbors_start[i].neighbor[j] != -1) continue; edge.facet_number = i; edge.which_edge = j; stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); insert_hash_edge(stl, edge, stl_match_neighbors_exact); } } for(i = 0; i < stl->stats.number_of_facets; i++) { facet = stl->facet_start[i]; neighbors_initial[0] = stl->neighbors_start[i].neighbor[0]; neighbors_initial[1] = stl->neighbors_start[i].neighbor[1]; neighbors_initial[2] = stl->neighbors_start[i].neighbor[2]; first_facet = i; for(j = 0; j < 3; j++) { if(stl->neighbors_start[i].neighbor[j] != -1) continue; new_facet.vertex[0] = facet.vertex[j]; new_facet.vertex[1] = facet.vertex[(j + 1) % 3]; if(neighbors_initial[(j + 2) % 3] == -1) { direction = 1; } else { direction = 0; } facet_num = i; vnot = (j + 2) % 3; for(;;) { if(vnot > 2) { if(direction == 0) { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; direction = 1; } else { pivot_vertex = (vnot + 1) % 3; next_edge = vnot % 3; direction = 0; } } else { if(direction == 0) { pivot_vertex = (vnot + 1) % 3; next_edge = vnot; } else { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; } } next_facet = stl->neighbors_start[facet_num].neighbor[next_edge]; if(next_facet == -1) { new_facet.vertex[2] = stl->facet_start[facet_num]. vertex[vnot % 3]; stl_add_facet(stl, &new_facet); for(k = 0; k < 3; k++) { edge.facet_number = stl->stats.number_of_facets - 1; edge.which_edge = k; stl_load_edge_exact(stl, &edge, &new_facet.vertex[k], &new_facet.vertex[(k + 1) % 3]); insert_hash_edge(stl, edge, stl_match_neighbors_exact); } break; } else { vnot = stl->neighbors_start[facet_num]. which_vertex_not[next_edge]; facet_num = next_facet; } if(facet_num == first_facet) { /* back to the beginning */ printf("\ Back to the first facet filling holes: probably a mobius part.\n\ Try using a smaller tolerance or don't do a nearby check\n"); return; } } } } } void stl_add_facet(stl_file *stl, stl_facet *new_facet) { if (stl->error) return; stl->stats.facets_added += 1; if(stl->stats.facets_malloced < stl->stats.number_of_facets + 1) { stl->facet_start = (stl_facet*)realloc(stl->facet_start, (sizeof(stl_facet) * (stl->stats.facets_malloced + 256))); if(stl->facet_start == NULL) perror("stl_add_facet"); stl->neighbors_start = (stl_neighbors*)realloc(stl->neighbors_start, (sizeof(stl_neighbors) * (stl->stats.facets_malloced + 256))); if(stl->neighbors_start == NULL) perror("stl_add_facet"); stl->stats.facets_malloced += 256; } stl->facet_start[stl->stats.number_of_facets] = *new_facet; /* note that the normal vector is not set here, just initialized to 0 */ stl->facet_start[stl->stats.number_of_facets].normal.x = 0.0; stl->facet_start[stl->stats.number_of_facets].normal.y = 0.0; stl->facet_start[stl->stats.number_of_facets].normal.z = 0.0; stl->neighbors_start[stl->stats.number_of_facets].neighbor[0] = -1; stl->neighbors_start[stl->stats.number_of_facets].neighbor[1] = -1; stl->neighbors_start[stl->stats.number_of_facets].neighbor[2] = -1; stl->stats.number_of_facets += 1; } Slic3r-version_1.39.1/xs/src/admesh/normals.cpp000066400000000000000000000251121324354444700213650ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include #include #include "stl.h" static void stl_reverse_vector(float v[]) { v[0] *= -1; v[1] *= -1; v[2] *= -1; } static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); static void stl_reverse_facet(stl_file *stl, int facet_num) { stl_vertex tmp_vertex; /* int tmp_neighbor;*/ int neighbor[3]; int vnot[3]; stl->stats.facets_reversed += 1; neighbor[0] = stl->neighbors_start[facet_num].neighbor[0]; neighbor[1] = stl->neighbors_start[facet_num].neighbor[1]; neighbor[2] = stl->neighbors_start[facet_num].neighbor[2]; vnot[0] = stl->neighbors_start[facet_num].which_vertex_not[0]; vnot[1] = stl->neighbors_start[facet_num].which_vertex_not[1]; vnot[2] = stl->neighbors_start[facet_num].which_vertex_not[2]; /* reverse the facet */ tmp_vertex = stl->facet_start[facet_num].vertex[0]; stl->facet_start[facet_num].vertex[0] = stl->facet_start[facet_num].vertex[1]; stl->facet_start[facet_num].vertex[1] = tmp_vertex; /* fix the vnots of the neighboring facets */ if(neighbor[0] != -1) stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = (stl->neighbors_start[neighbor[0]]. which_vertex_not[(vnot[0] + 1) % 3] + 3) % 6; if(neighbor[1] != -1) stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = (stl->neighbors_start[neighbor[1]]. which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6; if(neighbor[2] != -1) stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] = (stl->neighbors_start[neighbor[2]]. which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6; /* swap the neighbors of the facet that is being reversed */ stl->neighbors_start[facet_num].neighbor[1] = neighbor[2]; stl->neighbors_start[facet_num].neighbor[2] = neighbor[1]; /* swap the vnots of the facet that is being reversed */ stl->neighbors_start[facet_num].which_vertex_not[1] = vnot[2]; stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1]; /* reverse the values of the vnots of the facet that is being reversed */ stl->neighbors_start[facet_num].which_vertex_not[0] = (stl->neighbors_start[facet_num].which_vertex_not[0] + 3) % 6; stl->neighbors_start[facet_num].which_vertex_not[1] = (stl->neighbors_start[facet_num].which_vertex_not[1] + 3) % 6; stl->neighbors_start[facet_num].which_vertex_not[2] = (stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6; } void stl_fix_normal_directions(stl_file *stl) { char *norm_sw; /* int edge_num;*/ /* int vnot;*/ int checked = 0; int facet_num; /* int next_facet;*/ int i; int j; struct stl_normal { int facet_num; struct stl_normal *next; }; struct stl_normal *head; struct stl_normal *tail; struct stl_normal *newn; struct stl_normal *temp; if (stl->error) return; /* Initialize linked list. */ head = (struct stl_normal*)malloc(sizeof(struct stl_normal)); if(head == NULL) perror("stl_fix_normal_directions"); tail = (struct stl_normal*)malloc(sizeof(struct stl_normal)); if(tail == NULL) perror("stl_fix_normal_directions"); head->next = tail; tail->next = tail; /* Initialize list that keeps track of already fixed facets. */ norm_sw = (char*)calloc(stl->stats.number_of_facets, sizeof(char)); if(norm_sw == NULL) perror("stl_fix_normal_directions"); facet_num = 0; /* If normal vector is not within tolerance and backwards: Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances of it being wrong randomly are low if most of the triangles are right: */ if(stl_check_normal_vector(stl, 0, 0) == 2) stl_reverse_facet(stl, 0); /* Say that we've fixed this facet: */ norm_sw[facet_num] = 1; checked++; for(;;) { /* Add neighbors_to_list. Add unconnected neighbors to the list:a */ for(j = 0; j < 3; j++) { /* Reverse the neighboring facets if necessary. */ if(stl->neighbors_start[facet_num].which_vertex_not[j] > 2) { /* If the facet has a neighbor that is -1, it means that edge isn't shared by another facet */ if(stl->neighbors_start[facet_num].neighbor[j] != -1) stl_reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]); } /* If this edge of the facet is connected: */ if(stl->neighbors_start[facet_num].neighbor[j] != -1) { /* If we haven't fixed this facet yet, add it to the list: */ if(norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) { /* Add node to beginning of list. */ newn = (struct stl_normal*)malloc(sizeof(struct stl_normal)); if(newn == NULL) perror("stl_fix_normal_directions"); newn->facet_num = stl->neighbors_start[facet_num].neighbor[j]; newn->next = head->next; head->next = newn; } } } /* Get next facet to fix from top of list. */ if(head->next != tail) { facet_num = head->next->facet_num; if(norm_sw[facet_num] != 1) { /* If facet is in list mutiple times */ norm_sw[facet_num] = 1; /* Record this one as being fixed. */ checked++; } temp = head->next; /* Delete this facet from the list. */ head->next = head->next->next; free(temp); } else { /* if we ran out of facets to fix: */ /* All of the facets in this part have been fixed. */ stl->stats.number_of_parts += 1; if(checked >= stl->stats.number_of_facets) { /* All of the facets have been checked. Bail out. */ break; } else { /* There is another part here. Find it and continue. */ for(i = 0; i < stl->stats.number_of_facets; i++) { if(norm_sw[i] == 0) { /* This is the first facet of the next part. */ facet_num = i; if(stl_check_normal_vector(stl, i, 0) == 2) { stl_reverse_facet(stl, i); } norm_sw[facet_num] = 1; checked++; break; } } } } } free(head); free(tail); free(norm_sw); } static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag) { /* Returns 0 if the normal is within tolerance */ /* Returns 1 if the normal is not within tolerance, but direction is OK */ /* Returns 2 if the normal is not within tolerance and backwards */ /* Returns 4 if the status is unknown. */ float normal[3]; float test_norm[3]; stl_facet *facet; facet = &stl->facet_start[facet_num]; stl_calculate_normal(normal, facet); stl_normalize_vector(normal); if( (ABS(normal[0] - facet->normal.x) < 0.001) && (ABS(normal[1] - facet->normal.y) < 0.001) && (ABS(normal[2] - facet->normal.z) < 0.001)) { /* It is not really necessary to change the values here */ /* but just for consistency, I will. */ facet->normal.x = normal[0]; facet->normal.y = normal[1]; facet->normal.z = normal[2]; return 0; } test_norm[0] = facet->normal.x; test_norm[1] = facet->normal.y; test_norm[2] = facet->normal.z; stl_normalize_vector(test_norm); if( (ABS(normal[0] - test_norm[0]) < 0.001) && (ABS(normal[1] - test_norm[1]) < 0.001) && (ABS(normal[2] - test_norm[2]) < 0.001)) { if(normal_fix_flag) { facet->normal.x = normal[0]; facet->normal.y = normal[1]; facet->normal.z = normal[2]; stl->stats.normals_fixed += 1; } return 1; } stl_reverse_vector(test_norm); if( (ABS(normal[0] - test_norm[0]) < 0.001) && (ABS(normal[1] - test_norm[1]) < 0.001) && (ABS(normal[2] - test_norm[2]) < 0.001)) { /* Facet is backwards. */ if(normal_fix_flag) { facet->normal.x = normal[0]; facet->normal.y = normal[1]; facet->normal.z = normal[2]; stl->stats.normals_fixed += 1; } return 2; } if(normal_fix_flag) { facet->normal.x = normal[0]; facet->normal.y = normal[1]; facet->normal.z = normal[2]; stl->stats.normals_fixed += 1; } return 4; } void stl_calculate_normal(float normal[], stl_facet *facet) { float v1[3] = { facet->vertex[1].x - facet->vertex[0].x, facet->vertex[1].y - facet->vertex[0].y, facet->vertex[1].z - facet->vertex[0].z }; float v2[3] = { facet->vertex[2].x - facet->vertex[0].x, facet->vertex[2].y - facet->vertex[0].y, facet->vertex[2].z - facet->vertex[0].z }; normal[0] = (float)((double)v1[1] * (double)v2[2]) - ((double)v1[2] * (double)v2[1]); normal[1] = (float)((double)v1[2] * (double)v2[0]) - ((double)v1[0] * (double)v2[2]); normal[2] = (float)((double)v1[0] * (double)v2[1]) - ((double)v1[1] * (double)v2[0]); } void stl_normalize_vector(float v[]) { double length; double factor; float min_normal_length; length = sqrt((double)v[0] * (double)v[0] + (double)v[1] * (double)v[1] + (double)v[2] * (double)v[2]); min_normal_length = 0.000000000001; if(length < min_normal_length) { v[0] = 0.0; v[1] = 0.0; v[2] = 0.0; return; } factor = 1.0 / length; v[0] *= factor; v[1] *= factor; v[2] *= factor; } void stl_fix_normal_values(stl_file *stl) { int i; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { stl_check_normal_vector(stl, i, 1); } } void stl_reverse_all_facets(stl_file *stl) { int i; float normal[3]; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { stl_reverse_facet(stl, i); stl_calculate_normal(normal, &stl->facet_start[i]); stl_normalize_vector(normal); stl->facet_start[i].normal.x = normal[0]; stl->facet_start[i].normal.y = normal[1]; stl->facet_start[i].normal.z = normal[2]; } } Slic3r-version_1.39.1/xs/src/admesh/shared.cpp000066400000000000000000000174741324354444700211740ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include #include "stl.h" void stl_invalidate_shared_vertices(stl_file *stl) { if (stl->error) return; if (stl->v_indices != NULL) { free(stl->v_indices); stl->v_indices = NULL; } if (stl->v_shared != NULL) { free(stl->v_shared); stl->v_shared = NULL; } } void stl_generate_shared_vertices(stl_file *stl) { int i; int j; int first_facet; int direction; int facet_num; int vnot; int next_edge; int pivot_vertex; int next_facet; int reversed; if (stl->error) return; /* make sure this function is idempotent and does not leak memory */ stl_invalidate_shared_vertices(stl); stl->v_indices = (v_indices_struct*) calloc(stl->stats.number_of_facets, sizeof(v_indices_struct)); if(stl->v_indices == NULL) perror("stl_generate_shared_vertices"); stl->v_shared = (stl_vertex*) calloc((stl->stats.number_of_facets / 2), sizeof(stl_vertex)); if(stl->v_shared == NULL) perror("stl_generate_shared_vertices"); stl->stats.shared_malloced = stl->stats.number_of_facets / 2; stl->stats.shared_vertices = 0; for(i = 0; i < stl->stats.number_of_facets; i++) { stl->v_indices[i].vertex[0] = -1; stl->v_indices[i].vertex[1] = -1; stl->v_indices[i].vertex[2] = -1; } for(i = 0; i < stl->stats.number_of_facets; i++) { first_facet = i; for(j = 0; j < 3; j++) { if(stl->v_indices[i].vertex[j] != -1) { continue; } if(stl->stats.shared_vertices == stl->stats.shared_malloced) { stl->stats.shared_malloced += 1024; stl->v_shared = (stl_vertex*)realloc(stl->v_shared, stl->stats.shared_malloced * sizeof(stl_vertex)); if(stl->v_shared == NULL) perror("stl_generate_shared_vertices"); } stl->v_shared[stl->stats.shared_vertices] = stl->facet_start[i].vertex[j]; direction = 0; reversed = 0; facet_num = i; vnot = (j + 2) % 3; for(;;) { if(vnot > 2) { if(direction == 0) { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; direction = 1; } else { pivot_vertex = (vnot + 1) % 3; next_edge = vnot % 3; direction = 0; } } else { if(direction == 0) { pivot_vertex = (vnot + 1) % 3; next_edge = vnot; } else { pivot_vertex = (vnot + 2) % 3; next_edge = pivot_vertex; } } stl->v_indices[facet_num].vertex[pivot_vertex] = stl->stats.shared_vertices; next_facet = stl->neighbors_start[facet_num].neighbor[next_edge]; if(next_facet == -1) { if(reversed) { break; } else { direction = 1; vnot = (j + 1) % 3; reversed = 1; facet_num = first_facet; } } else if(next_facet != first_facet) { vnot = stl->neighbors_start[facet_num]. which_vertex_not[next_edge]; facet_num = next_facet; } else { break; } } stl->stats.shared_vertices += 1; } } } void stl_write_off(stl_file *stl, char *file) { int i; FILE *fp; char *error_msg; if (stl->error) return; /* Open the file */ fp = boost::nowide::fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } fprintf(fp, "OFF\n"); fprintf(fp, "%d %d 0\n", stl->stats.shared_vertices, stl->stats.number_of_facets); for(i = 0; i < stl->stats.shared_vertices; i++) { fprintf(fp, "\t%f %f %f\n", stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); } for(i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "\t3 %d %d %d\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); } fclose(fp); } void stl_write_vrml(stl_file *stl, char *file) { int i; FILE *fp; char *error_msg; if (stl->error) return; /* Open the file */ fp = boost::nowide::fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } fprintf(fp, "#VRML V1.0 ascii\n\n"); fprintf(fp, "Separator {\n"); fprintf(fp, "\tDEF STLShape ShapeHints {\n"); fprintf(fp, "\t\tvertexOrdering COUNTERCLOCKWISE\n"); fprintf(fp, "\t\tfaceType CONVEX\n"); fprintf(fp, "\t\tshapeType SOLID\n"); fprintf(fp, "\t\tcreaseAngle 0.0\n"); fprintf(fp, "\t}\n"); fprintf(fp, "\tDEF STLModel Separator {\n"); fprintf(fp, "\t\tDEF STLColor Material {\n"); fprintf(fp, "\t\t\temissiveColor 0.700000 0.700000 0.000000\n"); fprintf(fp, "\t\t}\n"); fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n"); fprintf(fp, "\t\t\tpoint [\n"); for(i = 0; i < (stl->stats.shared_vertices - 1); i++) { fprintf(fp, "\t\t\t\t%f %f %f,\n", stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); } fprintf(fp, "\t\t\t\t%f %f %f]\n", stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); fprintf(fp, "\t\t}\n"); fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n"); fprintf(fp, "\t\t\tcoordIndex [\n"); for(i = 0; i < (stl->stats.number_of_facets - 1); i++) { fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); } fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); fprintf(fp, "\t\t}\n"); fprintf(fp, "\t}\n"); fprintf(fp, "}\n"); fclose(fp); } void stl_write_obj (stl_file *stl, char *file) { int i; FILE* fp; if (stl->error) return; /* Open the file */ fp = boost::nowide::fopen(file, "w"); if (fp == NULL) { char* error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } for (i = 0; i < stl->stats.shared_vertices; i++) { fprintf(fp, "v %f %f %f\n", stl->v_shared[i].x, stl->v_shared[i].y, stl->v_shared[i].z); } for (i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1); } fclose(fp); } Slic3r-version_1.39.1/xs/src/admesh/stl.h000066400000000000000000000177031324354444700201700ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #ifndef __admesh_stl__ #define __admesh_stl__ #include #include #include #define STL_MAX(A,B) ((A)>(B)? (A):(B)) #define STL_MIN(A,B) ((A)<(B)? (A):(B)) #define ABS(X) ((X) < 0 ? -(X) : (X)) // Size of the binary STL header, free form. #define LABEL_SIZE 80 // Binary STL, length of the "number of faces" counter. #define NUM_FACET_SIZE 4 // Binary STL, sizeof header + number of faces. #define HEADER_SIZE 84 #define STL_MIN_FILE_SIZE 284 #define ASCII_LINES_PER_FACET 7 // Comparing an edge by memcmp, 2x3x4 bytes = 24 #define SIZEOF_EDGE_SORT 24 typedef struct { float x; float y; float z; } stl_vertex; static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect"); typedef struct { float x; float y; float z; } stl_normal; static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect"); typedef char stl_extra[2]; typedef struct { stl_normal normal; stl_vertex vertex[3]; stl_extra extra; } stl_facet; #define SIZEOF_STL_FACET 50 static_assert(offsetof(stl_facet, normal) == 0, "stl_facet.normal has correct offset"); static_assert(offsetof(stl_facet, vertex) == 12, "stl_facet.vertex has correct offset"); static_assert(offsetof(stl_facet, extra ) == 48, "stl_facet.extra has correct offset"); static_assert(sizeof(stl_facet) >= SIZEOF_STL_FACET, "size of stl_facet incorrect"); typedef enum {binary, ascii, inmemory} stl_type; typedef struct { stl_vertex p1; stl_vertex p2; int facet_number; } stl_edge; typedef struct stl_hash_edge { // Key of a hash edge: 2x binary copy of a floating point vertex. uint32_t key[6]; // Index of a facet owning this edge. int facet_number; // Index of this edge inside the facet with an index of facet_number. // If this edge is stored backwards, which_edge is increased by 3. int which_edge; struct stl_hash_edge *next; } stl_hash_edge; static_assert(offsetof(stl_hash_edge, facet_number) == SIZEOF_EDGE_SORT, "size of stl_hash_edge.key incorrect"); typedef struct { // Index of a neighbor facet. int neighbor[3]; // Index of an opposite vertex at the neighbor face. char which_vertex_not[3]; } stl_neighbors; typedef struct { int vertex[3]; } v_indices_struct; typedef struct { char header[81]; stl_type type; uint32_t number_of_facets; stl_vertex max; stl_vertex min; stl_vertex size; float bounding_diameter; float shortest_edge; float volume; unsigned number_of_blocks; int connected_edges; int connected_facets_1_edge; int connected_facets_2_edge; int connected_facets_3_edge; int facets_w_1_bad_edge; int facets_w_2_bad_edge; int facets_w_3_bad_edge; int original_num_facets; int edges_fixed; int degenerate_facets; int facets_removed; int facets_added; int facets_reversed; int backwards_edges; int normals_fixed; int number_of_parts; int malloced; int freed; int facets_malloced; int collisions; int shared_vertices; int shared_malloced; } stl_stats; typedef struct { FILE *fp; stl_facet *facet_start; stl_edge *edge_start; stl_hash_edge **heads; stl_hash_edge *tail; int M; stl_neighbors *neighbors_start; v_indices_struct *v_indices; stl_vertex *v_shared; stl_stats stats; char error; } stl_file; extern void stl_open(stl_file *stl, const char *file); extern void stl_close(stl_file *stl); extern void stl_stats_out(stl_file *stl, FILE *file, char *input_file); extern void stl_print_edges(stl_file *stl, FILE *file); extern void stl_print_neighbors(stl_file *stl, char *file); extern void stl_put_little_int(FILE *fp, int value_in); extern void stl_put_little_float(FILE *fp, float value_in); extern void stl_write_ascii(stl_file *stl, const char *file, const char *label); extern void stl_write_binary(stl_file *stl, const char *file, const char *label); extern void stl_write_binary_block(stl_file *stl, FILE *fp); extern void stl_check_facets_exact(stl_file *stl); extern void stl_check_facets_nearby(stl_file *stl, float tolerance); extern void stl_remove_unconnected_facets(stl_file *stl); extern void stl_write_vertex(stl_file *stl, int facet, int vertex); extern void stl_write_facet(stl_file *stl, char *label, int facet); extern void stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge); extern void stl_write_neighbor(stl_file *stl, int facet); extern void stl_write_quad_object(stl_file *stl, char *file); extern void stl_verify_neighbors(stl_file *stl); extern void stl_fill_holes(stl_file *stl); extern void stl_fix_normal_directions(stl_file *stl); extern void stl_fix_normal_values(stl_file *stl); extern void stl_reverse_all_facets(stl_file *stl); extern void stl_translate(stl_file *stl, float x, float y, float z); extern void stl_translate_relative(stl_file *stl, float x, float y, float z); extern void stl_scale_versor(stl_file *stl, float versor[3]); extern void stl_scale(stl_file *stl, float factor); extern void stl_rotate_x(stl_file *stl, float angle); extern void stl_rotate_y(stl_file *stl, float angle); extern void stl_rotate_z(stl_file *stl, float angle); extern void stl_mirror_xy(stl_file *stl); extern void stl_mirror_yz(stl_file *stl); extern void stl_mirror_xz(stl_file *stl); extern void stl_transform(stl_file *stl, float *trafo3x4); extern void stl_open_merge(stl_file *stl, char *file); extern void stl_invalidate_shared_vertices(stl_file *stl); extern void stl_generate_shared_vertices(stl_file *stl); extern void stl_write_obj(stl_file *stl, char *file); extern void stl_write_off(stl_file *stl, char *file); extern void stl_write_dxf(stl_file *stl, char *file, char *label); extern void stl_write_vrml(stl_file *stl, char *file); extern void stl_calculate_normal(float normal[], stl_facet *facet); extern void stl_normalize_vector(float v[]); extern void stl_calculate_volume(stl_file *stl); extern void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int tolerance_flag, float tolerance, int increment_flag, float increment, int nearby_flag, int iterations, int remove_unconnected_flag, int fill_holes_flag, int normal_directions_flag, int normal_values_flag, int reverse_all_flag, int verbose_flag); extern void stl_initialize(stl_file *stl); extern void stl_count_facets(stl_file *stl, const char *file); extern void stl_allocate(stl_file *stl); extern void stl_read(stl_file *stl, int first_facet, int first); extern void stl_facet_stats(stl_file *stl, stl_facet facet, int first); extern void stl_reallocate(stl_file *stl); extern void stl_add_facet(stl_file *stl, stl_facet *new_facet); extern void stl_get_size(stl_file *stl); extern void stl_clear_error(stl_file *stl); extern int stl_get_error(stl_file *stl); extern void stl_exit_on_error(stl_file *stl); #endif Slic3r-version_1.39.1/xs/src/admesh/stl_io.cpp000066400000000000000000000340341324354444700212060ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include "stl.h" #include #include #if !defined(SEEK_SET) #define SEEK_SET 0 #define SEEK_CUR 1 #define SEEK_END 2 #endif void stl_print_edges(stl_file *stl, FILE *file) { int i; int edges_allocated; if (stl->error) return; edges_allocated = stl->stats.number_of_facets * 3; for(i = 0; i < edges_allocated; i++) { fprintf(file, "%d, %f, %f, %f, %f, %f, %f\n", stl->edge_start[i].facet_number, stl->edge_start[i].p1.x, stl->edge_start[i].p1.y, stl->edge_start[i].p1.z, stl->edge_start[i].p2.x, stl->edge_start[i].p2.y, stl->edge_start[i].p2.z); } } void stl_stats_out(stl_file *stl, FILE *file, char *input_file) { if (stl->error) return; /* this is here for Slic3r, without our config.h it won't use this part of the code anyway */ #ifndef VERSION #define VERSION "unknown" #endif fprintf(file, "\n\ ================= Results produced by ADMesh version " VERSION " ================\n"); fprintf(file, "\ Input file : %s\n", input_file); if(stl->stats.type == binary) { fprintf(file, "\ File type : Binary STL file\n"); } else { fprintf(file, "\ File type : ASCII STL file\n"); } fprintf(file, "\ Header : %s\n", stl->stats.header); fprintf(file, "============== Size ==============\n"); fprintf(file, "Min X = % f, Max X = % f\n", stl->stats.min.x, stl->stats.max.x); fprintf(file, "Min Y = % f, Max Y = % f\n", stl->stats.min.y, stl->stats.max.y); fprintf(file, "Min Z = % f, Max Z = % f\n", stl->stats.min.z, stl->stats.max.z); fprintf(file, "\ ========= Facet Status ========== Original ============ Final ====\n"); fprintf(file, "\ Number of facets : %5d %5d\n", stl->stats.original_num_facets, stl->stats.number_of_facets); fprintf(file, "\ Facets with 1 disconnected edge : %5d %5d\n", stl->stats.facets_w_1_bad_edge, stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge); fprintf(file, "\ Facets with 2 disconnected edges : %5d %5d\n", stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge); fprintf(file, "\ Facets with 3 disconnected edges : %5d %5d\n", stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_1_edge); fprintf(file, "\ Total disconnected facets : %5d %5d\n", stl->stats.facets_w_1_bad_edge + stl->stats.facets_w_2_bad_edge + stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_3_edge); fprintf(file, "=== Processing Statistics === ===== Other Statistics =====\n"); fprintf(file, "\ Number of parts : %5d Volume : % f\n", stl->stats.number_of_parts, stl->stats.volume); fprintf(file, "\ Degenerate facets : %5d\n", stl->stats.degenerate_facets); fprintf(file, "\ Edges fixed : %5d\n", stl->stats.edges_fixed); fprintf(file, "\ Facets removed : %5d\n", stl->stats.facets_removed); fprintf(file, "\ Facets added : %5d\n", stl->stats.facets_added); fprintf(file, "\ Facets reversed : %5d\n", stl->stats.facets_reversed); fprintf(file, "\ Backwards edges : %5d\n", stl->stats.backwards_edges); fprintf(file, "\ Normals fixed : %5d\n", stl->stats.normals_fixed); } void stl_write_ascii(stl_file *stl, const char *file, const char *label) { int i; char *error_msg; if (stl->error) return; /* Open the file */ FILE *fp = boost::nowide::fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } fprintf(fp, "solid %s\n", label); for(i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, " facet normal % .8E % .8E % .8E\n", stl->facet_start[i].normal.x, stl->facet_start[i].normal.y, stl->facet_start[i].normal.z); fprintf(fp, " outer loop\n"); fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[0].x, stl->facet_start[i].vertex[0].y, stl->facet_start[i].vertex[0].z); fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[1].x, stl->facet_start[i].vertex[1].y, stl->facet_start[i].vertex[1].z); fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, stl->facet_start[i].vertex[2].z); fprintf(fp, " endloop\n"); fprintf(fp, " endfacet\n"); } fprintf(fp, "endsolid %s\n", label); fclose(fp); } void stl_print_neighbors(stl_file *stl, char *file) { int i; FILE *fp; char *error_msg; if (stl->error) return; /* Open the file */ fp = boost::nowide::fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_print_neighbors: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } for(i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n", i, stl->neighbors_start[i].neighbor[0], (int)stl->neighbors_start[i].which_vertex_not[0], stl->neighbors_start[i].neighbor[1], (int)stl->neighbors_start[i].which_vertex_not[1], stl->neighbors_start[i].neighbor[2], (int)stl->neighbors_start[i].which_vertex_not[2]); } fclose(fp); } #ifndef BOOST_LITTLE_ENDIAN // Swap a buffer of 32bit data from little endian to big endian and vice versa. void stl_internal_reverse_quads(char *buf, size_t cnt) { for (size_t i = 0; i < cnt; i += 4) { std::swap(buf[i], buf[i+3]); std::swap(buf[i+1], buf[i+2]); } } #endif void stl_write_binary(stl_file *stl, const char *file, const char *label) { FILE *fp; int i; char *error_msg; if (stl->error) return; /* Open the file */ fp = boost::nowide::fopen(file, "wb"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_binary: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } fprintf(fp, "%s", label); for(i = strlen(label); i < LABEL_SIZE; i++) putc(0, fp); fseek(fp, LABEL_SIZE, SEEK_SET); #ifdef BOOST_LITTLE_ENDIAN fwrite(&stl->stats.number_of_facets, 4, 1, fp); for (i = 0; i < stl->stats.number_of_facets; ++ i) fwrite(stl->facet_start + i, SIZEOF_STL_FACET, 1, fp); #else /* BOOST_LITTLE_ENDIAN */ char buffer[50]; // Convert the number of facets to little endian. memcpy(buffer, &stl->stats.number_of_facets, 4); stl_internal_reverse_quads(buffer, 4); fwrite(buffer, 4, 1, fp); for (i = 0; i < stl->stats.number_of_facets; ++ i) { memcpy(buffer, stl->facet_start + i, 50); // Convert to little endian. stl_internal_reverse_quads(buffer, 48); fwrite(buffer, SIZEOF_STL_FACET, 1, fp); } #endif /* BOOST_LITTLE_ENDIAN */ fclose(fp); } void stl_write_vertex(stl_file *stl, int facet, int vertex) { if (stl->error) return; printf(" vertex %d/%d % .8E % .8E % .8E\n", vertex, facet, stl->facet_start[facet].vertex[vertex].x, stl->facet_start[facet].vertex[vertex].y, stl->facet_start[facet].vertex[vertex].z); } void stl_write_facet(stl_file *stl, char *label, int facet) { if (stl->error) return; printf("facet (%d)/ %s\n", facet, label); stl_write_vertex(stl, facet, 0); stl_write_vertex(stl, facet, 1); stl_write_vertex(stl, facet, 2); } void stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge) { if (stl->error) return; printf("edge (%d)/(%d) %s\n", edge.facet_number, edge.which_edge, label); if(edge.which_edge < 3) { stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3); stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3); } else { stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3); stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3); } } void stl_write_neighbor(stl_file *stl, int facet) { if (stl->error) return; printf("Neighbors %d: %d, %d, %d ; %d, %d, %d\n", facet, stl->neighbors_start[facet].neighbor[0], stl->neighbors_start[facet].neighbor[1], stl->neighbors_start[facet].neighbor[2], stl->neighbors_start[facet].which_vertex_not[0], stl->neighbors_start[facet].which_vertex_not[1], stl->neighbors_start[facet].which_vertex_not[2]); } void stl_write_quad_object(stl_file *stl, char *file) { FILE *fp; int i; int j; char *error_msg; stl_vertex connect_color; stl_vertex uncon_1_color; stl_vertex uncon_2_color; stl_vertex uncon_3_color; stl_vertex color; if (stl->error) return; /* Open the file */ fp = boost::nowide::fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_quad_object: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } connect_color.x = 0.0; connect_color.y = 0.0; connect_color.z = 1.0; uncon_1_color.x = 0.0; uncon_1_color.y = 1.0; uncon_1_color.z = 0.0; uncon_2_color.x = 1.0; uncon_2_color.y = 1.0; uncon_2_color.z = 1.0; uncon_3_color.x = 1.0; uncon_3_color.y = 0.0; uncon_3_color.z = 0.0; fprintf(fp, "CQUAD\n"); for(i = 0; i < stl->stats.number_of_facets; i++) { j = ((stl->neighbors_start[i].neighbor[0] == -1) + (stl->neighbors_start[i].neighbor[1] == -1) + (stl->neighbors_start[i].neighbor[2] == -1)); if(j == 0) { color = connect_color; } else if(j == 1) { color = uncon_1_color; } else if(j == 2) { color = uncon_2_color; } else { color = uncon_3_color; } fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[0].x, stl->facet_start[i].vertex[0].y, stl->facet_start[i].vertex[0].z, color.x, color.y, color.z); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[1].x, stl->facet_start[i].vertex[1].y, stl->facet_start[i].vertex[1].z, color.x, color.y, color.z); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, stl->facet_start[i].vertex[2].z, color.x, color.y, color.z); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, stl->facet_start[i].vertex[2].z, color.x, color.y, color.z); } fclose(fp); } void stl_write_dxf(stl_file *stl, char *file, char *label) { int i; FILE *fp; char *error_msg; if (stl->error) return; /* Open the file */ fp = boost::nowide::fopen(file, "w"); if(fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); stl->error = 1; return; } fprintf(fp, "999\n%s\n", label); fprintf(fp, "0\nSECTION\n2\nHEADER\n0\nENDSEC\n"); fprintf(fp, "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nLAYER\n70\n1\n\ 0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n0\nENDTAB\n0\nENDSEC\n"); fprintf(fp, "0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n"); fprintf(fp, "0\nSECTION\n2\nENTITIES\n"); for(i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "0\n3DFACE\n8\n0\n"); fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", stl->facet_start[i].vertex[0].x, stl->facet_start[i].vertex[0].y, stl->facet_start[i].vertex[0].z); fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", stl->facet_start[i].vertex[1].x, stl->facet_start[i].vertex[1].y, stl->facet_start[i].vertex[1].z); fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, stl->facet_start[i].vertex[2].z); fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", stl->facet_start[i].vertex[2].x, stl->facet_start[i].vertex[2].y, stl->facet_start[i].vertex[2].z); } fprintf(fp, "0\nENDSEC\n0\nEOF\n"); fclose(fp); } void stl_clear_error(stl_file *stl) { stl->error = 0; } void stl_exit_on_error(stl_file *stl) { if (!stl->error) return; stl->error = 0; stl_close(stl); exit(1); } int stl_get_error(stl_file *stl) { return stl->error; } Slic3r-version_1.39.1/xs/src/admesh/stlinit.cpp000066400000000000000000000404711324354444700214050ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include #include #include #include #include #include "stl.h" #ifndef SEEK_SET #error "SEEK_SET not defined" #endif void stl_open(stl_file *stl, const char *file) { stl_initialize(stl); stl_count_facets(stl, file); stl_allocate(stl); stl_read(stl, 0, 1); if (!stl->error) fclose(stl->fp); } void stl_initialize(stl_file *stl) { memset(stl, 0, sizeof(stl_file)); stl->stats.volume = -1.0; } #ifndef BOOST_LITTLE_ENDIAN extern void stl_internal_reverse_quads(char *buf, size_t cnt); #endif /* BOOST_LITTLE_ENDIAN */ void stl_count_facets(stl_file *stl, const char *file) { long file_size; uint32_t header_num_facets; uint32_t num_facets; int i; size_t s; unsigned char chtest[128]; int num_lines = 1; char *error_msg; if (stl->error) return; /* Open the file in binary mode first */ stl->fp = boost::nowide::fopen(file, "rb"); if(stl->fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", file); perror(error_msg); free(error_msg); stl->error = 1; return; } /* Find size of file */ fseek(stl->fp, 0, SEEK_END); file_size = ftell(stl->fp); /* Check for binary or ASCII file */ fseek(stl->fp, HEADER_SIZE, SEEK_SET); if (!fread(chtest, sizeof(chtest), 1, stl->fp)) { perror("The input is an empty file"); stl->error = 1; return; } stl->stats.type = ascii; for(s = 0; s < sizeof(chtest); s++) { if(chtest[s] > 127) { stl->stats.type = binary; break; } } rewind(stl->fp); /* Get the header and the number of facets in the .STL file */ /* If the .STL file is binary, then do the following */ if(stl->stats.type == binary) { /* Test if the STL file has the right size */ if(((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) || (file_size < STL_MIN_FILE_SIZE)) { fprintf(stderr, "The file %s has the wrong size.\n", file); stl->error = 1; return; } num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET; /* Read the header */ if (fread(stl->stats.header, LABEL_SIZE, 1, stl->fp) > 79) { stl->stats.header[80] = '\0'; } /* Read the int following the header. This should contain # of facets */ bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp); #ifndef BOOST_LITTLE_ENDIAN // Convert from little endian to big endian. stl_internal_reverse_quads((char*)&header_num_facets, 4); #endif /* BOOST_LITTLE_ENDIAN */ if (! header_num_faces_read || num_facets != header_num_facets) { fprintf(stderr, "Warning: File size doesn't match number of facets in the header\n"); } } /* Otherwise, if the .STL file is ASCII, then do the following */ else { /* Reopen the file in text mode (for getting correct newlines on Windows) */ // fix to silence a warning about unused return value. // obviously if it fails we have problems.... stl->fp = boost::nowide::freopen(file, "r", stl->fp); // do another null check to be safe if(stl->fp == NULL) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", file); perror(error_msg); free(error_msg); stl->error = 1; return; } /* Find the number of facets */ char linebuf[100]; while (fgets(linebuf, 100, stl->fp) != NULL) { /* don't count short lines */ if (strlen(linebuf) <= 4) continue; /* skip solid/endsolid lines as broken STL file generators may put several of them */ if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) continue; ++num_lines; } rewind(stl->fp); /* Get the header */ for(i = 0; (i < 80) && (stl->stats.header[i] = getc(stl->fp)) != '\n'; i++); stl->stats.header[i] = '\0'; /* Lose the '\n' */ stl->stats.header[80] = '\0'; num_facets = num_lines / ASCII_LINES_PER_FACET; } stl->stats.number_of_facets += num_facets; stl->stats.original_num_facets = stl->stats.number_of_facets; } void stl_allocate(stl_file *stl) { if (stl->error) return; /* Allocate memory for the entire .STL file */ stl->facet_start = (stl_facet*)calloc(stl->stats.number_of_facets, sizeof(stl_facet)); if(stl->facet_start == NULL) perror("stl_initialize"); stl->stats.facets_malloced = stl->stats.number_of_facets; /* Allocate memory for the neighbors list */ stl->neighbors_start = (stl_neighbors*) calloc(stl->stats.number_of_facets, sizeof(stl_neighbors)); if(stl->facet_start == NULL) perror("stl_initialize"); } void stl_open_merge(stl_file *stl, char *file_to_merge) { int num_facets_so_far; stl_type origStlType; FILE *origFp; stl_file stl_to_merge; if (stl->error) return; /* Record how many facets we have so far from the first file. We will start putting facets in the next position. Since we're 0-indexed, it'l be the same position. */ num_facets_so_far = stl->stats.number_of_facets; /* Record the file type we started with: */ origStlType=stl->stats.type; /* Record the file pointer too: */ origFp=stl->fp; /* Initialize the sturucture with zero stats, header info and sizes: */ stl_initialize(&stl_to_merge); stl_count_facets(&stl_to_merge, file_to_merge); /* Copy what we need to into stl so that we can read the file_to_merge directly into it using stl_read: Save the rest of the valuable info: */ stl->stats.type=stl_to_merge.stats.type; stl->fp=stl_to_merge.fp; /* Add the number of facets we already have in stl with what we we found in stl_to_merge but haven't read yet. */ stl->stats.number_of_facets=num_facets_so_far+stl_to_merge.stats.number_of_facets; /* Allocate enough room for stl->stats.number_of_facets facets and neighbors: */ stl_reallocate(stl); /* Read the file to merge directly into stl, adding it to what we have already. Start at num_facets_so_far, the index to the first unused facet. Also say that this isn't our first time so we should augment stats like min and max instead of erasing them. */ stl_read(stl, num_facets_so_far, 0); /* Restore the stl information we overwrote (for stl_read) so that it still accurately reflects the subject part: */ stl->stats.type=origStlType; stl->fp=origFp; } extern void stl_reallocate(stl_file *stl) { if (stl->error) return; /* Reallocate more memory for the .STL file(s) */ stl->facet_start = (stl_facet*)realloc(stl->facet_start, stl->stats.number_of_facets * sizeof(stl_facet)); if(stl->facet_start == NULL) perror("stl_initialize"); stl->stats.facets_malloced = stl->stats.number_of_facets; /* Reallocate more memory for the neighbors list */ stl->neighbors_start = (stl_neighbors*) realloc(stl->neighbors_start, stl->stats.number_of_facets * sizeof(stl_neighbors)); if(stl->facet_start == NULL) perror("stl_initialize"); } /* Reads the contents of the file pointed to by stl->fp into the stl structure, starting at facet first_facet. The second argument says if it's our first time running this for the stl and therefore we should reset our max and min stats. */ void stl_read(stl_file *stl, int first_facet, int first) { stl_facet facet; int i; if (stl->error) return; if(stl->stats.type == binary) { fseek(stl->fp, HEADER_SIZE, SEEK_SET); } else { rewind(stl->fp); } char normal_buf[3][32]; for(i = first_facet; i < stl->stats.number_of_facets; i++) { if(stl->stats.type == binary) /* Read a single facet from a binary .STL file */ { /* we assume little-endian architecture! */ if (fread(&facet, 1, SIZEOF_STL_FACET, stl->fp) != SIZEOF_STL_FACET) { stl->error = 1; return; } #ifndef BOOST_LITTLE_ENDIAN // Convert the loaded little endian data to big endian. stl_internal_reverse_quads((char*)&facet, 48); #endif /* BOOST_LITTLE_ENDIAN */ } else /* Read a single facet from an ASCII .STL file */ { // skip solid/endsolid // (in this order, otherwise it won't work when they are paired in the middle of a file) fscanf(stl->fp, "endsolid\n"); fscanf(stl->fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid") // Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs. int res_normal = fscanf(stl->fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); assert(res_normal == 3); int res_outer_loop = fscanf(stl->fp, " outer loop"); assert(res_outer_loop == 0); int res_vertex1 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z); assert(res_vertex1 == 3); int res_vertex2 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z); assert(res_vertex2 == 3); int res_vertex3 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z); assert(res_vertex3 == 3); int res_endloop = fscanf(stl->fp, " endloop"); assert(res_endloop == 0); // There is a leading and trailing white space around endfacet to eat up all leading and trailing white spaces including numerous tabs and new lines. int res_endfacet = fscanf(stl->fp, " endfacet "); if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { perror("Something is syntactically very wrong with this ASCII STL!"); stl->error = 1; return; } // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. if (sscanf(normal_buf[0], "%f", &facet.normal.x) != 1 || sscanf(normal_buf[1], "%f", &facet.normal.y) != 1 || sscanf(normal_buf[2], "%f", &facet.normal.z) != 1) { // Normal was mangled. Maybe denormals or "not a number" were stored? // Just reset the normal and silently ignore it. memset(&facet.normal, 0, sizeof(facet.normal)); } } #if 0 // Report close to zero vertex coordinates. Due to the nature of the floating point numbers, // close to zero values may be represented with singificantly higher precision than the rest of the vertices. // It may be worth to round these numbers to zero during loading to reduce the number of errors reported // during the STL import. for (size_t j = 0; j < 3; ++ j) { if (facet.vertex[j].x > -1e-12f && facet.vertex[j].x < 1e-12f) printf("stl_read: facet %d.x = %e\r\n", j, facet.vertex[j].x); if (facet.vertex[j].y > -1e-12f && facet.vertex[j].y < 1e-12f) printf("stl_read: facet %d.y = %e\r\n", j, facet.vertex[j].y); if (facet.vertex[j].z > -1e-12f && facet.vertex[j].z < 1e-12f) printf("stl_read: facet %d.z = %e\r\n", j, facet.vertex[j].z); } #endif #if 1 { // Positive and negative zeros are possible in the floats, which are considered equal by the FP unit. // When using a memcmp on raw floats, those numbers report to be different. // Unify all +0 and -0 to +0 to make the floats equal under memcmp. uint32_t *f = (uint32_t*)&facet; for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats if (*f == 0x80000000) // Negative zero, switch to positive zero. *f = 0; } #else { // Due to the nature of the floating point numbers, close to zero values may be represented with singificantly higher precision // than the rest of the vertices. Round them to zero. float *f = (float*)&facet; for (int j = 0; j < 12; ++ j, ++ f) // 3x vertex + normal: 4x3 = 12 floats if (*f > -1e-12f && *f < 1e-12f) // Negative zero, switch to positive zero. *f = 0; } #endif /* Write the facet into memory. */ memcpy(stl->facet_start+i, &facet, SIZEOF_STL_FACET); stl_facet_stats(stl, facet, first); first = 0; } stl->stats.size.x = stl->stats.max.x - stl->stats.min.x; stl->stats.size.y = stl->stats.max.y - stl->stats.min.y; stl->stats.size.z = stl->stats.max.z - stl->stats.min.z; stl->stats.bounding_diameter = sqrt( stl->stats.size.x * stl->stats.size.x + stl->stats.size.y * stl->stats.size.y + stl->stats.size.z * stl->stats.size.z ); } void stl_facet_stats(stl_file *stl, stl_facet facet, int first) { float diff_x; float diff_y; float diff_z; float max_diff; if (stl->error) return; /* while we are going through all of the facets, let's find the */ /* maximum and minimum values for x, y, and z */ /* Initialize the max and min values the first time through*/ if (first) { stl->stats.max.x = facet.vertex[0].x; stl->stats.min.x = facet.vertex[0].x; stl->stats.max.y = facet.vertex[0].y; stl->stats.min.y = facet.vertex[0].y; stl->stats.max.z = facet.vertex[0].z; stl->stats.min.z = facet.vertex[0].z; diff_x = ABS(facet.vertex[0].x - facet.vertex[1].x); diff_y = ABS(facet.vertex[0].y - facet.vertex[1].y); diff_z = ABS(facet.vertex[0].z - facet.vertex[1].z); max_diff = STL_MAX(diff_x, diff_y); max_diff = STL_MAX(diff_z, max_diff); stl->stats.shortest_edge = max_diff; first = 0; } /* now find the max and min values */ stl->stats.max.x = STL_MAX(stl->stats.max.x, facet.vertex[0].x); stl->stats.min.x = STL_MIN(stl->stats.min.x, facet.vertex[0].x); stl->stats.max.y = STL_MAX(stl->stats.max.y, facet.vertex[0].y); stl->stats.min.y = STL_MIN(stl->stats.min.y, facet.vertex[0].y); stl->stats.max.z = STL_MAX(stl->stats.max.z, facet.vertex[0].z); stl->stats.min.z = STL_MIN(stl->stats.min.z, facet.vertex[0].z); stl->stats.max.x = STL_MAX(stl->stats.max.x, facet.vertex[1].x); stl->stats.min.x = STL_MIN(stl->stats.min.x, facet.vertex[1].x); stl->stats.max.y = STL_MAX(stl->stats.max.y, facet.vertex[1].y); stl->stats.min.y = STL_MIN(stl->stats.min.y, facet.vertex[1].y); stl->stats.max.z = STL_MAX(stl->stats.max.z, facet.vertex[1].z); stl->stats.min.z = STL_MIN(stl->stats.min.z, facet.vertex[1].z); stl->stats.max.x = STL_MAX(stl->stats.max.x, facet.vertex[2].x); stl->stats.min.x = STL_MIN(stl->stats.min.x, facet.vertex[2].x); stl->stats.max.y = STL_MAX(stl->stats.max.y, facet.vertex[2].y); stl->stats.min.y = STL_MIN(stl->stats.min.y, facet.vertex[2].y); stl->stats.max.z = STL_MAX(stl->stats.max.z, facet.vertex[2].z); stl->stats.min.z = STL_MIN(stl->stats.min.z, facet.vertex[2].z); } void stl_close(stl_file *stl) { if (stl->error) return; if(stl->neighbors_start != NULL) free(stl->neighbors_start); if(stl->facet_start != NULL) free(stl->facet_start); if(stl->v_indices != NULL) free(stl->v_indices); if(stl->v_shared != NULL) free(stl->v_shared); } Slic3r-version_1.39.1/xs/src/admesh/util.cpp000066400000000000000000000407711324354444700206770ustar00rootroot00000000000000/* ADMesh -- process triangulated solid meshes * Copyright (C) 1995, 1996 Anthony D. Martin * Copyright (C) 2013, 2014 several contributors, see AUTHORS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Questions, comments, suggestions, etc to * https://github.com/admesh/admesh/issues */ #include #include #include #include #include "stl.h" static void stl_rotate(float *x, float *y, const double c, const double s); static float get_area(stl_facet *facet); static float get_volume(stl_file *stl); void stl_verify_neighbors(stl_file *stl) { int i; int j; stl_edge edge_a; stl_edge edge_b; int neighbor; int vnot; if (stl->error) return; stl->stats.backwards_edges = 0; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { edge_a.p1 = stl->facet_start[i].vertex[j]; edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3]; neighbor = stl->neighbors_start[i].neighbor[j]; vnot = stl->neighbors_start[i].which_vertex_not[j]; if(neighbor == -1) continue; /* this edge has no neighbor... Continue. */ if(vnot < 3) { edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; } else { stl->stats.backwards_edges += 1; edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; } if(memcmp(&edge_a, &edge_b, SIZEOF_EDGE_SORT) != 0) { /* These edges should match but they don't. Print results. */ printf("edge %d of facet %d doesn't match edge %d of facet %d\n", j, i, vnot + 1, neighbor); stl_write_facet(stl, (char*)"first facet", i); stl_write_facet(stl, (char*)"second facet", neighbor); } } } } void stl_translate(stl_file *stl, float x, float y, float z) { int i; int j; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->facet_start[i].vertex[j].x -= (stl->stats.min.x - x); stl->facet_start[i].vertex[j].y -= (stl->stats.min.y - y); stl->facet_start[i].vertex[j].z -= (stl->stats.min.z - z); } } stl->stats.max.x -= (stl->stats.min.x - x); stl->stats.max.y -= (stl->stats.min.y - y); stl->stats.max.z -= (stl->stats.min.z - z); stl->stats.min.x = x; stl->stats.min.y = y; stl->stats.min.z = z; stl_invalidate_shared_vertices(stl); } /* Translates the stl by x,y,z, relatively from wherever it is currently */ void stl_translate_relative(stl_file *stl, float x, float y, float z) { int i; int j; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->facet_start[i].vertex[j].x += x; stl->facet_start[i].vertex[j].y += y; stl->facet_start[i].vertex[j].z += z; } } stl->stats.min.x += x; stl->stats.min.y += y; stl->stats.min.z += z; stl->stats.max.x += x; stl->stats.max.y += y; stl->stats.max.z += z; stl_invalidate_shared_vertices(stl); } void stl_scale_versor(stl_file *stl, float versor[3]) { int i; int j; if (stl->error) return; /* scale extents */ stl->stats.min.x *= versor[0]; stl->stats.min.y *= versor[1]; stl->stats.min.z *= versor[2]; stl->stats.max.x *= versor[0]; stl->stats.max.y *= versor[1]; stl->stats.max.z *= versor[2]; /* scale size */ stl->stats.size.x *= versor[0]; stl->stats.size.y *= versor[1]; stl->stats.size.z *= versor[2]; /* scale volume */ if (stl->stats.volume > 0.0) { stl->stats.volume *= (versor[0] * versor[1] * versor[2]); } for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->facet_start[i].vertex[j].x *= versor[0]; stl->facet_start[i].vertex[j].y *= versor[1]; stl->facet_start[i].vertex[j].z *= versor[2]; } } stl_invalidate_shared_vertices(stl); } void stl_scale(stl_file *stl, float factor) { float versor[3]; if (stl->error) return; versor[0] = factor; versor[1] = factor; versor[2] = factor; stl_scale_versor(stl, versor); } static void calculate_normals(stl_file *stl) { float normal[3]; if (stl->error) return; for(uint32_t i = 0; i < stl->stats.number_of_facets; i++) { stl_calculate_normal(normal, &stl->facet_start[i]); stl_normalize_vector(normal); stl->facet_start[i].normal.x = normal[0]; stl->facet_start[i].normal.y = normal[1]; stl->facet_start[i].normal.z = normal[2]; } } void stl_transform(stl_file *stl, float *trafo3x4) { int i_face, i_vertex; if (stl->error) return; for (i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) { stl_vertex *vertices = stl->facet_start[i_face].vertex; for (i_vertex = 0; i_vertex < 3; ++ i_vertex) { stl_vertex &v_dst = vertices[i_vertex]; stl_vertex v_src = v_dst; v_dst.x = trafo3x4[0] * v_src.x + trafo3x4[1] * v_src.y + trafo3x4[2] * v_src.z + trafo3x4[3]; v_dst.y = trafo3x4[4] * v_src.x + trafo3x4[5] * v_src.y + trafo3x4[6] * v_src.z + trafo3x4[7]; v_dst.z = trafo3x4[8] * v_src.x + trafo3x4[9] * v_src.y + trafo3x4[10] * v_src.z + trafo3x4[11]; } } stl_get_size(stl); calculate_normals(stl); } void stl_rotate_x(stl_file *stl, float angle) { int i; int j; double radian_angle = (angle / 180.0) * M_PI; double c = cos(radian_angle); double s = sin(radian_angle); if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl_rotate(&stl->facet_start[i].vertex[j].y, &stl->facet_start[i].vertex[j].z, c, s); } } stl_get_size(stl); calculate_normals(stl); } void stl_rotate_y(stl_file *stl, float angle) { int i; int j; double radian_angle = (angle / 180.0) * M_PI; double c = cos(radian_angle); double s = sin(radian_angle); if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl_rotate(&stl->facet_start[i].vertex[j].z, &stl->facet_start[i].vertex[j].x, c, s); } } stl_get_size(stl); calculate_normals(stl); } void stl_rotate_z(stl_file *stl, float angle) { int i; int j; double radian_angle = (angle / 180.0) * M_PI; double c = cos(radian_angle); double s = sin(radian_angle); if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl_rotate(&stl->facet_start[i].vertex[j].x, &stl->facet_start[i].vertex[j].y, c, s); } } stl_get_size(stl); calculate_normals(stl); } static void stl_rotate(float *x, float *y, const double c, const double s) { double xold = *x; double yold = *y; *x = float(c * xold - s * yold); *y = float(s * xold + c * yold); } extern void stl_get_size(stl_file *stl) { int i; int j; if (stl->error) return; if (stl->stats.number_of_facets == 0) return; stl->stats.min.x = stl->facet_start[0].vertex[0].x; stl->stats.min.y = stl->facet_start[0].vertex[0].y; stl->stats.min.z = stl->facet_start[0].vertex[0].z; stl->stats.max.x = stl->facet_start[0].vertex[0].x; stl->stats.max.y = stl->facet_start[0].vertex[0].y; stl->stats.max.z = stl->facet_start[0].vertex[0].z; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->stats.min.x = STL_MIN(stl->stats.min.x, stl->facet_start[i].vertex[j].x); stl->stats.min.y = STL_MIN(stl->stats.min.y, stl->facet_start[i].vertex[j].y); stl->stats.min.z = STL_MIN(stl->stats.min.z, stl->facet_start[i].vertex[j].z); stl->stats.max.x = STL_MAX(stl->stats.max.x, stl->facet_start[i].vertex[j].x); stl->stats.max.y = STL_MAX(stl->stats.max.y, stl->facet_start[i].vertex[j].y); stl->stats.max.z = STL_MAX(stl->stats.max.z, stl->facet_start[i].vertex[j].z); } } stl->stats.size.x = stl->stats.max.x - stl->stats.min.x; stl->stats.size.y = stl->stats.max.y - stl->stats.min.y; stl->stats.size.z = stl->stats.max.z - stl->stats.min.z; stl->stats.bounding_diameter = sqrt( stl->stats.size.x * stl->stats.size.x + stl->stats.size.y * stl->stats.size.y + stl->stats.size.z * stl->stats.size.z ); } void stl_mirror_xy(stl_file *stl) { int i; int j; float temp_size; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->facet_start[i].vertex[j].z *= -1.0; } } temp_size = stl->stats.min.z; stl->stats.min.z = stl->stats.max.z; stl->stats.max.z = temp_size; stl->stats.min.z *= -1.0; stl->stats.max.z *= -1.0; stl_reverse_all_facets(stl); stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } void stl_mirror_yz(stl_file *stl) { int i; int j; float temp_size; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->facet_start[i].vertex[j].x *= -1.0; } } temp_size = stl->stats.min.x; stl->stats.min.x = stl->stats.max.x; stl->stats.max.x = temp_size; stl->stats.min.x *= -1.0; stl->stats.max.x *= -1.0; stl_reverse_all_facets(stl); stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } void stl_mirror_xz(stl_file *stl) { int i; int j; float temp_size; if (stl->error) return; for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl->facet_start[i].vertex[j].y *= -1.0; } } temp_size = stl->stats.min.y; stl->stats.min.y = stl->stats.max.y; stl->stats.max.y = temp_size; stl->stats.min.y *= -1.0; stl->stats.max.y *= -1.0; stl_reverse_all_facets(stl); stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } static float get_volume(stl_file *stl) { stl_vertex p0; stl_vertex p; stl_normal n; float height; float area; float volume = 0.0; if (stl->error) return 0; /* Choose a point, any point as the reference */ p0.x = stl->facet_start[0].vertex[0].x; p0.y = stl->facet_start[0].vertex[0].y; p0.z = stl->facet_start[0].vertex[0].z; for(uint32_t i = 0; i < stl->stats.number_of_facets; i++) { p.x = stl->facet_start[i].vertex[0].x - p0.x; p.y = stl->facet_start[i].vertex[0].y - p0.y; p.z = stl->facet_start[i].vertex[0].z - p0.z; /* Do dot product to get distance from point to plane */ n = stl->facet_start[i].normal; height = (n.x * p.x) + (n.y * p.y) + (n.z * p.z); area = get_area(&stl->facet_start[i]); volume += (area * height) / 3.0f; } return volume; } void stl_calculate_volume(stl_file *stl) { if (stl->error) return; stl->stats.volume = get_volume(stl); if(stl->stats.volume < 0.0) { stl_reverse_all_facets(stl); stl->stats.volume = -stl->stats.volume; } } static float get_area(stl_facet *facet) { double cross[3][3]; float sum[3]; float n[3]; float area; int i; /* cast to double before calculating cross product because large coordinates can result in overflowing product (bad area is responsible for bad volume and bad facets reversal) */ for(i = 0; i < 3; i++) { cross[i][0]=(((double)facet->vertex[i].y * (double)facet->vertex[(i + 1) % 3].z) - ((double)facet->vertex[i].z * (double)facet->vertex[(i + 1) % 3].y)); cross[i][1]=(((double)facet->vertex[i].z * (double)facet->vertex[(i + 1) % 3].x) - ((double)facet->vertex[i].x * (double)facet->vertex[(i + 1) % 3].z)); cross[i][2]=(((double)facet->vertex[i].x * (double)facet->vertex[(i + 1) % 3].y) - ((double)facet->vertex[i].y * (double)facet->vertex[(i + 1) % 3].x)); } sum[0] = cross[0][0] + cross[1][0] + cross[2][0]; sum[1] = cross[0][1] + cross[1][1] + cross[2][1]; sum[2] = cross[0][2] + cross[1][2] + cross[2][2]; /* This should already be done. But just in case, let's do it again */ stl_calculate_normal(n, facet); stl_normalize_vector(n); area = 0.5 * (n[0] * sum[0] + n[1] * sum[1] + n[2] * sum[2]); return area; } void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int tolerance_flag, float tolerance, int increment_flag, float increment, int nearby_flag, int iterations, int remove_unconnected_flag, int fill_holes_flag, int normal_directions_flag, int normal_values_flag, int reverse_all_flag, int verbose_flag) { int i; int last_edges_fixed = 0; if (stl->error) return; if(exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag || fill_holes_flag || normal_directions_flag) { if (verbose_flag) printf("Checking exact...\n"); exact_flag = 1; stl_check_facets_exact(stl); stl->stats.facets_w_1_bad_edge = (stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge); stl->stats.facets_w_2_bad_edge = (stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge); stl->stats.facets_w_3_bad_edge = (stl->stats.number_of_facets - stl->stats.connected_facets_1_edge); } if(nearby_flag || fixall_flag) { if(!tolerance_flag) { tolerance = stl->stats.shortest_edge; } if(!increment_flag) { increment = stl->stats.bounding_diameter / 10000.0; } if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { for(i = 0; i < iterations; i++) { if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { if (verbose_flag) printf("\ Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); stl_check_facets_nearby(stl, tolerance); if (verbose_flag) printf(" Fixed %d edges.\n", stl->stats.edges_fixed - last_edges_fixed); last_edges_fixed = stl->stats.edges_fixed; tolerance += increment; } else { if (verbose_flag) printf("\ All facets connected. No further nearby check necessary.\n"); break; } } } else { if (verbose_flag) printf("All facets connected. No nearby check necessary.\n"); } } if(remove_unconnected_flag || fixall_flag || fill_holes_flag) { if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { if (verbose_flag) printf("Removing unconnected facets...\n"); stl_remove_unconnected_facets(stl); } else if (verbose_flag) printf("No unconnected need to be removed.\n"); } if(fill_holes_flag || fixall_flag) { if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { if (verbose_flag) printf("Filling holes...\n"); stl_fill_holes(stl); } else if (verbose_flag) printf("No holes need to be filled.\n"); } if(reverse_all_flag) { if (verbose_flag) printf("Reversing all facets...\n"); stl_reverse_all_facets(stl); } if(normal_directions_flag || fixall_flag) { if (verbose_flag) printf("Checking normal directions...\n"); stl_fix_normal_directions(stl); } if(normal_values_flag || fixall_flag) { if (verbose_flag) printf("Checking normal values...\n"); stl_fix_normal_values(stl); } /* Always calculate the volume. It shouldn't take too long */ if (verbose_flag) printf("Calculating volume...\n"); stl_calculate_volume(stl); if(exact_flag) { if (verbose_flag) printf("Verifying neighbors...\n"); stl_verify_neighbors(stl); } } Slic3r-version_1.39.1/xs/src/boost/000077500000000000000000000000001324354444700170725ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/boost/nowide/000077500000000000000000000000001324354444700203575ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/boost/nowide/args.hpp000066400000000000000000000111571324354444700220310ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_ARGS_HPP_INCLUDED #define BOOST_NOWIDE_ARGS_HPP_INCLUDED #include #include #include #ifdef BOOST_WINDOWS #include #endif namespace boost { namespace nowide { #if !defined(BOOST_WINDOWS) && !defined(BOOST_NOWIDE_DOXYGEN) class args { public: args(int &,char **&) {} args(int &,char **&,char **&){} ~args() {} }; #else /// /// \brief args is a class that fixes standard main() function arguments and changes them to UTF-8 under /// Microsoft Windows. /// /// The class uses \c GetCommandLineW(), \c CommandLineToArgvW() and \c GetEnvironmentStringsW() /// in order to obtain the information. It does not relates to actual values of argc,argv and env /// under Windows. /// /// It restores the original values in its destructor /// /// \note the class owns the memory of the newly allocated strings /// class args { public: /// /// Fix command line agruments /// args(int &argc,char **&argv) : old_argc_(argc), old_argv_(argv), old_env_(0), old_argc_ptr_(&argc), old_argv_ptr_(&argv), old_env_ptr_(0) { fix_args(argc,argv); } /// /// Fix command line agruments and environment /// args(int &argc,char **&argv,char **&en) : old_argc_(argc), old_argv_(argv), old_env_(en), old_argc_ptr_(&argc), old_argv_ptr_(&argv), old_env_ptr_(&en) { fix_args(argc,argv); fix_env(en); } /// /// Restore original argc,argv,env values, if changed /// ~args() { if(old_argc_ptr_) *old_argc_ptr_ = old_argc_; if(old_argv_ptr_) *old_argv_ptr_ = old_argv_; if(old_env_ptr_) *old_env_ptr_ = old_env_; } private: void fix_args(int &argc,char **&argv) { int wargc; wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(),&wargc); if(!wargv) { argc = 0; static char *dummy = 0; argv = &dummy; return; } try{ args_.resize(wargc+1,0); arg_values_.resize(wargc); for(int i=0;i args_; std::vector arg_values_; stackstring env_; std::vector envp_; int old_argc_; char **old_argv_; char **old_env_; int *old_argc_ptr_; char ***old_argv_ptr_; char ***old_env_ptr_; }; #endif } // nowide } // namespace boost #endif /// // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/boost/nowide/cenv.hpp000066400000000000000000000070421324354444700220260ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_CENV_H_INCLUDED #define BOOST_NOWIDE_CENV_H_INCLUDED #include #include #include #include #include #include #ifdef BOOST_WINDOWS #include #endif namespace boost { namespace nowide { #if !defined(BOOST_WINDOWS) && !defined(BOOST_NOWIDE_DOXYGEN) using ::getenv; using ::setenv; using ::unsetenv; using ::putenv; #else /// /// \brief UTF-8 aware getenv. Returns 0 if the variable is not set. /// /// This function is not thread safe or reenterable as defined by the standard library /// inline char *getenv(char const *key) { static stackstring value; wshort_stackstring name; if(!name.convert(key)) return 0; static const size_t buf_size = 64; wchar_t buf[buf_size]; std::vector tmp; wchar_t *ptr = buf; size_t n = GetEnvironmentVariableW(name.c_str(),buf,buf_size); if(n == 0 && GetLastError() == 203) // ERROR_ENVVAR_NOT_FOUND return 0; if(n >= buf_size) { tmp.resize(n+1,L'\0'); n = GetEnvironmentVariableW(name.c_str(),&tmp[0],static_cast(tmp.size() - 1)); // The size may have changed if(n >= tmp.size() - 1) return 0; ptr = &tmp[0]; } if(!value.convert(ptr)) return 0; return value.c_str(); } /// /// \brief UTF-8 aware setenv, \a key - the variable name, \a value is a new UTF-8 value, /// /// if override is not 0, that the old value is always overridded, otherwise, /// if the variable exists it remains unchanged /// inline int setenv(char const *key,char const *value,int override) { wshort_stackstring name; if(!name.convert(key)) return -1; if(!override) { wchar_t unused[2]; if(!(GetEnvironmentVariableW(name.c_str(),unused,2)==0 && GetLastError() == 203)) // ERROR_ENVVAR_NOT_FOUND return 0; } wstackstring wval; if(!wval.convert(value)) return -1; if(SetEnvironmentVariableW(name.c_str(),wval.c_str())) return 0; return -1; } /// /// \brief Remove enviroment variable \a key /// inline int unsetenv(char const *key) { wshort_stackstring name; if(!name.convert(key)) return -1; if(SetEnvironmentVariableW(name.c_str(),0)) return 0; return -1; } /// /// \brief UTF-8 aware putenv implementation, expects string in format KEY=VALUE /// inline int putenv(char *string) { char const *key = string; char const *key_end = string; while(*key_end != '=' && *key_end != 0) ++ key_end; if(*key_end == 0) return -1; wshort_stackstring wkey; if(!wkey.convert(key,key_end)) return -1; wstackstring wvalue; if(!wvalue.convert(key_end+1)) return -1; if(SetEnvironmentVariableW(wkey.c_str(),wvalue.c_str())) return 0; return -1; } #endif } // nowide } // namespace boost #endif /// // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/boost/nowide/config.hpp000066400000000000000000000027451324354444700223450ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_CONFIG_HPP_INCLUDED #define BOOST_NOWIDE_CONFIG_HPP_INCLUDED #include #ifndef BOOST_SYMBOL_VISIBLE # define BOOST_SYMBOL_VISIBLE #endif #ifdef BOOST_HAS_DECLSPEC # if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_NOWIDE_DYN_LINK) # ifdef BOOST_NOWIDE_SOURCE # define BOOST_NOWIDE_DECL BOOST_SYMBOL_EXPORT # else # define BOOST_NOWIDE_DECL BOOST_SYMBOL_IMPORT # endif // BOOST_NOWIDE_SOURCE # endif // DYN_LINK #endif // BOOST_HAS_DECLSPEC #ifndef BOOST_NOWIDE_DECL # define BOOST_NOWIDE_DECL #endif // // Automatically link to the correct build variant where possible. // #if !defined(BOOST_ALL_NO_LIB) && !defined(BOOST_NOWIDE_NO_LIB) && !defined(BOOST_NOWIDE_SOURCE) // // Set the name of our library, this will get undef'ed by auto_link.hpp // once it's done with it: // #define BOOST_LIB_NAME boost_nowide // // If we're importing code from a dll, then tell auto_link.hpp about it: // #if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_NOWIDE_DYN_LINK) # define BOOST_DYN_LINK #endif // // And include the header that does the work: // #include #endif // auto-linking disabled #endif // boost/nowide/config.hpp // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4Slic3r-version_1.39.1/xs/src/boost/nowide/convert.hpp000066400000000000000000000122031324354444700225460ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_CONVERT_H_INCLUDED #define BOOST_NOWIDE_CONVERT_H_INCLUDED #include #include namespace boost { namespace nowide { /// /// \brief Template function that converts a buffer of UTF sequences in range [source_begin,source_end) /// to the output \a buffer of size \a buffer_size. /// /// In case of success a NULL terminated string is returned (buffer), otherwise 0 is returned. /// /// If there is not enough room in the buffer or the source sequence contains invalid UTF, /// 0 is returned, and the contents of the buffer are undefined. /// template CharOut *basic_convert(CharOut *buffer,size_t buffer_size,CharIn const *source_begin,CharIn const *source_end) { CharOut *rv = buffer; if(buffer_size == 0) return 0; buffer_size --; while(source_begin!=source_end) { using namespace boost::locale::utf; code_point c = utf_traits::template decode(source_begin,source_end); if(c==illegal || c==incomplete) { rv = 0; break; } size_t width = utf_traits::width(c); if(buffer_size < width) { rv=0; break; } buffer = utf_traits::template encode(c,buffer); buffer_size -= width; } *buffer++ = 0; return rv; } /// \cond INTERNAL namespace details { // // wcslen defined only in C99... So we will not use it // template Char const *basic_strend(Char const *s) { while(*s) s++; return s; } } /// \endcond /// /// Convert NULL terminated UTF source string to NULL terminated \a output string of size at /// most output_size (including NULL) /// /// In case of success output is returned, if the input sequence is illegal, /// or there is not enough room NULL is returned /// inline char *narrow(char *output,size_t output_size,wchar_t const *source) { return basic_convert(output,output_size,source,details::basic_strend(source)); } /// /// Convert UTF text in range [begin,end) to NULL terminated \a output string of size at /// most output_size (including NULL) /// /// In case of success output is returned, if the input sequence is illegal, /// or there is not enough room NULL is returned /// inline char *narrow(char *output,size_t output_size,wchar_t const *begin,wchar_t const *end) { return basic_convert(output,output_size,begin,end); } /// /// Convert NULL terminated UTF source string to NULL terminated \a output string of size at /// most output_size (including NULL) /// /// In case of success output is returned, if the input sequence is illegal, /// or there is not enough room NULL is returned /// inline wchar_t *widen(wchar_t *output,size_t output_size,char const *source) { return basic_convert(output,output_size,source,details::basic_strend(source)); } /// /// Convert UTF text in range [begin,end) to NULL terminated \a output string of size at /// most output_size (including NULL) /// /// In case of success output is returned, if the input sequence is illegal, /// or there is not enough room NULL is returned /// inline wchar_t *widen(wchar_t *output,size_t output_size,char const *begin,char const *end) { return basic_convert(output,output_size,begin,end); } /// /// Convert between Wide - UTF-16/32 string and UTF-8 string. /// /// boost::locale::conv::conversion_error is thrown in a case of a error /// inline std::string narrow(wchar_t const *s) { return boost::locale::conv::utf_to_utf(s); } /// /// Convert between UTF-8 and UTF-16 string, implemented only on Windows platform /// /// boost::locale::conv::conversion_error is thrown in a case of a error /// inline std::wstring widen(char const *s) { return boost::locale::conv::utf_to_utf(s); } /// /// Convert between Wide - UTF-16/32 string and UTF-8 string /// /// boost::locale::conv::conversion_error is thrown in a case of a error /// inline std::string narrow(std::wstring const &s) { return boost::locale::conv::utf_to_utf(s); } /// /// Convert between UTF-8 and UTF-16 string, implemented only on Windows platform /// /// boost::locale::conv::conversion_error is thrown in a case of a error /// inline std::wstring widen(std::string const &s) { return boost::locale::conv::utf_to_utf(s); } } // nowide } // namespace boost #endif /// // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/boost/nowide/cstdio.hpp000066400000000000000000000046621324354444700223650ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_CSTDIO_H_INCLUDED #define BOOST_NOWIDE_CSTDIO_H_INCLUDED #include #include #include #include #include #include #ifdef BOOST_MSVC # pragma warning(push) # pragma warning(disable : 4996) #endif namespace boost { namespace nowide { #if !defined(BOOST_WINDOWS) && !defined(BOOST_NOWIDE_DOXYGEN) using std::fopen; using std::freopen; using std::remove; using std::rename; #else /// /// \brief Same as freopen but file_name and mode are UTF-8 strings /// /// If invalid UTF-8 given, NULL is returned and errno is set to EINVAL /// inline FILE *freopen(char const *file_name,char const *mode,FILE *stream) { wstackstring wname; wshort_stackstring wmode; if(!wname.convert(file_name) || !wmode.convert(mode)) { errno = EINVAL; return 0; } return _wfreopen(wname.c_str(),wmode.c_str(),stream); } /// /// \brief Same as fopen but file_name and mode are UTF-8 strings /// /// If invalid UTF-8 given, NULL is returned and errno is set to EINVAL /// inline FILE *fopen(char const *file_name,char const *mode) { wstackstring wname; wshort_stackstring wmode; if(!wname.convert(file_name) || !wmode.convert(mode)) { errno = EINVAL; return 0; } return _wfopen(wname.c_str(),wmode.c_str()); } /// /// \brief Same as rename but old_name and new_name are UTF-8 strings /// /// If invalid UTF-8 given, -1 is returned and errno is set to EINVAL /// inline int rename(char const *old_name,char const *new_name) { wstackstring wold,wnew; if(!wold.convert(old_name) || !wnew.convert(new_name)) { errno = EINVAL; return -1; } return _wrename(wold.c_str(),wnew.c_str()); } /// /// \brief Same as rename but name is UTF-8 string /// /// If invalid UTF-8 given, -1 is returned and errno is set to EINVAL /// inline int remove(char const *name) { wstackstring wname; if(!wname.convert(name)) { errno = EINVAL; return -1; } return _wremove(wname.c_str()); } #endif } // nowide } // namespace boost #ifdef BOOST_MSVC #pragma warning(pop) #endif #endif /// // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/boost/nowide/cstdlib.hpp000066400000000000000000000006631324354444700225210ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_CSTDLIB_HPP_INCLUDED #define BOOST_NOWIDE_CSTDLIB_HPP_INCLUDED #include #include #endif /// // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/boost/nowide/filebuf.hpp000066400000000000000000000274211324354444700225120ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_FILEBUF_HPP #define BOOST_NOWIDE_FILEBUF_HPP #include #include #include #include #include #include #ifdef BOOST_MSVC # pragma warning(push) # pragma warning(disable : 4996 4244 4800) #endif namespace boost { namespace nowide { #if !defined(BOOST_WINDOWS) && !defined(BOOST_NOWIDE_FSTREAM_TESTS) && !defined(BOOST_NOWIDE_DOXYGEN) using std::basic_filebuf; using std::filebuf; #else // Windows /// /// \brief This forward declaration defined the basic_filebuf type. /// /// it is implemented and specialized for CharType = char, it behaves /// implements std::filebuf over standard C I/O /// template > class basic_filebuf; /// /// \brief This is implementation of std::filebuf /// /// it is implemented and specialized for CharType = char, it behaves /// implements std::filebuf over standard C I/O /// template<> class basic_filebuf : public std::basic_streambuf { public: /// /// Creates new filebuf /// basic_filebuf() : buffer_size_(4), buffer_(0), file_(0), own_(true), mode_(std::ios::in | std::ios::out) { setg(0,0,0); setp(0,0); } virtual ~basic_filebuf() { if(file_) { ::fclose(file_); file_ = 0; } if(own_ && buffer_) delete [] buffer_; } /// /// Same as std::filebuf::open but s is UTF-8 string /// basic_filebuf *open(std::string const &s,std::ios_base::openmode mode) { return open(s.c_str(),mode); } /// /// Same as std::filebuf::open but s is UTF-8 string /// basic_filebuf *open(char const *s,std::ios_base::openmode mode) { if(file_) { sync(); ::fclose(file_); file_ = 0; } bool ate = bool(mode & std::ios_base::ate); if(ate) mode = mode ^ std::ios_base::ate; wchar_t const *smode = get_mode(mode); if(!smode) return 0; wstackstring name; if(!name.convert(s)) return 0; #ifdef BOOST_NOWIDE_FSTREAM_TESTS FILE *f = ::fopen(s,boost::nowide::convert(smode).c_str()); #else FILE *f = ::_wfopen(name.c_str(),smode); #endif if(!f) return 0; if(ate && fseek(f,0,SEEK_END)!=0) { fclose(f); return 0; } file_ = f; return this; } /// /// Same as std::filebuf::close() /// basic_filebuf *close() { bool res = sync() == 0; if(file_) { if(::fclose(file_)!=0) res = false; file_ = 0; } return res ? this : 0; } /// /// Same as std::filebuf::is_open() /// bool is_open() const { return file_ != 0; } private: void make_buffer() { if(buffer_) return; if(buffer_size_ > 0) { buffer_ = new char [buffer_size_]; own_ = true; } } protected: virtual std::streambuf *setbuf(char *s,std::streamsize n) { if(!buffer_ && n>=0) { buffer_ = s; buffer_size_ = n; own_ = false; } return this; } #ifdef BOOST_NOWIDE_DEBUG_FILEBUF void print_buf(char *b,char *p,char *e) { std::cerr << "-- Is Null: " << (b==0) << std::endl;; if(b==0) return; if(e != 0) std::cerr << "-- Total: " << e - b <<" offset from start " << p - b << std::endl; else std::cerr << "-- Total: " << p - b << std::endl; std::cerr << "-- ["; for(char *ptr = b;ptrprint_state(); } ~print_guard() { std::cerr << "Out: " << f << std::endl; self->print_state(); } basic_filebuf *self; char const *f; }; #else #endif int overflow(int c) { #ifdef BOOST_NOWIDE_DEBUG_FILEBUF print_guard g(this,__FUNCTION__); #endif if(!file_) return EOF; if(fixg() < 0) return EOF; size_t n = pptr() - pbase(); if(n > 0) { if(::fwrite(pbase(),1,n,file_) < n) return -1; fflush(file_); } if(buffer_size_ > 0) { make_buffer(); setp(buffer_,buffer_+buffer_size_); if(c!=EOF) sputc(c); } else if(c!=EOF) { if(::fputc(c,file_)==EOF) return EOF; fflush(file_); } return 0; } int sync() { return overflow(EOF); } int underflow() { #ifdef BOOST_NOWIDE_DEBUG_FILEBUF print_guard g(this,__FUNCTION__); #endif if(!file_) return EOF; if(fixp() < 0) return EOF; if(buffer_size_ == 0) { int c = ::fgetc(file_); if(c==EOF) { return EOF; } last_char_ = c; setg(&last_char_,&last_char_,&last_char_ + 1); return c; } make_buffer(); size_t n = ::fread(buffer_,1,buffer_size_,file_); setg(buffer_,buffer_,buffer_+n); if(n == 0) return EOF; return std::char_traits::to_int_type(*gptr()); } int pbackfail(int) { return pubseekoff(-1,std::ios::cur); } std::streampos seekoff(std::streamoff off, std::ios_base::seekdir seekdir, std::ios_base::openmode /*m*/) { #ifdef BOOST_NOWIDE_DEBUG_FILEBUF print_guard g(this,__FUNCTION__); #endif if(!file_) return EOF; if(fixp() < 0 || fixg() < 0) return EOF; if(seekdir == std::ios_base::cur) { if( ::fseek(file_,off,SEEK_CUR) < 0) return EOF; } else if(seekdir == std::ios_base::beg) { if( ::fseek(file_,off,SEEK_SET) < 0) return EOF; } else if(seekdir == std::ios_base::end) { if( ::fseek(file_,off,SEEK_END) < 0) return EOF; } else return -1; return ftell(file_); } std::streampos seekpos(std::streampos off,std::ios_base::openmode m) { return seekoff(std::streamoff(off),std::ios_base::beg,m); } private: int fixg() { if(gptr()!=egptr()) { std::streamsize off = gptr() - egptr(); setg(0,0,0); if(fseek(file_,off,SEEK_CUR) != 0) return -1; } setg(0,0,0); return 0; } int fixp() { if(pptr()!=0) { int r = sync(); setp(0,0); return r; } return 0; } void reset(FILE *f = 0) { sync(); if(file_) { fclose(file_); file_ = 0; } file_ = f; } static wchar_t const *get_mode(std::ios_base::openmode mode) { // // done according to n2914 table 106 27.9.1.4 // // note can't use switch case as overload operator can't be used // in constant expression if(mode == (std::ios_base::out)) return L"w"; if(mode == (std::ios_base::out | std::ios_base::app)) return L"a"; if(mode == (std::ios_base::app)) return L"a"; if(mode == (std::ios_base::out | std::ios_base::trunc)) return L"w"; if(mode == (std::ios_base::in)) return L"r"; if(mode == (std::ios_base::in | std::ios_base::out)) return L"r+"; if(mode == (std::ios_base::in | std::ios_base::out | std::ios_base::trunc)) return L"w+"; if(mode == (std::ios_base::in | std::ios_base::out | std::ios_base::app)) return L"a+"; if(mode == (std::ios_base::in | std::ios_base::app)) return L"a+"; if(mode == (std::ios_base::binary | std::ios_base::out)) return L"wb"; if(mode == (std::ios_base::binary | std::ios_base::out | std::ios_base::app)) return L"ab"; if(mode == (std::ios_base::binary | std::ios_base::app)) return L"ab"; if(mode == (std::ios_base::binary | std::ios_base::out | std::ios_base::trunc)) return L"wb"; if(mode == (std::ios_base::binary | std::ios_base::in)) return L"rb"; if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out)) return L"r+b"; if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out | std::ios_base::trunc)) return L"w+b"; if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::out | std::ios_base::app)) return L"a+b"; if(mode == (std::ios_base::binary | std::ios_base::in | std::ios_base::app)) return L"a+b"; return 0; } size_t buffer_size_; char *buffer_; FILE *file_; bool own_; char last_char_; std::ios::openmode mode_; }; /// /// \brief Convinience typedef /// typedef basic_filebuf filebuf; #endif // windows } // nowide } // namespace boost #ifdef BOOST_MSVC # pragma warning(pop) #endif #endif // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/boost/nowide/fstream.hpp000066400000000000000000000204331324354444700225330ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_FSTREAM_INCLUDED_HPP #define BOOST_NOWIDE_FSTREAM_INCLUDED_HPP //#include #include #include #include #include #include #include namespace boost { /// /// \brief This namespace includes implementation of the standard library functios /// such that they accept UTF-8 strings on Windows. On other platforms it is just an alias /// of std namespace (i.e. not on Windows) /// namespace nowide { #if !defined(BOOST_WINDOWS) && !defined(BOOST_NOWIDE_FSTREAM_TESTS) && !defined(BOOST_NOWIDE_DOXYGEN) using std::basic_ifstream; using std::basic_ofstream; using std::basic_fstream; using std::ifstream; using std::ofstream; using std::fstream; #else /// /// \brief Same as std::basic_ifstream but accepts UTF-8 strings under Windows /// template > class basic_ifstream : public std::basic_istream { public: typedef basic_filebuf internal_buffer_type; typedef std::basic_istream internal_stream_type; basic_ifstream() : internal_stream_type(0) { buf_.reset(new internal_buffer_type()); std::ios::rdbuf(buf_.get()); } explicit basic_ifstream(char const *file_name,std::ios_base::openmode mode = std::ios_base::in) : internal_stream_type(0) { buf_.reset(new internal_buffer_type()); std::ios::rdbuf(buf_.get()); open(file_name,mode); } explicit basic_ifstream(std::string const &file_name,std::ios_base::openmode mode = std::ios_base::in) : internal_stream_type(0) { buf_.reset(new internal_buffer_type()); std::ios::rdbuf(buf_.get()); open(file_name,mode); } void open(std::string const &file_name,std::ios_base::openmode mode = std::ios_base::in) { open(file_name.c_str(),mode); } void open(char const *file_name,std::ios_base::openmode mode = std::ios_base::in) { if(!buf_->open(file_name,mode | std::ios_base::in)) { this->setstate(std::ios_base::failbit); } else { this->clear(); } } bool is_open() { return buf_->is_open(); } bool is_open() const { return buf_->is_open(); } void close() { if(!buf_->close()) this->setstate(std::ios_base::failbit); else this->clear(); } internal_buffer_type *rdbuf() const { return buf_.get(); } ~basic_ifstream() { buf_->close(); } private: boost::scoped_ptr buf_; }; /// /// \brief Same as std::basic_ofstream but accepts UTF-8 strings under Windows /// template > class basic_ofstream : public std::basic_ostream { public: typedef basic_filebuf internal_buffer_type; typedef std::basic_ostream internal_stream_type; basic_ofstream() : internal_stream_type(0) { buf_.reset(new internal_buffer_type()); std::ios::rdbuf(buf_.get()); } explicit basic_ofstream(char const *file_name,std::ios_base::openmode mode = std::ios_base::out) : internal_stream_type(0) { buf_.reset(new internal_buffer_type()); std::ios::rdbuf(buf_.get()); open(file_name,mode); } explicit basic_ofstream(std::string const &file_name,std::ios_base::openmode mode = std::ios_base::out) : internal_stream_type(0) { buf_.reset(new internal_buffer_type()); std::ios::rdbuf(buf_.get()); open(file_name,mode); } void open(std::string const &file_name,std::ios_base::openmode mode = std::ios_base::out) { open(file_name.c_str(),mode); } void open(char const *file_name,std::ios_base::openmode mode = std::ios_base::out) { if(!buf_->open(file_name,mode | std::ios_base::out)) { this->setstate(std::ios_base::failbit); } else { this->clear(); } } bool is_open() { return buf_->is_open(); } bool is_open() const { return buf_->is_open(); } void close() { if(!buf_->close()) this->setstate(std::ios_base::failbit); else this->clear(); } internal_buffer_type *rdbuf() const { return buf_.get(); } ~basic_ofstream() { buf_->close(); } private: boost::scoped_ptr buf_; }; /// /// \brief Same as std::basic_fstream but accepts UTF-8 strings under Windows /// template > class basic_fstream : public std::basic_iostream { public: typedef basic_filebuf internal_buffer_type; typedef std::basic_iostream internal_stream_type; basic_fstream() : internal_stream_type(0) { buf_.reset(new internal_buffer_type()); std::ios::rdbuf(buf_.get()); } explicit basic_fstream(char const *file_name,std::ios_base::openmode mode = std::ios_base::out | std::ios_base::in) : internal_stream_type(0) { buf_.reset(new internal_buffer_type()); std::ios::rdbuf(buf_.get()); open(file_name,mode); } explicit basic_fstream(std::string const &file_name,std::ios_base::openmode mode = std::ios_base::out | std::ios_base::in) : internal_stream_type(0) { buf_.reset(new internal_buffer_type()); std::ios::rdbuf(buf_.get()); open(file_name,mode); } void open(std::string const &file_name,std::ios_base::openmode mode = std::ios_base::out | std::ios_base::out) { open(file_name.c_str(),mode); } void open(char const *file_name,std::ios_base::openmode mode = std::ios_base::out | std::ios_base::out) { if(!buf_->open(file_name,mode)) { this->setstate(std::ios_base::failbit); } else { this->clear(); } } bool is_open() { return buf_->is_open(); } bool is_open() const { return buf_->is_open(); } void close() { if(!buf_->close()) this->setstate(std::ios_base::failbit); else this->clear(); } internal_buffer_type *rdbuf() const { return buf_.get(); } ~basic_fstream() { buf_->close(); } private: boost::scoped_ptr buf_; }; /// /// \brief Same as std::filebuf but accepts UTF-8 strings under Windows /// typedef basic_filebuf filebuf; /// /// Same as std::ifstream but accepts UTF-8 strings under Windows /// typedef basic_ifstream ifstream; /// /// Same as std::ofstream but accepts UTF-8 strings under Windows /// typedef basic_ofstream ofstream; /// /// Same as std::fstream but accepts UTF-8 strings under Windows /// typedef basic_fstream fstream; #endif } // nowide } // namespace boost #endif // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/boost/nowide/integration/000077500000000000000000000000001324354444700227025ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/boost/nowide/integration/filesystem.hpp000066400000000000000000000016041324354444700256000ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_INTEGRATION_FILESYSTEM_HPP_INCLUDED #define BOOST_NOWIDE_INTEGRATION_FILESYSTEM_HPP_INCLUDED #include #include namespace boost { namespace nowide { /// /// Instal utf8_codecvt facet into boost::filesystem::path such all char strings are interpreted as utf-8 strings /// inline void nowide_filesystem() { std::locale tmp = std::locale(std::locale(),new boost::nowide::utf8_codecvt()); boost::filesystem::path::imbue(tmp); } } // nowide } // boost #endif /// // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/boost/nowide/iostream.cpp000066400000000000000000000156651324354444700227230ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #define BOOST_NOWIDE_SOURCE #include #include #include #include #ifdef BOOST_WINDOWS #ifndef NOMINMAX # define NOMINMAX #endif #include namespace boost { namespace nowide { namespace details { class console_output_buffer : public std::streambuf { public: console_output_buffer(HANDLE h) : handle_(h), isatty_(false) { if(handle_) { DWORD dummy; isatty_ = GetConsoleMode(handle_,&dummy) == TRUE; } } protected: int sync() { return overflow(EOF); } int overflow(int c) { if(!handle_) return -1; int n = pptr() - pbase(); int r = 0; if(n > 0 && (r=write(pbase(),n)) < 0) return -1; if(r < n) { memmove(pbase(),pbase() + r,n-r); } setp(buffer_, buffer_ + buffer_size); pbump(n-r); if(c!=EOF) sputc(c); return 0; } private: int write(char const *p,int n) { namespace uf = boost::locale::utf; char const *b = p; char const *e = p+n; DWORD size=0; if(!isatty_) { if(!WriteFile(handle_,p,n,&size,0) || static_cast(size) != n) return -1; return n; } if(n > buffer_size) return -1; wchar_t *out = wbuffer_; uf::code_point c; size_t decoded = 0; while(p < e && (c = uf::utf_traits::decode(p,e))!=uf::illegal && c!=uf::incomplete) { out = uf::utf_traits::encode(c,out); decoded = p-b; } if(c==uf::illegal) return -1; if(!WriteConsoleW(handle_,wbuffer_,out - wbuffer_,&size,0)) return -1; return decoded; } static const int buffer_size = 1024; char buffer_[buffer_size]; wchar_t wbuffer_[buffer_size]; // for null HANDLE handle_; bool isatty_; }; class console_input_buffer: public std::streambuf { public: console_input_buffer(HANDLE h) : handle_(h), isatty_(false), wsize_(0) { if(handle_) { DWORD dummy; isatty_ = GetConsoleMode(handle_,&dummy) == TRUE; } } protected: int pbackfail(int c) { if(c==EOF) return EOF; if(gptr()!=eback()) { gbump(-1); *gptr() = c; return 0; } if(pback_buffer_.empty()) { pback_buffer_.resize(4); char *b = &pback_buffer_[0]; char *e = b + pback_buffer_.size(); setg(b,e-1,e); *gptr() = c; } else { size_t n = pback_buffer_.size(); std::vector tmp; tmp.resize(n*2); memcpy(&tmp[n],&pback_buffer_[0],n); tmp.swap(pback_buffer_); char *b = &pback_buffer_[0]; char *e = b + n * 2; char *p = b+n-1; *p = c; setg(b,p,e); } return 0; } int underflow() { if(!handle_) return -1; if(!pback_buffer_.empty()) pback_buffer_.clear(); size_t n = read(); setg(buffer_,buffer_,buffer_+n); if(n == 0) return EOF; return std::char_traits::to_int_type(*gptr()); } private: size_t read() { namespace uf = boost::locale::utf; if(!isatty_) { DWORD read_bytes = 0; if(!ReadFile(handle_,buffer_,buffer_size,&read_bytes,0)) return 0; return read_bytes; } DWORD read_wchars = 0; size_t n = wbuffer_size - wsize_; if(!ReadConsoleW(handle_,wbuffer_,n,&read_wchars,0)) return 0; wsize_ += read_wchars; char *out = buffer_; wchar_t *b = wbuffer_; wchar_t *e = b + wsize_; wchar_t *p = b; uf::code_point c; wsize_ = e-p; while(p < e && (c = uf::utf_traits::decode(p,e))!=uf::illegal && c!=uf::incomplete) { out = uf::utf_traits::encode(c,out); wsize_ = e-p; } if(c==uf::illegal) return -1; if(c==uf::incomplete) { memmove(b,e-wsize_,sizeof(wchar_t)*wsize_); } return out - buffer_; } static const size_t buffer_size = 1024 * 3; static const size_t wbuffer_size = 1024; char buffer_[buffer_size]; wchar_t wbuffer_[buffer_size]; // for null HANDLE handle_; bool isatty_; int wsize_; std::vector pback_buffer_; }; winconsole_ostream::winconsole_ostream(int fd) : std::ostream(0) { HANDLE h = 0; switch(fd) { case 1: h = GetStdHandle(STD_OUTPUT_HANDLE); break; case 2: h = GetStdHandle(STD_ERROR_HANDLE); break; } d.reset(new console_output_buffer(h)); std::ostream::rdbuf(d.get()); } winconsole_ostream::~winconsole_ostream() { } winconsole_istream::winconsole_istream() : std::istream(0) { HANDLE h = GetStdHandle(STD_INPUT_HANDLE); d.reset(new console_input_buffer(h)); std::istream::rdbuf(d.get()); } winconsole_istream::~winconsole_istream() { } } // details BOOST_NOWIDE_DECL details::winconsole_istream cin; BOOST_NOWIDE_DECL details::winconsole_ostream cout(1); BOOST_NOWIDE_DECL details::winconsole_ostream cerr(2); BOOST_NOWIDE_DECL details::winconsole_ostream clog(2); namespace { struct initialize { initialize() { boost::nowide::cin.tie(&boost::nowide::cout); boost::nowide::cerr.tie(&boost::nowide::cout); boost::nowide::clog.tie(&boost::nowide::cout); } } inst; } } // nowide } // namespace boost #endif /// // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/boost/nowide/iostream.hpp000066400000000000000000000053161324354444700227200ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_IOSTREAM_HPP_INCLUDED #define BOOST_NOWIDE_IOSTREAM_HPP_INCLUDED #include #include #include #include #include #ifdef BOOST_MSVC # pragma warning(push) # pragma warning(disable : 4251) #endif namespace boost { namespace nowide { #if !defined(BOOST_WINDOWS) && !defined(BOOST_NOWIDE_DOXYGEN) using std::cout; using std::cerr; using std::cin; using std::clog; #else /// \cond INTERNAL namespace details { class console_output_buffer; class console_input_buffer; class BOOST_NOWIDE_DECL winconsole_ostream : public std::ostream { winconsole_ostream(winconsole_ostream const &); void operator=(winconsole_ostream const &); public: winconsole_ostream(int fd); ~winconsole_ostream(); private: boost::scoped_ptr d; }; class BOOST_NOWIDE_DECL winconsole_istream : public std::istream { winconsole_istream(winconsole_istream const &); void operator=(winconsole_istream const &); public: winconsole_istream(); ~winconsole_istream(); private: struct data; boost::scoped_ptr d; }; } // details /// \endcond /// /// \brief Same as std::cin, but uses UTF-8 /// /// Note, the stream is not synchronized with stdio and not affected by std::ios::sync_with_stdio /// extern BOOST_NOWIDE_DECL details::winconsole_istream cin; /// /// \brief Same as std::cout, but uses UTF-8 /// /// Note, the stream is not synchronized with stdio and not affected by std::ios::sync_with_stdio /// extern BOOST_NOWIDE_DECL details::winconsole_ostream cout; /// /// \brief Same as std::cerr, but uses UTF-8 /// /// Note, the stream is not synchronized with stdio and not affected by std::ios::sync_with_stdio /// extern BOOST_NOWIDE_DECL details::winconsole_ostream cerr; /// /// \brief Same as std::clog, but uses UTF-8 /// /// Note, the stream is not synchronized with stdio and not affected by std::ios::sync_with_stdio /// extern BOOST_NOWIDE_DECL details::winconsole_ostream clog; #endif } // nowide } // namespace boost #ifdef BOOST_MSVC # pragma warning(pop) #endif #endif /// // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/boost/nowide/stackstring.hpp000066400000000000000000000076001324354444700234270ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_DETAILS_WIDESTR_H_INCLUDED #define BOOST_NOWIDE_DETAILS_WIDESTR_H_INCLUDED #include #include #include namespace boost { namespace nowide { /// /// \brief A class that allows to create a temporary wide or narrow UTF strings from /// wide or narrow UTF source. /// /// It uses on stack buffer of the string is short enough /// and allocated a buffer on the heap if the size of the buffer is too small /// template class basic_stackstring { public: static const size_t buffer_size = BufferSize; typedef CharOut output_char; typedef CharIn input_char; basic_stackstring(basic_stackstring const &other) : mem_buffer_(0) { clear(); if(other.mem_buffer_) { size_t len = 0; while(other.mem_buffer_[len]) len ++; mem_buffer_ = new output_char[len + 1]; memcpy(mem_buffer_,other.mem_buffer_,sizeof(output_char) * (len+1)); } else { memcpy(buffer_,other.buffer_,buffer_size * sizeof(output_char)); } } void swap(basic_stackstring &other) { std::swap(mem_buffer_,other.mem_buffer_); for(size_t i=0;i wstackstring; /// /// Convinience typedef /// typedef basic_stackstring stackstring; /// /// Convinience typedef /// typedef basic_stackstring wshort_stackstring; /// /// Convinience typedef /// typedef basic_stackstring short_stackstring; } // nowide } // namespace boost #endif /// // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/boost/nowide/system.hpp000066400000000000000000000016641324354444700224230ustar00rootroot00000000000000// // Copyright (c) 2012 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_CSTDLIB_HPP #define BOOST_NOWIDE_CSTDLIB_HPP #include #include #include namespace boost { namespace nowide { #if !defined(BOOST_WINDOWS) && !defined(BOOST_NOWIDE_DOXYGEN) using ::system; #else // Windows /// /// Same as std::system but cmd is UTF-8. /// /// If the input is not valid UTF-8, -1 returned and errno set to EINVAL /// inline int system(char const *cmd) { if(!cmd) return _wsystem(0); wstackstring wcmd; if(!wcmd.convert(cmd)) { errno = EINVAL; return -1; } return _wsystem(wcmd.c_str()); } #endif } // nowide } // namespace boost #endif /// // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/boost/nowide/utf8_codecvt.hpp000066400000000000000000000413271324354444700234740ustar00rootroot00000000000000// // Copyright (c) 2015 Artyom Beilis (Tonkikh) // // Distributed under the Boost Software License, Version 1.0. (See // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // #ifndef BOOST_NOWIDE_UTF8_CODECVT_HPP #define BOOST_NOWIDE_UTF8_CODECVT_HPP #include #include #include #include namespace boost { namespace nowide { // // Make sure that mbstate can keep 16 bit of UTF-16 sequence // BOOST_STATIC_ASSERT(sizeof(std::mbstate_t)>=2); #ifdef _MSC_VER // MSVC do_length is non-standard it counts wide characters instead of narrow and does not change mbstate #define BOOST_NOWIDE_DO_LENGTH_MBSTATE_CONST #endif template class utf8_codecvt; template class utf8_codecvt : public std::codecvt { public: utf8_codecvt(size_t refs = 0) : std::codecvt(refs) { } protected: typedef CharType uchar; virtual std::codecvt_base::result do_unshift(std::mbstate_t &s,char *from,char * /*to*/,char *&next) const { boost::uint16_t &state = *reinterpret_cast(&s); #ifdef DEBUG_CODECVT std::cout << "Entering unshift " << std::hex << state << std::dec << std::endl; #endif if(state != 0) return std::codecvt_base::error; next=from; return std::codecvt_base::ok; } virtual int do_encoding() const throw() { return 0; } virtual int do_max_length() const throw() { return 4; } virtual bool do_always_noconv() const throw() { return false; } virtual int do_length( std::mbstate_t #ifdef BOOST_NOWIDE_DO_LENGTH_MBSTATE_CONST const #endif &std_state, char const *from, char const *from_end, size_t max) const { #ifndef BOOST_NOWIDE_DO_LENGTH_MBSTATE_CONST char const *save_from = from; boost::uint16_t &state = *reinterpret_cast(&std_state); #else size_t save_max = max; boost::uint16_t state = *reinterpret_cast(&std_state); #endif while(max > 0 && from < from_end){ char const *prev_from = from; boost::uint32_t ch=boost::locale::utf::utf_traits::decode(from,from_end); if(ch==boost::locale::utf::incomplete || ch==boost::locale::utf::illegal) { from = prev_from; break; } max --; if(ch > 0xFFFF) { if(state == 0) { from = prev_from; state = 1; } else { state = 0; } } } #ifndef BOOST_NOWIDE_DO_LENGTH_MBSTATE_CONST return from - save_from; #else return save_max - max; #endif } virtual std::codecvt_base::result do_in( std::mbstate_t &std_state, char const *from, char const *from_end, char const *&from_next, uchar *to, uchar *to_end, uchar *&to_next) const { std::codecvt_base::result r=std::codecvt_base::ok; // mbstate_t is POD type and should be initialized to 0 (i.a. state = stateT()) // according to standard. We use it to keep a flag 0/1 for surrogate pair writing // // if 0 no code above >0xFFFF observed, of 1 a code above 0xFFFF observerd // and first pair is written, but no input consumed boost::uint16_t &state = *reinterpret_cast(&std_state); while(to < to_end && from < from_end) { #ifdef DEBUG_CODECVT std::cout << "Entering IN--------------" << std::endl; std::cout << "State " << std::hex << state <::decode(from,from_end); if(ch==boost::locale::utf::illegal) { from = from_saved; r=std::codecvt_base::error; break; } if(ch==boost::locale::utf::incomplete) { from = from_saved; r=std::codecvt_base::partial; break; } // Normal codepoints go direcly to stream if(ch <= 0xFFFF) { *to++=ch; } else { // for other codepoints we do following // // 1. We can't consume our input as we may find ourselfs // in state where all input consumed but not all output written,i.e. only // 1st pair is written // 2. We only write first pair and mark this in the state, we also revert back // the from pointer in order to make sure this codepoint would be read // once again and then we would consume our input together with writing // second surrogate pair ch-=0x10000; boost::uint16_t vh = ch >> 10; boost::uint16_t vl = ch & 0x3FF; boost::uint16_t w1 = vh + 0xD800; boost::uint16_t w2 = vl + 0xDC00; if(state == 0) { from = from_saved; *to++ = w1; state = 1; } else { *to++ = w2; state = 0; } } } from_next=from; to_next=to; if(r == std::codecvt_base::ok && (from!=from_end || state!=0)) r = std::codecvt_base::partial; #ifdef DEBUG_CODECVT std::cout << "Returning "; switch(r) { case std::codecvt_base::ok: std::cout << "ok" << std::endl; break; case std::codecvt_base::partial: std::cout << "partial" << std::endl; break; case std::codecvt_base::error: std::cout << "error" << std::endl; break; default: std::cout << "other" << std::endl; break; } std::cout << "State " << std::hex << state <=2 in order // to be able to store first observerd surrogate pair // // State: state!=0 - a first surrogate pair was observerd (state = first pair), // we expect the second one to come and then zero the state /// boost::uint16_t &state = *reinterpret_cast(&std_state); while(to < to_end && from < from_end) { #ifdef DEBUG_CODECVT std::cout << "Entering OUT --------------" << std::endl; std::cout << "State " << std::hex << state <::width(ch); if(to_end - to < len) { r=std::codecvt_base::partial; break; } to = boost::locale::utf::utf_traits::encode(ch,to); state = 0; from++; } from_next=from; to_next=to; if(r==std::codecvt_base::ok && from!=from_end) r = std::codecvt_base::partial; #ifdef DEBUG_CODECVT std::cout << "Returning "; switch(r) { case std::codecvt_base::ok: std::cout << "ok" << std::endl; break; case std::codecvt_base::partial: std::cout << "partial" << std::endl; break; case std::codecvt_base::error: std::cout << "error" << std::endl; break; default: std::cout << "other" << std::endl; break; } std::cout << "State " << std::hex << state < class utf8_codecvt : public std::codecvt { public: utf8_codecvt(size_t refs = 0) : std::codecvt(refs) { } protected: typedef CharType uchar; virtual std::codecvt_base::result do_unshift(std::mbstate_t &/*s*/,char *from,char * /*to*/,char *&next) const { next=from; return std::codecvt_base::ok; } virtual int do_encoding() const throw() { return 0; } virtual int do_max_length() const throw() { return 4; } virtual bool do_always_noconv() const throw() { return false; } virtual int do_length( std::mbstate_t #ifdef BOOST_NOWIDE_DO_LENGTH_MBSTATE_CONST const #endif &/*state*/, char const *from, char const *from_end, size_t max) const { #ifndef BOOST_NOWIDE_DO_LENGTH_MBSTATE_CONST char const *start_from = from; #else size_t save_max = max; #endif while(max > 0 && from < from_end){ char const *save_from = from; boost::uint32_t ch=boost::locale::utf::utf_traits::decode(from,from_end); if(ch==boost::locale::utf::incomplete || ch==boost::locale::utf::illegal) { from = save_from; break; } max--; } #ifndef BOOST_NOWIDE_DO_LENGTH_MBSTATE_CONST return from - start_from; #else return save_max - max; #endif } virtual std::codecvt_base::result do_in( std::mbstate_t &/*state*/, char const *from, char const *from_end, char const *&from_next, uchar *to, uchar *to_end, uchar *&to_next) const { std::codecvt_base::result r=std::codecvt_base::ok; // mbstate_t is POD type and should be initialized to 0 (i.a. state = stateT()) // according to standard. We use it to keep a flag 0/1 for surrogate pair writing // // if 0 no code above >0xFFFF observed, of 1 a code above 0xFFFF observerd // and first pair is written, but no input consumed while(to < to_end && from < from_end) { #ifdef DEBUG_CODECVT std::cout << "Entering IN--------------" << std::endl; std::cout << "State " << std::hex << state <::decode(from,from_end); if(ch==boost::locale::utf::illegal) { r=std::codecvt_base::error; from = from_saved; break; } if(ch==boost::locale::utf::incomplete) { r=std::codecvt_base::partial; from=from_saved; break; } *to++=ch; } from_next=from; to_next=to; if(r == std::codecvt_base::ok && from!=from_end) r = std::codecvt_base::partial; #ifdef DEBUG_CODECVT std::cout << "Returning "; switch(r) { case std::codecvt_base::ok: std::cout << "ok" << std::endl; break; case std::codecvt_base::partial: std::cout << "partial" << std::endl; break; case std::codecvt_base::error: std::cout << "error" << std::endl; break; default: std::cout << "other" << std::endl; break; } std::cout << "State " << std::hex << state <::width(ch); if(to_end - to < len) { r=std::codecvt_base::partial; break; } to = boost::locale::utf::utf_traits::encode(ch,to); from++; } from_next=from; to_next=to; if(r==std::codecvt_base::ok && from!=from_end) r = std::codecvt_base::partial; #ifdef DEBUG_CODECVT std::cout << "Returning "; switch(r) { case std::codecvt_base::ok: std::cout << "ok" << std::endl; break; case std::codecvt_base::partial: std::cout << "partial" << std::endl; break; case std::codecvt_base::error: std::cout << "error" << std::endl; break; default: std::cout << "other" << std::endl; break; } std::cout << "State " << std::hex << state < #ifdef BOOST_NOWIDE_USE_WINDOWS_H #include #else // // These are function prototypes... Allow to to include windows.h // extern "C" { __declspec(dllimport) wchar_t* __stdcall GetEnvironmentStringsW(void); __declspec(dllimport) int __stdcall FreeEnvironmentStringsW(wchar_t *); __declspec(dllimport) wchar_t* __stdcall GetCommandLineW(void); __declspec(dllimport) wchar_t** __stdcall CommandLineToArgvW(wchar_t const *,int *); __declspec(dllimport) unsigned long __stdcall GetLastError(); __declspec(dllimport) void* __stdcall LocalFree(void *); __declspec(dllimport) int __stdcall SetEnvironmentVariableW(wchar_t const *,wchar_t const *); __declspec(dllimport) unsigned long __stdcall GetEnvironmentVariableW(wchar_t const *,wchar_t *,unsigned long); } #endif #endif /// // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 Slic3r-version_1.39.1/xs/src/clipper.cpp000066400000000000000000004045651324354444700201240ustar00rootroot00000000000000/******************************************************************************* * * * Author : Angus Johnson * * Version : 6.2.9 * * Date : 16 February 2015 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2015 * * * * License: * * Use, modification & distribution is subject to Boost Software License Ver 1. * * http://www.boost.org/LICENSE_1_0.txt * * * * Attributions: * * The code in this library is an extension of Bala Vatti's clipping algorithm: * * "A generic solution to polygon clipping" * * Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * * http://portal.acm.org/citation.cfm?id=129906 * * * * Computer graphics and geometric modeling: implementation and algorithms * * By Max K. Agoston * * Springer; 1 edition (January 4, 2005) * * http://books.google.com/books?q=vatti+clipping+agoston * * * * See also: * * "Polygon Offsetting by Computing Winding Numbers" * * Paper no. DETC2005-85513 pp. 565-575 * * ASME 2005 International Design Engineering Technical Conferences * * and Computers and Information in Engineering Conference (IDETC/CIE2005) * * September 24-28, 2005 , Long Beach, California, USA * * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * * * *******************************************************************************/ /******************************************************************************* * * * This is a translation of the Delphi Clipper library and the naming style * * used has retained a Delphi flavour. * * * *******************************************************************************/ #include "clipper.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace ClipperLib { static double const pi = 3.141592653589793238; static double const two_pi = pi *2; static double const def_arc_tolerance = 0.25; enum Direction { dRightToLeft, dLeftToRight }; static int const Unassigned = -1; //edge not currently 'owning' a solution static int const Skip = -2; //edge that would otherwise close a path #define HORIZONTAL (-1.0E+40) #define TOLERANCE (1.0e-20) #define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) // Output polygon. struct OutRec { int Idx; bool IsHole; bool IsOpen; //The 'FirstLeft' field points to another OutRec that contains or is the //'parent' of OutRec. It is 'first left' because the ActiveEdgeList (AEL) is //parsed left from the current edge (owning OutRec) until the owner OutRec //is found. This field simplifies sorting the polygons into a tree structure //which reflects the parent/child relationships of all polygons. //This field should be renamed Parent, and will be later. OutRec *FirstLeft; // Used only by void Clipper::BuildResult2(PolyTree& polytree) PolyNode *PolyNd; // Linked list of output points, dynamically allocated. OutPt *Pts; OutPt *BottomPt; }; //------------------------------------------------------------------------------ inline cInt Round(double val) { return static_cast((val < 0) ? (val - 0.5) : (val + 0.5)); } //------------------------------------------------------------------------------ // PolyTree methods ... //------------------------------------------------------------------------------ int PolyTree::Total() const { int result = (int)AllNodes.size(); //with negative offsets, ignore the hidden outer polygon ... if (result > 0 && Childs.front() != &AllNodes.front()) result--; return result; } //------------------------------------------------------------------------------ // PolyNode methods ... //------------------------------------------------------------------------------ void PolyNode::AddChild(PolyNode& child) { unsigned cnt = (unsigned)Childs.size(); Childs.push_back(&child); child.Parent = this; child.Index = cnt; } //------------------------------------------------------------------------------ // Edge delimits a hole if it has an odd number of parent loops. bool PolyNode::IsHole() const { bool result = true; PolyNode* node = Parent; while (node) { result = !result; node = node->Parent; } return result; } //------------------------------------------------------------------------------ // Miscellaneous global functions //------------------------------------------------------------------------------ double Area(const Path &poly) { int size = (int)poly.size(); if (size < 3) return 0; double a = 0; for (int i = 0, j = size -1; i < size; ++i) { a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); j = i; } return -a * 0.5; } //------------------------------------------------------------------------------ double Area(const OutRec &outRec) { OutPt *op = outRec.Pts; if (!op) return 0; double a = 0; do { a += (double)(op->Prev->Pt.X + op->Pt.X) * (double)(op->Prev->Pt.Y - op->Pt.Y); op = op->Next; } while (op != outRec.Pts); return a * 0.5; } //------------------------------------------------------------------------------ bool PointIsVertex(const IntPoint &Pt, OutPt *pp) { OutPt *pp2 = pp; do { if (pp2->Pt == Pt) return true; pp2 = pp2->Next; } while (pp2 != pp); return false; } //------------------------------------------------------------------------------ //See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & Agathos //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf int PointInPolygon(const IntPoint &pt, const Path &path) { //returns 0 if false, +1 if true, -1 if pt ON polygon boundary int result = 0; size_t cnt = path.size(); if (cnt < 3) return 0; IntPoint ip = path[0]; for(size_t i = 1; i <= cnt; ++i) { IntPoint ipNext = (i == cnt ? path[0] : path[i]); if (ipNext.Y == pt.Y && ((ipNext.X == pt.X) || (ip.Y == pt.Y && ((ipNext.X > pt.X) == (ip.X < pt.X))))) return -1; if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) { if (ip.X >= pt.X) { if (ipNext.X > pt.X) result = 1 - result; else { double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); if (!d) return -1; if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; } } else { if (ipNext.X > pt.X) { double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); if (!d) return -1; if ((d > 0) == (ipNext.Y > ip.Y)) result = 1 - result; } } } ip = ipNext; } return result; } //------------------------------------------------------------------------------ // Called by Poly2ContainsPoly1() int PointInPolygon (const IntPoint &pt, OutPt *op) { //returns 0 if false, +1 if true, -1 if pt ON polygon boundary int result = 0; OutPt* startOp = op; do { if (op->Next->Pt.Y == pt.Y) { if ((op->Next->Pt.X == pt.X) || (op->Pt.Y == pt.Y && ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) return -1; } if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) { if (op->Pt.X >= pt.X) { if (op->Next->Pt.X > pt.X) result = 1 - result; else { double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); if (!d) return -1; if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; } } else { if (op->Next->Pt.X > pt.X) { double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); if (!d) return -1; if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) result = 1 - result; } } } op = op->Next; } while (startOp != op); return result; } //------------------------------------------------------------------------------ // This is potentially very expensive! O(n^2)! bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) { PROFILE_FUNC(); OutPt* op = OutPt1; do { //nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon int res = PointInPolygon(op->Pt, OutPt2); if (res >= 0) return res > 0; op = op->Next; } while (op != OutPt1); return true; } //---------------------------------------------------------------------- inline bool SlopesEqual(const cInt dx1, const cInt dy1, const cInt dx2, const cInt dy2, bool UseFullInt64Range) { return (UseFullInt64Range) ? // |dx1| < 2^63, |dx2| < 2^63 etc, Int128::sign_determinant_2x2_filtered(dx1, dy1, dx2, dy2) == 0 : // Int128::sign_determinant_2x2(dx1, dy1, dx2, dy2) == 0 : // |dx1| < 2^31, |dx2| < 2^31 etc, // therefore the following computation could be done with 64bit arithmetics. dy1 * dx2 == dx1 * dy2; } inline bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) { return SlopesEqual(e1.Delta.X, e1.Delta.Y, e2.Delta.X, e2.Delta.Y, UseFullInt64Range); } inline bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, const IntPoint &pt3, bool UseFullInt64Range) { return SlopesEqual(pt1.X-pt2.X, pt1.Y-pt2.Y, pt2.X-pt3.X, pt2.Y-pt3.Y, UseFullInt64Range); } inline bool SlopesEqual(const IntPoint &pt1, const IntPoint &pt2, const IntPoint &pt3, const IntPoint &pt4, bool UseFullInt64Range) { return SlopesEqual(pt1.X-pt2.X, pt1.Y-pt2.Y, pt3.X-pt4.X, pt3.Y-pt4.Y, UseFullInt64Range); } //------------------------------------------------------------------------------ inline bool IsHorizontal(TEdge &e) { return e.Delta.Y == 0; } //------------------------------------------------------------------------------ inline double GetDx(const IntPoint &pt1, const IntPoint &pt2) { return (pt1.Y == pt2.Y) ? HORIZONTAL : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); } //--------------------------------------------------------------------------- inline cInt TopX(TEdge &edge, const cInt currentY) { return (currentY == edge.Top.Y) ? edge.Top.X : edge.Bot.X + Round(edge.Dx *(currentY - edge.Bot.Y)); } //------------------------------------------------------------------------------ void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) { #ifdef use_xyz ip.Z = 0; #endif double b1, b2; if (Edge1.Dx == Edge2.Dx) { ip.Y = Edge1.Curr.Y; ip.X = TopX(Edge1, ip.Y); return; } else if (Edge1.Delta.X == 0) { ip.X = Edge1.Bot.X; if (IsHorizontal(Edge2)) ip.Y = Edge2.Bot.Y; else { b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); ip.Y = Round(ip.X / Edge2.Dx + b2); } } else if (Edge2.Delta.X == 0) { ip.X = Edge2.Bot.X; if (IsHorizontal(Edge1)) ip.Y = Edge1.Bot.Y; else { b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); ip.Y = Round(ip.X / Edge1.Dx + b1); } } else { b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; double q = (b2-b1) / (Edge1.Dx - Edge2.Dx); ip.Y = Round(q); ip.X = (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) ? Round(Edge1.Dx * q + b1) : Round(Edge2.Dx * q + b2); } if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) { if (Edge1.Top.Y > Edge2.Top.Y) ip.Y = Edge1.Top.Y; else ip.Y = Edge2.Top.Y; if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) ip.X = TopX(Edge1, ip.Y); else ip.X = TopX(Edge2, ip.Y); } //finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... if (ip.Y > Edge1.Curr.Y) { ip.Y = Edge1.Curr.Y; //use the more vertical edge to derive X ... if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) ip.X = TopX(Edge2, ip.Y); else ip.X = TopX(Edge1, ip.Y); } } //------------------------------------------------------------------------------ // Reverse a linked loop of points representing a closed polygon. // This has a time complexity of O(n) void ReversePolyPtLinks(OutPt *pp) { if (!pp) return; OutPt *pp1 = pp; do { OutPt *pp2 = pp1->Next; pp1->Next = pp1->Prev; pp1->Prev = pp2; pp1 = pp2; } while( pp1 != pp ); } //------------------------------------------------------------------------------ inline void InitEdge(TEdge* e, TEdge* eNext, TEdge* ePrev, const IntPoint& Pt) { std::memset(e, 0, sizeof(TEdge)); e->Next = eNext; e->Prev = ePrev; e->Curr = Pt; e->OutIdx = Unassigned; } //------------------------------------------------------------------------------ void InitEdge2(TEdge& e, PolyType Pt) { if (e.Curr.Y >= e.Next->Curr.Y) { e.Bot = e.Curr; e.Top = e.Next->Curr; } else { e.Top = e.Curr; e.Bot = e.Next->Curr; } e.Delta.X = (e.Top.X - e.Bot.X); e.Delta.Y = (e.Top.Y - e.Bot.Y); if (e.Delta.Y == 0) e.Dx = HORIZONTAL; else e.Dx = (double)(e.Delta.X) / e.Delta.Y; e.PolyTyp = Pt; } //------------------------------------------------------------------------------ // Called from ClipperBase::AddPathInternal() to remove collinear and duplicate points. inline TEdge* RemoveEdge(TEdge* e) { //removes e from double_linked_list (but without removing from memory) e->Prev->Next = e->Next; e->Next->Prev = e->Prev; TEdge* result = e->Next; e->Prev = 0; //flag as removed (see ClipperBase.Clear) return result; } //------------------------------------------------------------------------------ inline void ReverseHorizontal(TEdge &e) { //swap horizontal edges' Top and Bottom x's so they follow the natural //progression of the bounds - ie so their xbots will align with the //adjoining lower edge. [Helpful in the ProcessHorizontal() method.] std::swap(e.Top.X, e.Bot.X); #ifdef use_xyz std::swap(e.Top.Z, e.Bot.Z); #endif } //------------------------------------------------------------------------------ bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) { //precondition: segments are Collinear. if (std::abs(pt1a.X - pt1b.X) > std::abs(pt1a.Y - pt1b.Y)) { if (pt1a.X > pt1b.X) std::swap(pt1a, pt1b); if (pt2a.X > pt2b.X) std::swap(pt2a, pt2b); if (pt1a.X > pt2a.X) pt1 = pt1a; else pt1 = pt2a; if (pt1b.X < pt2b.X) pt2 = pt1b; else pt2 = pt2b; return pt1.X < pt2.X; } else { if (pt1a.Y < pt1b.Y) std::swap(pt1a, pt1b); if (pt2a.Y < pt2b.Y) std::swap(pt2a, pt2b); if (pt1a.Y < pt2a.Y) pt1 = pt1a; else pt1 = pt2a; if (pt1b.Y > pt2b.Y) pt2 = pt1b; else pt2 = pt2b; return pt1.Y > pt2.Y; } } //------------------------------------------------------------------------------ bool FirstIsBottomPt(const OutPt* btmPt1, const OutPt* btmPt2) { OutPt *p = btmPt1->Prev; while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Prev; double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); p = btmPt1->Next; while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) p = p->Next; double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); p = btmPt2->Prev; while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Prev; double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); p = btmPt2->Next; while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) p = p->Next; double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); } //------------------------------------------------------------------------------ // Called by GetLowermostRec() OutPt* GetBottomPt(OutPt *pp) { OutPt* dups = 0; OutPt* p = pp->Next; while (p != pp) { if (p->Pt.Y > pp->Pt.Y) { pp = p; dups = 0; } else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) { if (p->Pt.X < pp->Pt.X) { dups = 0; pp = p; } else { if (p->Next != pp && p->Prev != pp) dups = p; } } p = p->Next; } if (dups) { //there appears to be at least 2 vertices at BottomPt so ... while (dups != p) { if (!FirstIsBottomPt(p, dups)) pp = dups; dups = dups->Next; while (dups->Pt != pp->Pt) dups = dups->Next; } } return pp; } //------------------------------------------------------------------------------ bool Pt2IsBetweenPt1AndPt3(const IntPoint &pt1, const IntPoint &pt2, const IntPoint &pt3) { if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) return false; else if (pt1.X != pt3.X) return (pt2.X > pt1.X) == (pt2.X < pt3.X); else return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); } //------------------------------------------------------------------------------ bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) { if (seg1a > seg1b) std::swap(seg1a, seg1b); if (seg2a > seg2b) std::swap(seg2a, seg2b); return (seg1a < seg2b) && (seg2a < seg1b); } //------------------------------------------------------------------------------ // ClipperBase class methods ... //------------------------------------------------------------------------------ // Called from ClipperBase::AddPath() to verify the scale of the input polygon coordinates. inline void RangeTest(const IntPoint& Pt, bool& useFullRange) { if (useFullRange) { if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) throw clipperException("Coordinate outside allowed range"); } else if (Pt.X > loRange|| Pt.Y > loRange || -Pt.X > loRange || -Pt.Y > loRange) { useFullRange = true; RangeTest(Pt, useFullRange); } } //------------------------------------------------------------------------------ // Called from ClipperBase::AddPath() to construct the Local Minima List. // Find a local minimum edge on the path starting with E. inline TEdge* FindNextLocMin(TEdge* E) { for (;;) { while (E->Bot != E->Prev->Bot || E->Curr == E->Top) E = E->Next; if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) break; while (IsHorizontal(*E->Prev)) E = E->Prev; TEdge* E2 = E; while (IsHorizontal(*E)) E = E->Next; if (E->Top.Y == E->Prev->Bot.Y) continue; //ie just an intermediate horz. if (E2->Prev->Bot.X < E->Bot.X) E = E2; break; } return E; } //------------------------------------------------------------------------------ // Called from ClipperBase::AddPath(). TEdge* ClipperBase::ProcessBound(TEdge* E, bool NextIsForward) { TEdge *Result = E; TEdge *Horz = 0; if (E->OutIdx == Skip) { //if edges still remain in the current bound beyond the skip edge then //create another LocMin and call ProcessBound once more if (NextIsForward) { while (E->Top.Y == E->Next->Bot.Y) E = E->Next; //don't include top horizontals when parsing a bound a second time, //they will be contained in the opposite bound ... while (E != Result && IsHorizontal(*E)) E = E->Prev; } else { while (E->Top.Y == E->Prev->Bot.Y) E = E->Prev; while (E != Result && IsHorizontal(*E)) E = E->Next; } if (E == Result) { if (NextIsForward) Result = E->Next; else Result = E->Prev; } else { //there are more edges in the bound beyond result starting with E if (NextIsForward) E = Result->Next; else E = Result->Prev; LocalMinimum locMin; locMin.Y = E->Bot.Y; locMin.LeftBound = 0; locMin.RightBound = E; E->WindDelta = 0; Result = ProcessBound(E, NextIsForward); m_MinimaList.push_back(locMin); } return Result; } TEdge *EStart; if (IsHorizontal(*E)) { //We need to be careful with open paths because this may not be a //true local minima (ie E may be following a skip edge). //Also, consecutive horz. edges may start heading left before going right. if (NextIsForward) EStart = E->Prev; else EStart = E->Next; if (IsHorizontal(*EStart)) //ie an adjoining horizontal skip edge { if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) ReverseHorizontal(*E); } else if (EStart->Bot.X != E->Bot.X) ReverseHorizontal(*E); } EStart = E; if (NextIsForward) { while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) Result = Result->Next; if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) { //nb: at the top of a bound, horizontals are added to the bound //only when the preceding edge attaches to the horizontal's left vertex //unless a Skip edge is encountered when that becomes the top divide Horz = Result; while (IsHorizontal(*Horz->Prev)) Horz = Horz->Prev; if (Horz->Prev->Top.X > Result->Next->Top.X) Result = Horz->Prev; } while (E != Result) { E->NextInLML = E->Next; if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); E = E->Next; } if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); Result = Result->Next; //move to the edge just beyond current bound } else { while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) Result = Result->Prev; if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) { Horz = Result; while (IsHorizontal(*Horz->Next)) Horz = Horz->Next; if (Horz->Next->Top.X == Result->Prev->Top.X || Horz->Next->Top.X > Result->Prev->Top.X) Result = Horz->Next; } while (E != Result) { E->NextInLML = E->Prev; if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) ReverseHorizontal(*E); E = E->Prev; } if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) ReverseHorizontal(*E); Result = Result->Prev; //move to the edge just beyond current bound } return Result; } //------------------------------------------------------------------------------ bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) { PROFILE_FUNC(); // Remove duplicate end point from a closed input path. // Remove duplicate points from the end of the input path. int highI = (int)pg.size() -1; if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; if ((Closed && highI < 2) || (!Closed && highI < 1)) return false; // Allocate a new edge array. std::vector edges(highI + 1); // Fill in the edge array. bool result = AddPathInternal(pg, highI, PolyTyp, Closed, edges.data()); if (result) // Success, remember the edge array. m_edges.emplace_back(std::move(edges)); return result; } bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) { PROFILE_FUNC(); std::vector num_edges(ppg.size(), 0); int num_edges_total = 0; for (size_t i = 0; i < ppg.size(); ++ i) { const Path &pg = ppg[i]; // Remove duplicate end point from a closed input path. // Remove duplicate points from the end of the input path. int highI = (int)pg.size() -1; if (Closed) while (highI > 0 && (pg[highI] == pg[0])) --highI; while (highI > 0 && (pg[highI] == pg[highI -1])) --highI; if ((Closed && highI < 2) || (!Closed && highI < 1)) highI = -1; num_edges[i] = highI + 1; num_edges_total += highI + 1; } if (num_edges_total == 0) return false; // Allocate a new edge array. std::vector edges(num_edges_total); // Fill in the edge array. bool result = false; TEdge *p_edge = edges.data(); for (Paths::size_type i = 0; i < ppg.size(); ++i) if (num_edges[i]) { bool res = AddPathInternal(ppg[i], num_edges[i] - 1, PolyTyp, Closed, p_edge); if (res) { p_edge += num_edges[i]; result = true; } } if (result) // At least some edges were generated. Remember the edge array. m_edges.emplace_back(std::move(edges)); return result; } bool ClipperBase::AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, bool Closed, TEdge* edges) { PROFILE_FUNC(); #ifdef use_lines if (!Closed && PolyTyp == ptClip) throw clipperException("AddPath: Open paths must be subject."); #else if (!Closed) throw clipperException("AddPath: Open paths have been disabled."); #endif assert(highI >= 0 && highI < pg.size()); //1. Basic (first) edge initialization ... try { edges[1].Curr = pg[1]; RangeTest(pg[0], m_UseFullRange); RangeTest(pg[highI], m_UseFullRange); InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); InitEdge(&edges[highI], &edges[0], &edges[highI-1], pg[highI]); for (int i = highI - 1; i >= 1; --i) { RangeTest(pg[i], m_UseFullRange); InitEdge(&edges[i], &edges[i+1], &edges[i-1], pg[i]); } } catch(...) { throw; //range test fails } TEdge *eStart = &edges[0]; //2. Remove duplicate vertices, and (when closed) collinear edges ... TEdge *E = eStart, *eLoopStop = eStart; for (;;) { //nb: allows matching start and end points when not Closed ... if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) { if (E == E->Next) break; if (E == eStart) eStart = E->Next; E = RemoveEdge(E); eLoopStop = E; continue; } if (E->Prev == E->Next) break; //only two vertices else if (Closed && SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, m_UseFullRange) && (!m_PreserveCollinear || !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) { //Collinear edges are allowed for open paths but in closed paths //the default is to merge adjacent collinear edges into a single edge. //However, if the PreserveCollinear property is enabled, only overlapping //collinear edges (ie spikes) will be removed from closed paths. if (E == eStart) eStart = E->Next; E = RemoveEdge(E); E = E->Prev; eLoopStop = E; continue; } E = E->Next; if ((E == eLoopStop) || (!Closed && E->Next == eStart)) break; } if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) { return false; } if (!Closed) { m_HasOpenPaths = true; eStart->Prev->OutIdx = Skip; } //3. Do second stage of edge initialization ... // IsFlat means all vertices have the same Y coordinate. bool IsFlat = true; E = eStart; do { InitEdge2(*E, PolyTyp); E = E->Next; if (IsFlat && E->Curr.Y != eStart->Curr.Y) IsFlat = false; } while (E != eStart); //4. Finally, add edge bounds to LocalMinima list ... //Totally flat paths must be handled differently when adding them //to LocalMinima list to avoid endless loops etc ... if (IsFlat) { if (Closed) { return false; } E->Prev->OutIdx = Skip; LocalMinimum locMin; locMin.Y = E->Bot.Y; locMin.LeftBound = 0; locMin.RightBound = E; locMin.RightBound->Side = esRight; locMin.RightBound->WindDelta = 0; for (;;) { if (E->Bot.X != E->Prev->Top.X) ReverseHorizontal(*E); if (E->Next->OutIdx == Skip) break; E->NextInLML = E->Next; E = E->Next; } m_MinimaList.push_back(locMin); return true; } bool leftBoundIsForward; TEdge* EMin = 0; //workaround to avoid an endless loop in the while loop below when //open paths have matching start and end points ... if (E->Prev->Bot == E->Prev->Top) E = E->Next; // Find local minima and store them into a Local Minima List. // Multiple Local Minima could be created for a single path. for (;;) { E = FindNextLocMin(E); if (E == EMin) break; else if (!EMin) EMin = E; //E and E.Prev now share a local minima (left aligned if horizontal). //Compare their slopes to find which starts which bound ... LocalMinimum locMin; locMin.Y = E->Bot.Y; if (E->Dx < E->Prev->Dx) { locMin.LeftBound = E->Prev; locMin.RightBound = E; leftBoundIsForward = false; //Q.nextInLML = Q.prev } else { locMin.LeftBound = E; locMin.RightBound = E->Prev; leftBoundIsForward = true; //Q.nextInLML = Q.next } locMin.LeftBound->Side = esLeft; locMin.RightBound->Side = esRight; if (!Closed) locMin.LeftBound->WindDelta = 0; else if (locMin.LeftBound->Next == locMin.RightBound) locMin.LeftBound->WindDelta = -1; else locMin.LeftBound->WindDelta = 1; locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta; E = ProcessBound(locMin.LeftBound, leftBoundIsForward); if (E->OutIdx == Skip) E = ProcessBound(E, leftBoundIsForward); TEdge* E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward); if (E2->OutIdx == Skip) E2 = ProcessBound(E2, !leftBoundIsForward); if (locMin.LeftBound->OutIdx == Skip) locMin.LeftBound = 0; else if (locMin.RightBound->OutIdx == Skip) locMin.RightBound = 0; m_MinimaList.push_back(locMin); if (!leftBoundIsForward) E = E2; } return true; } //------------------------------------------------------------------------------ void ClipperBase::Clear() { PROFILE_FUNC(); m_MinimaList.clear(); m_edges.clear(); m_UseFullRange = false; m_HasOpenPaths = false; } //------------------------------------------------------------------------------ // Initialize the Local Minima List: // Sort the LML entries, initialize the left / right bound edges of each Local Minima. void ClipperBase::Reset() { PROFILE_FUNC(); if (m_MinimaList.empty()) return; //ie nothing to process std::sort(m_MinimaList.begin(), m_MinimaList.end(), [](const LocalMinimum& lm1, const LocalMinimum& lm2){ return lm1.Y < lm2.Y; }); //reset all edges ... for (LocalMinimum &lm : m_MinimaList) { TEdge* e = lm.LeftBound; if (e) { e->Curr = e->Bot; e->Side = esLeft; e->OutIdx = Unassigned; } e = lm.RightBound; if (e) { e->Curr = e->Bot; e->Side = esRight; e->OutIdx = Unassigned; } } } //------------------------------------------------------------------------------ // Get bounds of the edges referenced by the Local Minima List. // Returns (0,0,0,0) for an empty rectangle. IntRect ClipperBase::GetBounds() { PROFILE_FUNC(); IntRect result; auto lm = m_MinimaList.begin(); if (lm == m_MinimaList.end()) { result.left = result.top = result.right = result.bottom = 0; return result; } result.left = lm->LeftBound->Bot.X; result.top = lm->LeftBound->Bot.Y; result.right = lm->LeftBound->Bot.X; result.bottom = lm->LeftBound->Bot.Y; while (lm != m_MinimaList.end()) { result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y); TEdge* e = lm->LeftBound; for (;;) { TEdge* bottomE = e; while (e->NextInLML) { if (e->Bot.X < result.left) result.left = e->Bot.X; if (e->Bot.X > result.right) result.right = e->Bot.X; e = e->NextInLML; } result.left = std::min(result.left, e->Bot.X); result.right = std::max(result.right, e->Bot.X); result.left = std::min(result.left, e->Top.X); result.right = std::max(result.right, e->Top.X); result.top = std::min(result.top, e->Top.Y); if (bottomE == lm->LeftBound) e = lm->RightBound; else break; } ++lm; } return result; } //------------------------------------------------------------------------------ // TClipper methods ... //------------------------------------------------------------------------------ Clipper::Clipper(int initOptions) : ClipperBase(), m_OutPtsFree(nullptr), m_OutPtsChunkSize(32), m_OutPtsChunkLast(32), m_ActiveEdges(nullptr), m_SortedEdges(nullptr) { m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); m_HasOpenPaths = false; #ifdef use_xyz m_ZFill = 0; #endif } //------------------------------------------------------------------------------ void Clipper::Reset() { PROFILE_FUNC(); ClipperBase::Reset(); m_Scanbeam = std::priority_queue(); m_Maxima.clear(); m_ActiveEdges = 0; m_SortedEdges = 0; for (auto lm = m_MinimaList.rbegin(); lm != m_MinimaList.rend(); ++lm) m_Scanbeam.push(lm->Y); } //------------------------------------------------------------------------------ bool Clipper::Execute(ClipType clipType, Paths &solution, PolyFillType subjFillType, PolyFillType clipFillType) { PROFILE_FUNC(); if (m_HasOpenPaths) throw clipperException("Error: PolyTree struct is needed for open path clipping."); solution.resize(0); m_SubjFillType = subjFillType; m_ClipFillType = clipFillType; m_ClipType = clipType; m_UsingPolyTree = false; bool succeeded = ExecuteInternal(); if (succeeded) BuildResult(solution); DisposeAllOutRecs(); return succeeded; } //------------------------------------------------------------------------------ bool Clipper::Execute(ClipType clipType, PolyTree& polytree, PolyFillType subjFillType, PolyFillType clipFillType) { PROFILE_FUNC(); m_SubjFillType = subjFillType; m_ClipFillType = clipFillType; m_ClipType = clipType; m_UsingPolyTree = true; bool succeeded = ExecuteInternal(); if (succeeded) BuildResult2(polytree); DisposeAllOutRecs(); return succeeded; } //------------------------------------------------------------------------------ bool Clipper::ExecuteInternal() { PROFILE_FUNC(); bool succeeded = true; try { PROFILE_BLOCK(Clipper_ExecuteInternal_Process); Reset(); if (m_MinimaList.empty()) return true; cInt botY = m_Scanbeam.top(); do { m_Scanbeam.pop(); } while (! m_Scanbeam.empty() && botY == m_Scanbeam.top()); do { InsertLocalMinimaIntoAEL(botY); ProcessHorizontals(); m_GhostJoins.clear(); if (m_Scanbeam.empty()) break; cInt topY = m_Scanbeam.top(); do { m_Scanbeam.pop(); } while (! m_Scanbeam.empty() && topY == m_Scanbeam.top()); succeeded = ProcessIntersections(topY); if (!succeeded) break; ProcessEdgesAtTopOfScanbeam(topY); botY = topY; } while (!m_Scanbeam.empty() || !m_MinimaList.empty()); } catch(...) { succeeded = false; } if (succeeded) { PROFILE_BLOCK(Clipper_ExecuteInternal_Fix); //fix orientations ... //FIXME Vojtech: Does it not invalidate the loop hierarchy maintained as OutRec::FirstLeft pointers? //FIXME Vojtech: The area is calculated with floats, it may not be numerically stable! { PROFILE_BLOCK(Clipper_ExecuteInternal_Fix_orientations); for (OutRec *outRec : m_PolyOuts) if (outRec->Pts && !outRec->IsOpen && (outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) ReversePolyPtLinks(outRec->Pts); } JoinCommonEdges(); //unfortunately FixupOutPolygon() must be done after JoinCommonEdges() { PROFILE_BLOCK(Clipper_ExecuteInternal_Fix_fixup); for (OutRec *outRec : m_PolyOuts) if (outRec->Pts) { if (outRec->IsOpen) // Removes duplicate points. FixupOutPolyline(*outRec); else // Removes duplicate points and simplifies consecutive parallel edges by removing the middle vertex. FixupOutPolygon(*outRec); } } // For each polygon, search for exactly duplicate non-successive points. // If such a point is found, the loop is split into two pieces. // Search for the duplicate points is O(n^2)! // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/Clipper/Properties/StrictlySimple.htm if (m_StrictSimple) DoSimplePolygons(); } m_Joins.clear(); m_GhostJoins.clear(); return succeeded; } //------------------------------------------------------------------------------ OutPt* Clipper::AllocateOutPt() { OutPt *pt; if (m_OutPtsFree) { // Recycle some of the already released points. pt = m_OutPtsFree; m_OutPtsFree = pt->Next; } else if (m_OutPtsChunkLast < m_OutPtsChunkSize) { // Get a point from the last chunk. pt = m_OutPts.back() + (m_OutPtsChunkLast ++); } else { // The last chunk is full. Allocate a new one. m_OutPts.push_back(new OutPt[m_OutPtsChunkSize]); m_OutPtsChunkLast = 1; pt = m_OutPts.back(); } return pt; } void Clipper::DisposeAllOutRecs() { for (OutPt *pts : m_OutPts) delete[] pts; for (OutRec *rec : m_PolyOuts) delete rec; m_OutPts.clear(); m_OutPtsFree = nullptr; m_OutPtsChunkLast = m_OutPtsChunkSize; m_PolyOuts.clear(); } //------------------------------------------------------------------------------ void Clipper::SetWindingCount(TEdge &edge) const { TEdge *e = edge.PrevInAEL; //find the edge of the same polytype that immediately preceeds 'edge' in AEL while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) e = e->PrevInAEL; if (!e) { edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); edge.WindCnt2 = 0; e = m_ActiveEdges; //ie get ready to calc WindCnt2 } else if (edge.WindDelta == 0 && m_ClipType != ctUnion) { edge.WindCnt = 1; edge.WindCnt2 = e->WindCnt2; e = e->NextInAEL; //ie get ready to calc WindCnt2 } else if (IsEvenOddFillType(edge)) { //EvenOdd filling ... if (edge.WindDelta == 0) { //are we inside a subj polygon ... bool Inside = true; TEdge *e2 = e->PrevInAEL; while (e2) { if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) Inside = !Inside; e2 = e2->PrevInAEL; } edge.WindCnt = (Inside ? 0 : 1); } else { edge.WindCnt = edge.WindDelta; } edge.WindCnt2 = e->WindCnt2; e = e->NextInAEL; //ie get ready to calc WindCnt2 } else { //nonZero, Positive or Negative filling ... if (e->WindCnt * e->WindDelta < 0) { //prev edge is 'decreasing' WindCount (WC) toward zero //so we're outside the previous polygon ... if (std::abs(e->WindCnt) > 1) { //outside prev poly but still inside another. //when reversing direction of prev poly use the same WC if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; //otherwise continue to 'decrease' WC ... else edge.WindCnt = e->WindCnt + edge.WindDelta; } else //now outside all polys of same polytype so set own WC ... edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); } else { //prev edge is 'increasing' WindCount (WC) away from zero //so we're inside the previous polygon ... if (edge.WindDelta == 0) edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); //if wind direction is reversing prev then use same WC else if (e->WindDelta * edge.WindDelta < 0) edge.WindCnt = e->WindCnt; //otherwise add to WC ... else edge.WindCnt = e->WindCnt + edge.WindDelta; } edge.WindCnt2 = e->WindCnt2; e = e->NextInAEL; //ie get ready to calc WindCnt2 } //update WindCnt2 ... if (IsEvenOddAltFillType(edge)) { //EvenOdd filling ... while (e != &edge) { if (e->WindDelta != 0) edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); e = e->NextInAEL; } } else { //nonZero, Positive or Negative filling ... while ( e != &edge ) { edge.WindCnt2 += e->WindDelta; e = e->NextInAEL; } } } //------------------------------------------------------------------------------ bool Clipper::IsContributing(const TEdge& edge) const { PolyFillType pft, pft2; if (edge.PolyTyp == ptSubject) { pft = m_SubjFillType; pft2 = m_ClipFillType; } else { pft = m_ClipFillType; pft2 = m_SubjFillType; } switch(pft) { case pftEvenOdd: //return false if a subj line has been flagged as inside a subj polygon if (edge.WindDelta == 0 && edge.WindCnt != 1) return false; break; case pftNonZero: if (std::abs(edge.WindCnt) != 1) return false; break; case pftPositive: if (edge.WindCnt != 1) return false; break; default: //pftNegative if (edge.WindCnt != -1) return false; } switch(m_ClipType) { case ctIntersection: switch(pft2) { case pftEvenOdd: case pftNonZero: return (edge.WindCnt2 != 0); case pftPositive: return (edge.WindCnt2 > 0); default: return (edge.WindCnt2 < 0); } break; case ctUnion: switch(pft2) { case pftEvenOdd: case pftNonZero: return (edge.WindCnt2 == 0); case pftPositive: return (edge.WindCnt2 <= 0); default: return (edge.WindCnt2 >= 0); } break; case ctDifference: if (edge.PolyTyp == ptSubject) switch(pft2) { case pftEvenOdd: case pftNonZero: return (edge.WindCnt2 == 0); case pftPositive: return (edge.WindCnt2 <= 0); default: return (edge.WindCnt2 >= 0); } else switch(pft2) { case pftEvenOdd: case pftNonZero: return (edge.WindCnt2 != 0); case pftPositive: return (edge.WindCnt2 > 0); default: return (edge.WindCnt2 < 0); } break; case ctXor: if (edge.WindDelta == 0) //XOr always contributing unless open switch(pft2) { case pftEvenOdd: case pftNonZero: return (edge.WindCnt2 == 0); case pftPositive: return (edge.WindCnt2 <= 0); default: return (edge.WindCnt2 >= 0); } else return true; break; default: return true; } } //------------------------------------------------------------------------------ // Called from Clipper::InsertLocalMinimaIntoAEL() and Clipper::IntersectEdges(). OutPt* Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { PROFILE_FUNC(); OutPt* result; TEdge *e, *prevE; if (IsHorizontal(*e2) || ( e1->Dx > e2->Dx )) { result = AddOutPt(e1, Pt); e2->OutIdx = e1->OutIdx; e1->Side = esLeft; e2->Side = esRight; e = e1; if (e->PrevInAEL == e2) prevE = e2->PrevInAEL; else prevE = e->PrevInAEL; } else { result = AddOutPt(e2, Pt); e1->OutIdx = e2->OutIdx; e1->Side = esRight; e2->Side = esLeft; e = e2; if (e->PrevInAEL == e1) prevE = e1->PrevInAEL; else prevE = e->PrevInAEL; } if (prevE && prevE->OutIdx >= 0 && (TopX(*prevE, Pt.Y) == TopX(*e, Pt.Y)) && SlopesEqual(*e, *prevE, m_UseFullRange) && (e->WindDelta != 0) && (prevE->WindDelta != 0)) { OutPt* outPt = AddOutPt(prevE, Pt); m_Joins.emplace_back(Join(result, outPt, e->Top)); } return result; } //------------------------------------------------------------------------------ void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { AddOutPt( e1, Pt ); if (e2->WindDelta == 0) AddOutPt(e2, Pt); if( e1->OutIdx == e2->OutIdx ) { e1->OutIdx = Unassigned; e2->OutIdx = Unassigned; } else if (e1->OutIdx < e2->OutIdx) AppendPolygon(e1, e2); else AppendPolygon(e2, e1); } //------------------------------------------------------------------------------ void Clipper::AddEdgeToSEL(TEdge *edge) { //SEL pointers in PEdge are reused to build a list of horizontal edges. //However, we don't need to worry about order with horizontal edge processing. if( !m_SortedEdges ) { m_SortedEdges = edge; edge->PrevInSEL = 0; edge->NextInSEL = 0; } else { edge->NextInSEL = m_SortedEdges; edge->PrevInSEL = 0; m_SortedEdges->PrevInSEL = edge; m_SortedEdges = edge; } } //------------------------------------------------------------------------------ void Clipper::CopyAELToSEL() { TEdge* e = m_ActiveEdges; m_SortedEdges = e; while ( e ) { e->PrevInSEL = e->PrevInAEL; e->NextInSEL = e->NextInAEL; e = e->NextInAEL; } } //------------------------------------------------------------------------------ // Called from Clipper::ExecuteInternal() void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) { PROFILE_FUNC(); while (!m_MinimaList.empty() && m_MinimaList.back().Y == botY) { TEdge* lb = m_MinimaList.back().LeftBound; TEdge* rb = m_MinimaList.back().RightBound; m_MinimaList.pop_back(); OutPt *Op1 = 0; if (!lb) { //nb: don't insert LB into either AEL or SEL InsertEdgeIntoAEL(rb, 0); SetWindingCount(*rb); if (IsContributing(*rb)) Op1 = AddOutPt(rb, rb->Bot); } else if (!rb) { InsertEdgeIntoAEL(lb, 0); SetWindingCount(*lb); if (IsContributing(*lb)) Op1 = AddOutPt(lb, lb->Bot); m_Scanbeam.push(lb->Top.Y); } else { InsertEdgeIntoAEL(lb, 0); InsertEdgeIntoAEL(rb, lb); SetWindingCount( *lb ); rb->WindCnt = lb->WindCnt; rb->WindCnt2 = lb->WindCnt2; if (IsContributing(*lb)) Op1 = AddLocalMinPoly(lb, rb, lb->Bot); m_Scanbeam.push(lb->Top.Y); } if (rb) { if(IsHorizontal(*rb)) AddEdgeToSEL(rb); else m_Scanbeam.push(rb->Top.Y); } if (!lb || !rb) continue; //if any output polygons share an edge, they'll need joining later ... if (Op1 && IsHorizontal(*rb) && m_GhostJoins.size() > 0 && (rb->WindDelta != 0)) { for (Join &jr : m_GhostJoins) //if the horizontal Rb and a 'ghost' horizontal overlap, then convert //the 'ghost' join to a real join ready for later ... if (HorzSegmentsOverlap(jr.OutPt1->Pt.X, jr.OffPt.X, rb->Bot.X, rb->Top.X)) m_Joins.emplace_back(Join(jr.OutPt1, Op1, jr.OffPt)); } if (lb->OutIdx >= 0 && lb->PrevInAEL && lb->PrevInAEL->Curr.X == lb->Bot.X && lb->PrevInAEL->OutIdx >= 0 && SlopesEqual(*lb->PrevInAEL, *lb, m_UseFullRange) && (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) { OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); m_Joins.emplace_back(Join(Op1, Op2, lb->Top)); } if(lb->NextInAEL != rb) { if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && SlopesEqual(*rb->PrevInAEL, *rb, m_UseFullRange) && (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) { OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); m_Joins.emplace_back(Join(Op1, Op2, rb->Top)); } TEdge* e = lb->NextInAEL; if (e) { while( e != rb ) { //nb: For calculating winding counts etc, IntersectEdges() assumes //that param1 will be to the Right of param2 ABOVE the intersection ... IntersectEdges(rb , e , lb->Curr); //order important here e = e->NextInAEL; } } } } } //------------------------------------------------------------------------------ void Clipper::DeleteFromAEL(TEdge *e) { TEdge* AelPrev = e->PrevInAEL; TEdge* AelNext = e->NextInAEL; if( !AelPrev && !AelNext && (e != m_ActiveEdges) ) return; //already deleted if( AelPrev ) AelPrev->NextInAEL = AelNext; else m_ActiveEdges = AelNext; if( AelNext ) AelNext->PrevInAEL = AelPrev; e->NextInAEL = 0; e->PrevInAEL = 0; } //------------------------------------------------------------------------------ void Clipper::DeleteFromSEL(TEdge *e) { TEdge* SelPrev = e->PrevInSEL; TEdge* SelNext = e->NextInSEL; if( !SelPrev && !SelNext && (e != m_SortedEdges) ) return; //already deleted if( SelPrev ) SelPrev->NextInSEL = SelNext; else m_SortedEdges = SelNext; if( SelNext ) SelNext->PrevInSEL = SelPrev; e->NextInSEL = 0; e->PrevInSEL = 0; } //------------------------------------------------------------------------------ #ifdef use_xyz void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2) { if (pt.Z != 0 || !m_ZFill) return; else if (pt == e1.Bot) pt.Z = e1.Bot.Z; else if (pt == e1.Top) pt.Z = e1.Top.Z; else if (pt == e2.Bot) pt.Z = e2.Bot.Z; else if (pt == e2.Top) pt.Z = e2.Top.Z; else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); } //------------------------------------------------------------------------------ #endif void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) { bool e1Contributing = ( e1->OutIdx >= 0 ); bool e2Contributing = ( e2->OutIdx >= 0 ); #ifdef use_xyz SetZ(Pt, *e1, *e2); #endif #ifdef use_lines //if either edge is on an OPEN path ... if (e1->WindDelta == 0 || e2->WindDelta == 0) { //ignore subject-subject open path intersections UNLESS they //are both open paths, AND they are both 'contributing maximas' ... if (e1->WindDelta == 0 && e2->WindDelta == 0) return; //if intersecting a subj line with a subj poly ... else if (e1->PolyTyp == e2->PolyTyp && e1->WindDelta != e2->WindDelta && m_ClipType == ctUnion) { if (e1->WindDelta == 0) { if (e2Contributing) { AddOutPt(e1, Pt); if (e1Contributing) e1->OutIdx = Unassigned; } } else { if (e1Contributing) { AddOutPt(e2, Pt); if (e2Contributing) e2->OutIdx = Unassigned; } } } else if (e1->PolyTyp != e2->PolyTyp) { //toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... if ((e1->WindDelta == 0) && std::abs(e2->WindCnt) == 1 && (m_ClipType != ctUnion || e2->WindCnt2 == 0)) { AddOutPt(e1, Pt); if (e1Contributing) e1->OutIdx = Unassigned; } else if ((e2->WindDelta == 0) && (std::abs(e1->WindCnt) == 1) && (m_ClipType != ctUnion || e1->WindCnt2 == 0)) { AddOutPt(e2, Pt); if (e2Contributing) e2->OutIdx = Unassigned; } } return; } #endif //update winding counts... //assumes that e1 will be to the Right of e2 ABOVE the intersection if ( e1->PolyTyp == e2->PolyTyp ) { if ( IsEvenOddFillType( *e1) ) { int oldE1WindCnt = e1->WindCnt; e1->WindCnt = e2->WindCnt; e2->WindCnt = oldE1WindCnt; } else { if (e1->WindCnt + e2->WindDelta == 0 ) e1->WindCnt = -e1->WindCnt; else e1->WindCnt += e2->WindDelta; if ( e2->WindCnt - e1->WindDelta == 0 ) e2->WindCnt = -e2->WindCnt; else e2->WindCnt -= e1->WindDelta; } } else { if (!IsEvenOddFillType(*e2)) e1->WindCnt2 += e2->WindDelta; else e1->WindCnt2 = ( e1->WindCnt2 == 0 ) ? 1 : 0; if (!IsEvenOddFillType(*e1)) e2->WindCnt2 -= e1->WindDelta; else e2->WindCnt2 = ( e2->WindCnt2 == 0 ) ? 1 : 0; } PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; if (e1->PolyTyp == ptSubject) { e1FillType = m_SubjFillType; e1FillType2 = m_ClipFillType; } else { e1FillType = m_ClipFillType; e1FillType2 = m_SubjFillType; } if (e2->PolyTyp == ptSubject) { e2FillType = m_SubjFillType; e2FillType2 = m_ClipFillType; } else { e2FillType = m_ClipFillType; e2FillType2 = m_SubjFillType; } cInt e1Wc, e2Wc; switch (e1FillType) { case pftPositive: e1Wc = e1->WindCnt; break; case pftNegative: e1Wc = -e1->WindCnt; break; default: e1Wc = std::abs(e1->WindCnt); } switch(e2FillType) { case pftPositive: e2Wc = e2->WindCnt; break; case pftNegative: e2Wc = -e2->WindCnt; break; default: e2Wc = std::abs(e2->WindCnt); } if ( e1Contributing && e2Contributing ) { if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor) ) { AddLocalMaxPoly(e1, e2, Pt); } else { AddOutPt(e1, Pt); AddOutPt(e2, Pt); std::swap(e1->Side, e2->Side); std::swap(e1->OutIdx, e2->OutIdx); } } else if ( e1Contributing ) { if (e2Wc == 0 || e2Wc == 1) { AddOutPt(e1, Pt); std::swap(e1->Side, e2->Side); std::swap(e1->OutIdx, e2->OutIdx); } } else if ( e2Contributing ) { if (e1Wc == 0 || e1Wc == 1) { AddOutPt(e2, Pt); std::swap(e1->Side, e2->Side); std::swap(e1->OutIdx, e2->OutIdx); } } else if ( (e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) { //neither edge is currently contributing ... cInt e1Wc2, e2Wc2; switch (e1FillType2) { case pftPositive: e1Wc2 = e1->WindCnt2; break; case pftNegative : e1Wc2 = -e1->WindCnt2; break; default: e1Wc2 = std::abs(e1->WindCnt2); } switch (e2FillType2) { case pftPositive: e2Wc2 = e2->WindCnt2; break; case pftNegative: e2Wc2 = -e2->WindCnt2; break; default: e2Wc2 = std::abs(e2->WindCnt2); } if (e1->PolyTyp != e2->PolyTyp) { AddLocalMinPoly(e1, e2, Pt); } else if (e1Wc == 1 && e2Wc == 1) switch( m_ClipType ) { case ctIntersection: if (e1Wc2 > 0 && e2Wc2 > 0) AddLocalMinPoly(e1, e2, Pt); break; case ctUnion: if ( e1Wc2 <= 0 && e2Wc2 <= 0 ) AddLocalMinPoly(e1, e2, Pt); break; case ctDifference: if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) AddLocalMinPoly(e1, e2, Pt); break; case ctXor: AddLocalMinPoly(e1, e2, Pt); } else std::swap(e1->Side, e2->Side); } } //------------------------------------------------------------------------------ void Clipper::SetHoleState(TEdge *e, OutRec *outrec) const { bool IsHole = false; TEdge *e2 = e->PrevInAEL; while (e2) { if (e2->OutIdx >= 0 && e2->WindDelta != 0) { IsHole = !IsHole; if (! outrec->FirstLeft) outrec->FirstLeft = m_PolyOuts[e2->OutIdx]; } e2 = e2->PrevInAEL; } if (IsHole) outrec->IsHole = true; } //------------------------------------------------------------------------------ OutRec* GetLowermostRec(OutRec *outRec1, OutRec *outRec2) { //work out which polygon fragment has the correct hole state ... if (!outRec1->BottomPt) outRec1->BottomPt = GetBottomPt(outRec1->Pts); if (!outRec2->BottomPt) outRec2->BottomPt = GetBottomPt(outRec2->Pts); OutPt *OutPt1 = outRec1->BottomPt; OutPt *OutPt2 = outRec2->BottomPt; if (OutPt1->Pt.Y > OutPt2->Pt.Y) return outRec1; else if (OutPt1->Pt.Y < OutPt2->Pt.Y) return outRec2; else if (OutPt1->Pt.X < OutPt2->Pt.X) return outRec1; else if (OutPt1->Pt.X > OutPt2->Pt.X) return outRec2; else if (OutPt1->Next == OutPt1) return outRec2; else if (OutPt2->Next == OutPt2) return outRec1; else if (FirstIsBottomPt(OutPt1, OutPt2)) return outRec1; else return outRec2; } //------------------------------------------------------------------------------ bool Param1RightOfParam2(OutRec* outRec1, OutRec* outRec2) { do { outRec1 = outRec1->FirstLeft; if (outRec1 == outRec2) return true; } while (outRec1); return false; } //------------------------------------------------------------------------------ OutRec* Clipper::GetOutRec(int Idx) { OutRec* outrec = m_PolyOuts[Idx]; while (outrec != m_PolyOuts[outrec->Idx]) outrec = m_PolyOuts[outrec->Idx]; return outrec; } //------------------------------------------------------------------------------ void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) const { //get the start and ends of both output polygons ... OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; OutRec *holeStateRec; if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; else holeStateRec = GetLowermostRec(outRec1, outRec2); //get the start and ends of both output polygons and //join e2 poly onto e1 poly and delete pointers to e2 ... OutPt* p1_lft = outRec1->Pts; OutPt* p1_rt = p1_lft->Prev; OutPt* p2_lft = outRec2->Pts; OutPt* p2_rt = p2_lft->Prev; EdgeSide Side; //join e2 poly onto e1 poly and delete pointers to e2 ... if( e1->Side == esLeft ) { if( e2->Side == esLeft ) { //z y x a b c ReversePolyPtLinks(p2_lft); p2_lft->Next = p1_lft; p1_lft->Prev = p2_lft; p1_rt->Next = p2_rt; p2_rt->Prev = p1_rt; outRec1->Pts = p2_rt; } else { //x y z a b c p2_rt->Next = p1_lft; p1_lft->Prev = p2_rt; p2_lft->Prev = p1_rt; p1_rt->Next = p2_lft; outRec1->Pts = p2_lft; } Side = esLeft; } else { if( e2->Side == esRight ) { //a b c z y x ReversePolyPtLinks(p2_lft); p1_rt->Next = p2_rt; p2_rt->Prev = p1_rt; p2_lft->Next = p1_lft; p1_lft->Prev = p2_lft; } else { //a b c x y z p1_rt->Next = p2_lft; p2_lft->Prev = p1_rt; p1_lft->Prev = p2_rt; p2_rt->Next = p1_lft; } Side = esRight; } outRec1->BottomPt = 0; if (holeStateRec == outRec2) { if (outRec2->FirstLeft != outRec1) outRec1->FirstLeft = outRec2->FirstLeft; outRec1->IsHole = outRec2->IsHole; } outRec2->Pts = 0; outRec2->BottomPt = 0; outRec2->FirstLeft = outRec1; int OKIdx = e1->OutIdx; int ObsoleteIdx = e2->OutIdx; e1->OutIdx = Unassigned; //nb: safe because we only get here via AddLocalMaxPoly e2->OutIdx = Unassigned; TEdge* e = m_ActiveEdges; while( e ) { if( e->OutIdx == ObsoleteIdx ) { e->OutIdx = OKIdx; e->Side = Side; break; } e = e->NextInAEL; } outRec2->Idx = outRec1->Idx; } //------------------------------------------------------------------------------ OutRec* Clipper::CreateOutRec() { OutRec* result = new OutRec; result->IsHole = false; result->IsOpen = false; result->FirstLeft = 0; result->Pts = 0; result->BottomPt = 0; result->PolyNd = 0; m_PolyOuts.push_back(result); result->Idx = (int)m_PolyOuts.size()-1; return result; } //------------------------------------------------------------------------------ OutPt* Clipper::AddOutPt(TEdge *e, const IntPoint &pt) { if( e->OutIdx < 0 ) { OutRec *outRec = CreateOutRec(); outRec->IsOpen = (e->WindDelta == 0); OutPt* newOp = this->AllocateOutPt(); outRec->Pts = newOp; newOp->Idx = outRec->Idx; newOp->Pt = pt; newOp->Next = newOp; newOp->Prev = newOp; if (!outRec->IsOpen) SetHoleState(e, outRec); e->OutIdx = outRec->Idx; return newOp; } else { OutRec *outRec = m_PolyOuts[e->OutIdx]; //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' OutPt* op = outRec->Pts; bool ToFront = (e->Side == esLeft); if (ToFront && (pt == op->Pt)) return op; else if (!ToFront && (pt == op->Prev->Pt)) return op->Prev; OutPt* newOp = this->AllocateOutPt(); newOp->Idx = outRec->Idx; newOp->Pt = pt; newOp->Next = op; newOp->Prev = op->Prev; newOp->Prev->Next = newOp; op->Prev = newOp; if (ToFront) outRec->Pts = newOp; return newOp; } } //------------------------------------------------------------------------------ OutPt* Clipper::GetLastOutPt(TEdge *e) { OutRec *outRec = m_PolyOuts[e->OutIdx]; if (e->Side == esLeft) return outRec->Pts; else return outRec->Pts->Prev; } //------------------------------------------------------------------------------ void Clipper::ProcessHorizontals() { PROFILE_FUNC(); TEdge* horzEdge = m_SortedEdges; while(horzEdge) { DeleteFromSEL(horzEdge); ProcessHorizontal(horzEdge); horzEdge = m_SortedEdges; } } //------------------------------------------------------------------------------ inline bool IsMaxima(TEdge *e, const cInt Y) { return e && e->Top.Y == Y && !e->NextInLML; } //------------------------------------------------------------------------------ inline bool IsIntermediate(TEdge *e, const cInt Y) { return e->Top.Y == Y && e->NextInLML; } //------------------------------------------------------------------------------ inline TEdge *GetMaximaPair(TEdge *e) { TEdge* result = 0; if ((e->Next->Top == e->Top) && !e->Next->NextInLML) result = e->Next; else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) result = e->Prev; if (result && (result->OutIdx == Skip || //result is false if both NextInAEL & PrevInAEL are nil & not horizontal ... (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) return 0; return result; } //------------------------------------------------------------------------------ void Clipper::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) { //check that one or other edge hasn't already been removed from AEL ... if (Edge1->NextInAEL == Edge1->PrevInAEL || Edge2->NextInAEL == Edge2->PrevInAEL) return; if( Edge1->NextInAEL == Edge2 ) { TEdge* Next = Edge2->NextInAEL; if( Next ) Next->PrevInAEL = Edge1; TEdge* Prev = Edge1->PrevInAEL; if( Prev ) Prev->NextInAEL = Edge2; Edge2->PrevInAEL = Prev; Edge2->NextInAEL = Edge1; Edge1->PrevInAEL = Edge2; Edge1->NextInAEL = Next; } else if( Edge2->NextInAEL == Edge1 ) { TEdge* Next = Edge1->NextInAEL; if( Next ) Next->PrevInAEL = Edge2; TEdge* Prev = Edge2->PrevInAEL; if( Prev ) Prev->NextInAEL = Edge1; Edge1->PrevInAEL = Prev; Edge1->NextInAEL = Edge2; Edge2->PrevInAEL = Edge1; Edge2->NextInAEL = Next; } else { TEdge* Next = Edge1->NextInAEL; TEdge* Prev = Edge1->PrevInAEL; Edge1->NextInAEL = Edge2->NextInAEL; if( Edge1->NextInAEL ) Edge1->NextInAEL->PrevInAEL = Edge1; Edge1->PrevInAEL = Edge2->PrevInAEL; if( Edge1->PrevInAEL ) Edge1->PrevInAEL->NextInAEL = Edge1; Edge2->NextInAEL = Next; if( Edge2->NextInAEL ) Edge2->NextInAEL->PrevInAEL = Edge2; Edge2->PrevInAEL = Prev; if( Edge2->PrevInAEL ) Edge2->PrevInAEL->NextInAEL = Edge2; } if( !Edge1->PrevInAEL ) m_ActiveEdges = Edge1; else if( !Edge2->PrevInAEL ) m_ActiveEdges = Edge2; } //------------------------------------------------------------------------------ void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) { if( !( Edge1->NextInSEL ) && !( Edge1->PrevInSEL ) ) return; if( !( Edge2->NextInSEL ) && !( Edge2->PrevInSEL ) ) return; if( Edge1->NextInSEL == Edge2 ) { TEdge* Next = Edge2->NextInSEL; if( Next ) Next->PrevInSEL = Edge1; TEdge* Prev = Edge1->PrevInSEL; if( Prev ) Prev->NextInSEL = Edge2; Edge2->PrevInSEL = Prev; Edge2->NextInSEL = Edge1; Edge1->PrevInSEL = Edge2; Edge1->NextInSEL = Next; } else if( Edge2->NextInSEL == Edge1 ) { TEdge* Next = Edge1->NextInSEL; if( Next ) Next->PrevInSEL = Edge2; TEdge* Prev = Edge2->PrevInSEL; if( Prev ) Prev->NextInSEL = Edge1; Edge1->PrevInSEL = Prev; Edge1->NextInSEL = Edge2; Edge2->PrevInSEL = Edge1; Edge2->NextInSEL = Next; } else { TEdge* Next = Edge1->NextInSEL; TEdge* Prev = Edge1->PrevInSEL; Edge1->NextInSEL = Edge2->NextInSEL; if( Edge1->NextInSEL ) Edge1->NextInSEL->PrevInSEL = Edge1; Edge1->PrevInSEL = Edge2->PrevInSEL; if( Edge1->PrevInSEL ) Edge1->PrevInSEL->NextInSEL = Edge1; Edge2->NextInSEL = Next; if( Edge2->NextInSEL ) Edge2->NextInSEL->PrevInSEL = Edge2; Edge2->PrevInSEL = Prev; if( Edge2->PrevInSEL ) Edge2->PrevInSEL->NextInSEL = Edge2; } if( !Edge1->PrevInSEL ) m_SortedEdges = Edge1; else if( !Edge2->PrevInSEL ) m_SortedEdges = Edge2; } //------------------------------------------------------------------------------ inline void GetHorzDirection(TEdge& HorzEdge, Direction& Dir, cInt& Left, cInt& Right) { if (HorzEdge.Bot.X < HorzEdge.Top.X) { Left = HorzEdge.Bot.X; Right = HorzEdge.Top.X; Dir = dLeftToRight; } else { Left = HorzEdge.Top.X; Right = HorzEdge.Bot.X; Dir = dRightToLeft; } } //------------------------------------------------------------------------ /******************************************************************************* * Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * * Bottom of a scanbeam) are processed as if layered. The order in which HEs * * are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * * (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * * and with other non-horizontal edges [*]. Once these intersections are * * processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * * the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * *******************************************************************************/ void Clipper::ProcessHorizontal(TEdge *horzEdge) { Direction dir; cInt horzLeft, horzRight; bool IsOpen = (horzEdge->OutIdx >= 0 && m_PolyOuts[horzEdge->OutIdx]->IsOpen); GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); TEdge* eLastHorz = horzEdge, *eMaxPair = 0; while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) eLastHorz = eLastHorz->NextInLML; if (!eLastHorz->NextInLML) eMaxPair = GetMaximaPair(eLastHorz); std::vector::const_iterator maxIt; std::vector::const_reverse_iterator maxRit; if (!m_Maxima.empty()) { //get the first maxima in range (X) ... if (dir == dLeftToRight) { maxIt = m_Maxima.begin(); while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) ++maxIt; if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X) maxIt = m_Maxima.end(); } else { maxRit = m_Maxima.rbegin(); while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) ++maxRit; if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X) maxRit = m_Maxima.rend(); } } OutPt* op1 = 0; for (;;) //loop through consec. horizontal edges { bool IsLastHorz = (horzEdge == eLastHorz); TEdge* e = (dir == dLeftToRight) ? horzEdge->NextInAEL : horzEdge->PrevInAEL; while(e) { //this code block inserts extra coords into horizontal edges (in output //polygons) whereever maxima touch these horizontal edges. This helps //'simplifying' polygons (ie if the Simplify property is set). if (!m_Maxima.empty()) { if (dir == dLeftToRight) { while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) { if (horzEdge->OutIdx >= 0 && !IsOpen) AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y)); ++maxIt; } } else { while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) { if (horzEdge->OutIdx >= 0 && !IsOpen) AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y)); ++maxRit; } } }; if ((dir == dLeftToRight && e->Curr.X > horzRight) || (dir == dRightToLeft && e->Curr.X < horzLeft)) break; //Also break if we've got to the end of an intermediate horizontal edge ... //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && e->Dx < horzEdge->NextInLML->Dx) break; if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times { op1 = AddOutPt(horzEdge, e->Curr); TEdge* eNextHorz = m_SortedEdges; while (eNextHorz) { if (eNextHorz->OutIdx >= 0 && HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) { OutPt* op2 = GetLastOutPt(eNextHorz); m_Joins.emplace_back(Join(op2, op1, eNextHorz->Top)); } eNextHorz = eNextHorz->NextInSEL; } m_GhostJoins.emplace_back(Join(op1, 0, horzEdge->Bot)); } //OK, so far we're still in range of the horizontal Edge but make sure //we're at the last of consec. horizontals when matching with eMaxPair if(e == eMaxPair && IsLastHorz) { if (horzEdge->OutIdx >= 0) AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top); DeleteFromAEL(horzEdge); DeleteFromAEL(eMaxPair); return; } if(dir == dLeftToRight) { IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); IntersectEdges(horzEdge, e, Pt); } else { IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); IntersectEdges( e, horzEdge, Pt); } TEdge* eNext = (dir == dLeftToRight) ? e->NextInAEL : e->PrevInAEL; SwapPositionsInAEL( horzEdge, e ); e = eNext; } //end while(e) //Break out of loop if HorzEdge.NextInLML is not also horizontal ... if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) break; UpdateEdgeIntoAEL(horzEdge); if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Bot); GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); } //end for (;;) if (horzEdge->OutIdx >= 0 && !op1) { op1 = GetLastOutPt(horzEdge); TEdge* eNextHorz = m_SortedEdges; while (eNextHorz) { if (eNextHorz->OutIdx >= 0 && HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X, eNextHorz->Bot.X, eNextHorz->Top.X)) { OutPt* op2 = GetLastOutPt(eNextHorz); m_Joins.emplace_back(Join(op2, op1, eNextHorz->Top)); } eNextHorz = eNextHorz->NextInSEL; } m_GhostJoins.emplace_back(Join(op1, 0, horzEdge->Top)); } if (horzEdge->NextInLML) { if(horzEdge->OutIdx >= 0) { op1 = AddOutPt( horzEdge, horzEdge->Top); UpdateEdgeIntoAEL(horzEdge); if (horzEdge->WindDelta == 0) return; //nb: HorzEdge is no longer horizontal here TEdge* ePrev = horzEdge->PrevInAEL; TEdge* eNext = horzEdge->NextInAEL; if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) { OutPt* op2 = AddOutPt(ePrev, horzEdge->Bot); m_Joins.emplace_back(Join(op1, op2, horzEdge->Top)); } else if (eNext && eNext->Curr.X == horzEdge->Bot.X && eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) { OutPt* op2 = AddOutPt(eNext, horzEdge->Bot); m_Joins.emplace_back(Join(op1, op2, horzEdge->Top)); } } else UpdateEdgeIntoAEL(horzEdge); } else { if (horzEdge->OutIdx >= 0) AddOutPt(horzEdge, horzEdge->Top); DeleteFromAEL(horzEdge); } } //------------------------------------------------------------------------------ void Clipper::UpdateEdgeIntoAEL(TEdge *&e) { if( !e->NextInLML ) throw clipperException("UpdateEdgeIntoAEL: invalid call"); e->NextInLML->OutIdx = e->OutIdx; TEdge* AelPrev = e->PrevInAEL; TEdge* AelNext = e->NextInAEL; if (AelPrev) AelPrev->NextInAEL = e->NextInLML; else m_ActiveEdges = e->NextInLML; if (AelNext) AelNext->PrevInAEL = e->NextInLML; e->NextInLML->Side = e->Side; e->NextInLML->WindDelta = e->WindDelta; e->NextInLML->WindCnt = e->WindCnt; e->NextInLML->WindCnt2 = e->WindCnt2; e = e->NextInLML; e->Curr = e->Bot; e->PrevInAEL = AelPrev; e->NextInAEL = AelNext; if (!IsHorizontal(*e)) m_Scanbeam.push(e->Top.Y); } //------------------------------------------------------------------------------ bool Clipper::ProcessIntersections(const cInt topY) { PROFILE_FUNC(); if( !m_ActiveEdges ) return true; try { BuildIntersectList(topY); size_t IlSize = m_IntersectList.size(); if (IlSize == 0) return true; if (IlSize == 1 || FixupIntersectionOrder()) { for (IntersectNode &iNode : m_IntersectList) { IntersectEdges( iNode.Edge1, iNode.Edge2, iNode.Pt); SwapPositionsInAEL( iNode.Edge1 , iNode.Edge2 ); } m_IntersectList.clear(); } else return false; } catch(...) { m_SortedEdges = 0; m_IntersectList.clear(); throw clipperException("ProcessIntersections error"); } m_SortedEdges = 0; return true; } //------------------------------------------------------------------------------ void Clipper::BuildIntersectList(const cInt topY) { if ( !m_ActiveEdges ) return; //prepare for sorting ... TEdge* e = m_ActiveEdges; m_SortedEdges = e; while( e ) { e->PrevInSEL = e->PrevInAEL; e->NextInSEL = e->NextInAEL; e->Curr.X = TopX( *e, topY ); e = e->NextInAEL; } //bubblesort ... bool isModified; do { isModified = false; e = m_SortedEdges; while( e->NextInSEL ) { TEdge *eNext = e->NextInSEL; IntPoint Pt; if(e->Curr.X > eNext->Curr.X) { IntersectPoint(*e, *eNext, Pt); m_IntersectList.emplace_back(IntersectNode(e, eNext, Pt)); SwapPositionsInSEL(e, eNext); isModified = true; } else e = eNext; } if( e->PrevInSEL ) e->PrevInSEL->NextInSEL = 0; else break; } while ( isModified ); m_SortedEdges = 0; //important } //------------------------------------------------------------------------------ inline bool EdgesAdjacent(const IntersectNode &inode) { return (inode.Edge1->NextInSEL == inode.Edge2) || (inode.Edge1->PrevInSEL == inode.Edge2); } //------------------------------------------------------------------------------ bool Clipper::FixupIntersectionOrder() { //pre-condition: intersections are sorted Bottom-most first. //Now it's crucial that intersections are made only between adjacent edges, //so to ensure this the order of intersections may need adjusting ... CopyAELToSEL(); std::sort(m_IntersectList.begin(), m_IntersectList.end(), [](const IntersectNode &node1, const IntersectNode &node2) { return node2.Pt.Y < node1.Pt.Y; }); size_t cnt = m_IntersectList.size(); for (size_t i = 0; i < cnt; ++i) { if (!EdgesAdjacent(m_IntersectList[i])) { size_t j = i + 1; while (j < cnt && !EdgesAdjacent(m_IntersectList[j])) j++; if (j == cnt) return false; std::swap(m_IntersectList[i], m_IntersectList[j]); } SwapPositionsInSEL(m_IntersectList[i].Edge1, m_IntersectList[i].Edge2); } return true; } //------------------------------------------------------------------------------ void Clipper::DoMaxima(TEdge *e) { TEdge* eMaxPair = GetMaximaPair(e); if (!eMaxPair) { if (e->OutIdx >= 0) AddOutPt(e, e->Top); DeleteFromAEL(e); return; } TEdge* eNext = e->NextInAEL; while(eNext && eNext != eMaxPair) { IntersectEdges(e, eNext, e->Top); SwapPositionsInAEL(e, eNext); eNext = e->NextInAEL; } if(e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) { DeleteFromAEL(e); DeleteFromAEL(eMaxPair); } else if( e->OutIdx >= 0 && eMaxPair->OutIdx >= 0 ) { if (e->OutIdx >= 0) AddLocalMaxPoly(e, eMaxPair, e->Top); DeleteFromAEL(e); DeleteFromAEL(eMaxPair); } #ifdef use_lines else if (e->WindDelta == 0) { if (e->OutIdx >= 0) { AddOutPt(e, e->Top); e->OutIdx = Unassigned; } DeleteFromAEL(e); if (eMaxPair->OutIdx >= 0) { AddOutPt(eMaxPair, e->Top); eMaxPair->OutIdx = Unassigned; } DeleteFromAEL(eMaxPair); } #endif else throw clipperException("DoMaxima error"); } //------------------------------------------------------------------------------ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) { PROFILE_FUNC(); TEdge* e = m_ActiveEdges; while( e ) { //1. process maxima, treating them as if they're 'bent' horizontal edges, // but exclude maxima with horizontal edges. nb: e can't be a horizontal. bool IsMaximaEdge = IsMaxima(e, topY); if(IsMaximaEdge) { TEdge* eMaxPair = GetMaximaPair(e); IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); } if(IsMaximaEdge) { if (m_StrictSimple) m_Maxima.push_back(e->Top.X); TEdge* ePrev = e->PrevInAEL; DoMaxima(e); if( !ePrev ) e = m_ActiveEdges; else e = ePrev->NextInAEL; } else { //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) { UpdateEdgeIntoAEL(e); if (e->OutIdx >= 0) AddOutPt(e, e->Bot); AddEdgeToSEL(e); } else { e->Curr.X = TopX( *e, topY ); e->Curr.Y = topY; } //When StrictlySimple and 'e' is being touched by another edge, then //make sure both edges have a vertex here ... if (m_StrictSimple) { TEdge* ePrev = e->PrevInAEL; if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && (ePrev->OutIdx >= 0) && (ePrev->Curr.X == e->Curr.X) && (ePrev->WindDelta != 0)) { IntPoint pt = e->Curr; #ifdef use_xyz SetZ(pt, *ePrev, *e); #endif OutPt* op = AddOutPt(ePrev, pt); OutPt* op2 = AddOutPt(e, pt); m_Joins.emplace_back(Join(op, op2, pt)); //StrictlySimple (type-3) join } } e = e->NextInAEL; } } //3. Process horizontals at the Top of the scanbeam ... std::sort(m_Maxima.begin(), m_Maxima.end()); ProcessHorizontals(); m_Maxima.clear(); //4. Promote intermediate vertices ... e = m_ActiveEdges; while(e) { if(IsIntermediate(e, topY)) { OutPt* op = 0; if( e->OutIdx >= 0 ) op = AddOutPt(e, e->Top); UpdateEdgeIntoAEL(e); //if output polygons share an edge, they'll need joining later ... TEdge* ePrev = e->PrevInAEL; TEdge* eNext = e->NextInAEL; if (ePrev && ePrev->Curr.X == e->Bot.X && ePrev->Curr.Y == e->Bot.Y && op && ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && SlopesEqual(*e, *ePrev, m_UseFullRange) && (e->WindDelta != 0) && (ePrev->WindDelta != 0)) { OutPt* op2 = AddOutPt(ePrev, e->Bot); m_Joins.emplace_back(Join(op, op2, e->Top)); } else if (eNext && eNext->Curr.X == e->Bot.X && eNext->Curr.Y == e->Bot.Y && op && eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && SlopesEqual(*e, *eNext, m_UseFullRange) && (e->WindDelta != 0) && (eNext->WindDelta != 0)) { OutPt* op2 = AddOutPt(eNext, e->Bot); m_Joins.emplace_back(Join(op, op2, e->Top)); } } e = e->NextInAEL; } } //------------------------------------------------------------------------------ void Clipper::FixupOutPolyline(OutRec &outrec) { OutPt *pp = outrec.Pts; OutPt *lastPP = pp->Prev; while (pp != lastPP) { pp = pp->Next; if (pp->Pt == pp->Prev->Pt) { if (pp == lastPP) lastPP = pp->Prev; OutPt *tmpPP = pp->Prev; tmpPP->Next = pp->Next; pp->Next->Prev = tmpPP; this->DisposeOutPt(pp); pp = tmpPP; } } if (pp == pp->Prev) { this->DisposeOutPts(pp); outrec.Pts = 0; return; } } //------------------------------------------------------------------------------ void Clipper::FixupOutPolygon(OutRec &outrec) { //FixupOutPolygon() - removes duplicate points and simplifies consecutive //parallel edges by removing the middle vertex. OutPt *lastOK = nullptr; outrec.BottomPt = nullptr; OutPt *pp = outrec.Pts; bool preserveCol = m_PreserveCollinear || m_StrictSimple; for (;;) { if (pp->Prev == pp || pp->Prev == pp->Next) { // Empty loop or a stick. Release the polygon. this->DisposeOutPts(pp); outrec.Pts = nullptr; return; } //test for duplicate points and collinear edges ... if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && (!preserveCol || !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) { lastOK = nullptr; OutPt *tmp = pp; pp->Prev->Next = pp->Next; pp->Next->Prev = pp->Prev; pp = pp->Prev; this->DisposeOutPt(tmp); } else if (pp == lastOK) break; else { if (!lastOK) lastOK = pp; pp = pp->Next; } } outrec.Pts = pp; } //------------------------------------------------------------------------------ // Count the number of points in a closed linked loop starting with Pts. int PointCount(OutPt *Pts) { if (!Pts) return 0; int result = 0; OutPt* p = Pts; do { result++; p = p->Next; } while (p != Pts); return result; } //------------------------------------------------------------------------------ void Clipper::BuildResult(Paths &polys) { polys.reserve(m_PolyOuts.size()); for (OutRec* outRec : m_PolyOuts) { assert(! outRec->IsOpen); if (!outRec->Pts) continue; Path pg; OutPt* p = outRec->Pts->Prev; int cnt = PointCount(p); if (cnt < 2) continue; pg.reserve(cnt); for (int i = 0; i < cnt; ++i) { pg.emplace_back(p->Pt); p = p->Prev; } polys.emplace_back(std::move(pg)); } } //------------------------------------------------------------------------------ void Clipper::BuildResult2(PolyTree& polytree) { polytree.Clear(); polytree.AllNodes.reserve(m_PolyOuts.size()); //add each output polygon/contour to polytree ... for (OutRec* outRec : m_PolyOuts) { int cnt = PointCount(outRec->Pts); if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) // Ignore an invalid output loop or a polyline. continue; //skip OutRecs that (a) contain outermost polygons or //(b) already have the correct owner/child linkage ... if (outRec->FirstLeft && (outRec->IsHole == outRec->FirstLeft->IsHole || ! outRec->FirstLeft->Pts)) { OutRec* orfl = outRec->FirstLeft; while (orfl && ((orfl->IsHole == outRec->IsHole) || !orfl->Pts)) orfl = orfl->FirstLeft; outRec->FirstLeft = orfl; } //nb: polytree takes ownership of all the PolyNodes polytree.AllNodes.emplace_back(PolyNode()); PolyNode* pn = &polytree.AllNodes.back(); outRec->PolyNd = pn; pn->Parent = 0; pn->Index = 0; pn->Contour.reserve(cnt); OutPt *op = outRec->Pts->Prev; for (int j = 0; j < cnt; j++) { pn->Contour.emplace_back(op->Pt); op = op->Prev; } } //fixup PolyNode links etc ... polytree.Childs.reserve(m_PolyOuts.size()); for (OutRec* outRec : m_PolyOuts) { if (!outRec->PolyNd) continue; if (outRec->IsOpen) { outRec->PolyNd->m_IsOpen = true; polytree.AddChild(*outRec->PolyNd); } else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); else polytree.AddChild(*outRec->PolyNd); } } //------------------------------------------------------------------------------ inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) { if (e2.Curr.X == e1.Curr.X) { if (e2.Top.Y > e1.Top.Y) return e2.Top.X < TopX(e1, e2.Top.Y); else return e1.Top.X > TopX(e2, e1.Top.Y); } else return e2.Curr.X < e1.Curr.X; } //------------------------------------------------------------------------------ bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, cInt& Left, cInt& Right) { if (a1 < a2) { if (b1 < b2) {Left = std::max(a1,b1); Right = std::min(a2,b2);} else {Left = std::max(a1,b2); Right = std::min(a2,b1);} } else { if (b1 < b2) {Left = std::max(a2,b1); Right = std::min(a1,b2);} else {Left = std::max(a2,b2); Right = std::min(a1,b1);} } return Left < Right; } //------------------------------------------------------------------------------ // Make all points of outrec point to outrec.Idx inline void UpdateOutPtIdxs(OutRec& outrec) { OutPt* op = outrec.Pts; do { op->Idx = outrec.Idx; op = op->Prev; } while(op != outrec.Pts); } //------------------------------------------------------------------------------ void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge) { if(!m_ActiveEdges) { edge->PrevInAEL = 0; edge->NextInAEL = 0; m_ActiveEdges = edge; } else if(!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) { edge->PrevInAEL = 0; edge->NextInAEL = m_ActiveEdges; m_ActiveEdges->PrevInAEL = edge; m_ActiveEdges = edge; } else { if(!startEdge) startEdge = m_ActiveEdges; while(startEdge->NextInAEL && !E2InsertsBeforeE1(*startEdge->NextInAEL , *edge)) startEdge = startEdge->NextInAEL; edge->NextInAEL = startEdge->NextInAEL; if(startEdge->NextInAEL) startEdge->NextInAEL->PrevInAEL = edge; edge->PrevInAEL = startEdge; startEdge->NextInAEL = edge; } } //---------------------------------------------------------------------- OutPt* Clipper::DupOutPt(OutPt* outPt, bool InsertAfter) { OutPt* result = this->AllocateOutPt(); result->Pt = outPt->Pt; result->Idx = outPt->Idx; if (InsertAfter) { result->Next = outPt->Next; result->Prev = outPt; outPt->Next->Prev = result; outPt->Next = result; } else { result->Prev = outPt->Prev; result->Next = outPt; outPt->Prev->Next = result; outPt->Prev = result; } return result; } //------------------------------------------------------------------------------ bool Clipper::JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, const IntPoint &Pt, bool DiscardLeft) { Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); if (Dir1 == Dir2) return false; //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we //want Op1b to be on the Right. (And likewise with Op2 and Op2b.) //So, to facilitate this while inserting Op1b and Op2b ... //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) if (Dir1 == dLeftToRight) { while (op1->Next->Pt.X <= Pt.X && op1->Next->Pt.X >= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) op1 = op1->Next; if (DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; op1b = this->DupOutPt(op1, !DiscardLeft); if (op1b->Pt != Pt) { op1 = op1b; op1->Pt = Pt; op1b = this->DupOutPt(op1, !DiscardLeft); } } else { while (op1->Next->Pt.X >= Pt.X && op1->Next->Pt.X <= op1->Pt.X && op1->Next->Pt.Y == Pt.Y) op1 = op1->Next; if (!DiscardLeft && (op1->Pt.X != Pt.X)) op1 = op1->Next; op1b = this->DupOutPt(op1, DiscardLeft); if (op1b->Pt != Pt) { op1 = op1b; op1->Pt = Pt; op1b = this->DupOutPt(op1, DiscardLeft); } } if (Dir2 == dLeftToRight) { while (op2->Next->Pt.X <= Pt.X && op2->Next->Pt.X >= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) op2 = op2->Next; if (DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; op2b = this->DupOutPt(op2, !DiscardLeft); if (op2b->Pt != Pt) { op2 = op2b; op2->Pt = Pt; op2b = this->DupOutPt(op2, !DiscardLeft); }; } else { while (op2->Next->Pt.X >= Pt.X && op2->Next->Pt.X <= op2->Pt.X && op2->Next->Pt.Y == Pt.Y) op2 = op2->Next; if (!DiscardLeft && (op2->Pt.X != Pt.X)) op2 = op2->Next; op2b = this->DupOutPt(op2, DiscardLeft); if (op2b->Pt != Pt) { op2 = op2b; op2->Pt = Pt; op2b = this->DupOutPt(op2, DiscardLeft); }; }; if ((Dir1 == dLeftToRight) == DiscardLeft) { op1->Prev = op2; op2->Next = op1; op1b->Next = op2b; op2b->Prev = op1b; } else { op1->Next = op2; op2->Prev = op1; op1b->Prev = op2b; op2b->Next = op1b; } return true; } //------------------------------------------------------------------------------ bool Clipper::JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2) { OutPt *op1 = j->OutPt1, *op1b; OutPt *op2 = j->OutPt2, *op2b; //There are 3 kinds of joins for output polygons ... //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal). //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same //location at the Bottom of the overlapping segment (& Join.OffPt is above). //3. StrictSimple joins where edges touch but are not collinear and where //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && (j->OffPt == j->OutPt2->Pt)) { //Strictly Simple join ... if (outRec1 != outRec2) return false; op1b = j->OutPt1->Next; while (op1b != op1 && (op1b->Pt == j->OffPt)) op1b = op1b->Next; bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); op2b = j->OutPt2->Next; while (op2b != op2 && (op2b->Pt == j->OffPt)) op2b = op2b->Next; bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); if (reverse1 == reverse2) return false; if (reverse1) { op1b = this->DupOutPt(op1, false); op2b = this->DupOutPt(op2, true); op1->Prev = op2; op2->Next = op1; op1b->Next = op2b; op2b->Prev = op1b; j->OutPt1 = op1; j->OutPt2 = op1b; return true; } else { op1b = this->DupOutPt(op1, true); op2b = this->DupOutPt(op2, false); op1->Next = op2; op2->Prev = op1; op1b->Prev = op2b; op2b->Next = op1b; j->OutPt1 = op1; j->OutPt2 = op1b; return true; } } else if (isHorizontal) { //treat horizontal joins differently to non-horizontal joins since with //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt //may be anywhere along the horizontal edge. op1b = op1; while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && op1->Prev != op2) op1 = op1->Prev; while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && op1b->Next != op2) op1b = op1b->Next; if (op1b->Next == op1 || op1b->Next == op2) return false; //a flat 'polygon' op2b = op2; while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && op2->Prev != op1b) op2 = op2->Prev; while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && op2b->Next != op1) op2b = op2b->Next; if (op2b->Next == op2 || op2b->Next == op1) return false; //a flat 'polygon' cInt Left, Right; //Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) return false; //DiscardLeftSide: when overlapping edges are joined, a spike will created //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up //on the discard Side as either may still be needed for other joins ... IntPoint Pt; bool DiscardLeftSide; if (op1->Pt.X >= Left && op1->Pt.X <= Right) { Pt = op1->Pt; DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); } else if (op2->Pt.X >= Left&& op2->Pt.X <= Right) { Pt = op2->Pt; DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); } else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) { Pt = op1b->Pt; DiscardLeftSide = op1b->Pt.X > op1->Pt.X; } else { Pt = op2b->Pt; DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); } j->OutPt1 = op1; j->OutPt2 = op2; return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); } else { //nb: For non-horizontal joins ... // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y // 2. Jr.OutPt1.Pt > Jr.OffPt.Y //make sure the polygons are correctly oriented ... op1b = op1->Next; while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Next; bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); if (Reverse1) { op1b = op1->Prev; while ((op1b->Pt == op1->Pt) && (op1b != op1)) op1b = op1b->Prev; if ((op1b->Pt.Y > op1->Pt.Y) || !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) return false; }; op2b = op2->Next; while ((op2b->Pt == op2->Pt) && (op2b != op2))op2b = op2b->Next; bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); if (Reverse2) { op2b = op2->Prev; while ((op2b->Pt == op2->Pt) && (op2b != op2)) op2b = op2b->Prev; if ((op2b->Pt.Y > op2->Pt.Y) || !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) return false; } if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || ((outRec1 == outRec2) && (Reverse1 == Reverse2))) return false; if (Reverse1) { op1b = this->DupOutPt(op1, false); op2b = this->DupOutPt(op2, true); op1->Prev = op2; op2->Next = op1; op1b->Next = op2b; op2b->Prev = op1b; j->OutPt1 = op1; j->OutPt2 = op1b; return true; } else { op1b = this->DupOutPt(op1, true); op2b = this->DupOutPt(op2, false); op1->Next = op2; op2->Prev = op1; op1b->Prev = op2b; op2b->Next = op1b; j->OutPt1 = op1; j->OutPt2 = op1b; return true; } } } //---------------------------------------------------------------------- // This is potentially very expensive! O(n^3)! void Clipper::FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const { PROFILE_FUNC(); //tests if NewOutRec contains the polygon before reassigning FirstLeft for (OutRec *outRec : m_PolyOuts) { if (!outRec->Pts || !outRec->FirstLeft) continue; OutRec* firstLeft = outRec->FirstLeft; // Skip empty polygons. while (firstLeft && !firstLeft->Pts) firstLeft = firstLeft->FirstLeft; if (firstLeft == OldOutRec && Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) outRec->FirstLeft = NewOutRec; } } //---------------------------------------------------------------------- void Clipper::FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const { //reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon for (OutRec *outRec : m_PolyOuts) if (outRec->FirstLeft == OldOutRec) outRec->FirstLeft = NewOutRec; } //---------------------------------------------------------------------- void Clipper::JoinCommonEdges() { PROFILE_FUNC(); for (Join &join : m_Joins) { OutRec *outRec1 = GetOutRec(join.OutPt1->Idx); OutRec *outRec2 = GetOutRec(join.OutPt2->Idx); if (!outRec1->Pts || !outRec2->Pts) continue; if (outRec1->IsOpen || outRec2->IsOpen) continue; //get the polygon fragment with the correct hole state (FirstLeft) //before calling JoinPoints() ... OutRec *holeStateRec; if (outRec1 == outRec2) holeStateRec = outRec1; else if (Param1RightOfParam2(outRec1, outRec2)) holeStateRec = outRec2; else if (Param1RightOfParam2(outRec2, outRec1)) holeStateRec = outRec1; else holeStateRec = GetLowermostRec(outRec1, outRec2); if (!JoinPoints(&join, outRec1, outRec2)) continue; if (outRec1 == outRec2) { //instead of joining two polygons, we've just created a new one by //splitting one polygon into two. outRec1->Pts = join.OutPt1; outRec1->BottomPt = 0; outRec2 = CreateOutRec(); outRec2->Pts = join.OutPt2; //update all OutRec2.Pts Idx's ... UpdateOutPtIdxs(*outRec2); //We now need to check every OutRec.FirstLeft pointer. If it points //to OutRec1 it may need to point to OutRec2 instead ... if (m_UsingPolyTree) for (size_t j = 0; j < m_PolyOuts.size() - 1; j++) { OutRec* oRec = m_PolyOuts[j]; OutRec* firstLeft = oRec->FirstLeft; while (firstLeft && !firstLeft->Pts) firstLeft = firstLeft->FirstLeft; if (!oRec->Pts || firstLeft != outRec1 || oRec->IsHole == outRec1->IsHole) continue; if (Poly2ContainsPoly1(oRec->Pts, join.OutPt2)) oRec->FirstLeft = outRec2; } if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) { //outRec2 is contained by outRec1 ... outRec2->IsHole = !outRec1->IsHole; outRec2->FirstLeft = outRec1; // For each m_PolyOuts, replace FirstLeft from outRec2 to outRec1. if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) ReversePolyPtLinks(outRec2->Pts); } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) { //outRec1 is contained by outRec2 ... outRec2->IsHole = outRec1->IsHole; outRec1->IsHole = !outRec2->IsHole; outRec2->FirstLeft = outRec1->FirstLeft; outRec1->FirstLeft = outRec2; // For each m_PolyOuts, replace FirstLeft from outRec1 to outRec2. if (m_UsingPolyTree) FixupFirstLefts2(outRec1, outRec2); if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) ReversePolyPtLinks(outRec1->Pts); } else { //the 2 polygons are completely separate ... outRec2->IsHole = outRec1->IsHole; outRec2->FirstLeft = outRec1->FirstLeft; //fixup FirstLeft pointers that may need reassigning to OutRec2 // For each polygon of m_PolyOuts, replace FirstLeft from outRec1 to outRec2 if the polygon is inside outRec2. //FIXME This is potentially very expensive! O(n^3)! if (m_UsingPolyTree) FixupFirstLefts1(outRec1, outRec2); } } else { //joined 2 polygons together ... outRec2->Pts = 0; outRec2->BottomPt = 0; outRec2->Idx = outRec1->Idx; outRec1->IsHole = holeStateRec->IsHole; if (holeStateRec == outRec2) outRec1->FirstLeft = outRec2->FirstLeft; outRec2->FirstLeft = outRec1; // For each m_PolyOuts, replace FirstLeft from outRec2 to outRec1. if (m_UsingPolyTree) FixupFirstLefts2(outRec2, outRec1); } } } //------------------------------------------------------------------------------ // ClipperOffset support functions ... //------------------------------------------------------------------------------ DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) { if(pt2.X == pt1.X && pt2.Y == pt1.Y) return DoublePoint(0, 0); double Dx = (double)(pt2.X - pt1.X); double dy = (double)(pt2.Y - pt1.Y); double f = 1 *1.0/ std::sqrt( Dx*Dx + dy*dy ); Dx *= f; dy *= f; return DoublePoint(dy, -Dx); } //------------------------------------------------------------------------------ // ClipperOffset class //------------------------------------------------------------------------------ void ClipperOffset::Clear() { for (int i = 0; i < m_polyNodes.ChildCount(); ++i) delete m_polyNodes.Childs[i]; m_polyNodes.Childs.clear(); m_lowest.X = -1; } //------------------------------------------------------------------------------ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType) { int highI = (int)path.size() - 1; if (highI < 0) return; PolyNode* newNode = new PolyNode(); newNode->m_jointype = joinType; newNode->m_endtype = endType; //strip duplicate points from path and also get index to the lowest point ... bool has_shortest_edge_length = ShortestEdgeLength > 0.; double shortest_edge_length2 = has_shortest_edge_length ? ShortestEdgeLength * ShortestEdgeLength : 0.; if (endType == etClosedLine || endType == etClosedPolygon) for (; highI > 0; -- highI) { bool same = false; if (has_shortest_edge_length) { double dx = double(path[highI].X - path[0].X); double dy = double(path[highI].Y - path[0].Y); same = dx*dx + dy*dy < shortest_edge_length2; } else same = path[0] == path[highI]; if (! same) break; } newNode->Contour.reserve(highI + 1); newNode->Contour.push_back(path[0]); int j = 0, k = 0; for (int i = 1; i <= highI; i++) { bool same = false; if (has_shortest_edge_length) { double dx = double(path[i].X - newNode->Contour[j].X); double dy = double(path[i].Y - newNode->Contour[j].Y); same = dx*dx + dy*dy < shortest_edge_length2; } else same = newNode->Contour[j] == path[i]; if (same) continue; j++; newNode->Contour.push_back(path[i]); if (path[i].Y > newNode->Contour[k].Y || (path[i].Y == newNode->Contour[k].Y && path[i].X < newNode->Contour[k].X)) k = j; } if (endType == etClosedPolygon && j < 2) { delete newNode; return; } m_polyNodes.AddChild(*newNode); //if this path's lowest pt is lower than all the others then update m_lowest if (endType != etClosedPolygon) return; if (m_lowest.X < 0) m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); else { IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; if (newNode->Contour[k].Y > ip.Y || (newNode->Contour[k].Y == ip.Y && newNode->Contour[k].X < ip.X)) m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); } } //------------------------------------------------------------------------------ void ClipperOffset::AddPaths(const Paths& paths, JoinType joinType, EndType endType) { for (const Path &path : paths) AddPath(path, joinType, endType); } //------------------------------------------------------------------------------ void ClipperOffset::FixOrientations() { //fixup orientations of all closed paths if the orientation of the //closed path with the lowermost vertex is wrong ... if (m_lowest.X >= 0 && !Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) { for (int i = 0; i < m_polyNodes.ChildCount(); ++i) { PolyNode& node = *m_polyNodes.Childs[i]; if (node.m_endtype == etClosedPolygon || (node.m_endtype == etClosedLine && Orientation(node.Contour))) ReversePath(node.Contour); } } else { for (int i = 0; i < m_polyNodes.ChildCount(); ++i) { PolyNode& node = *m_polyNodes.Childs[i]; if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) ReversePath(node.Contour); } } } //------------------------------------------------------------------------------ void ClipperOffset::Execute(Paths& solution, double delta) { solution.clear(); FixOrientations(); DoOffset(delta); //now clean up 'corners' ... Clipper clpr; clpr.AddPaths(m_destPolys, ptSubject, true); if (delta > 0) { clpr.Execute(ctUnion, solution, pftPositive, pftPositive); } else { IntRect r = clpr.GetBounds(); Path outer(4); outer[0] = IntPoint(r.left - 10, r.bottom + 10); outer[1] = IntPoint(r.right + 10, r.bottom + 10); outer[2] = IntPoint(r.right + 10, r.top - 10); outer[3] = IntPoint(r.left - 10, r.top - 10); clpr.AddPath(outer, ptSubject, true); clpr.ReverseSolution(true); clpr.Execute(ctUnion, solution, pftNegative, pftNegative); if (solution.size() > 0) solution.erase(solution.begin()); } } //------------------------------------------------------------------------------ void ClipperOffset::Execute(PolyTree& solution, double delta) { solution.Clear(); FixOrientations(); DoOffset(delta); //now clean up 'corners' ... Clipper clpr; clpr.AddPaths(m_destPolys, ptSubject, true); if (delta > 0) { clpr.Execute(ctUnion, solution, pftPositive, pftPositive); } else { IntRect r = clpr.GetBounds(); Path outer(4); outer[0] = IntPoint(r.left - 10, r.bottom + 10); outer[1] = IntPoint(r.right + 10, r.bottom + 10); outer[2] = IntPoint(r.right + 10, r.top - 10); outer[3] = IntPoint(r.left - 10, r.top - 10); clpr.AddPath(outer, ptSubject, true); clpr.ReverseSolution(true); clpr.Execute(ctUnion, solution, pftNegative, pftNegative); //remove the outer PolyNode rectangle ... if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) { PolyNode* outerNode = solution.Childs[0]; solution.Childs.reserve(outerNode->ChildCount()); solution.Childs[0] = outerNode->Childs[0]; solution.Childs[0]->Parent = outerNode->Parent; for (int i = 1; i < outerNode->ChildCount(); ++i) solution.AddChild(*outerNode->Childs[i]); } else solution.Clear(); } } //------------------------------------------------------------------------------ void ClipperOffset::DoOffset(double delta) { m_destPolys.clear(); m_delta = delta; //if Zero offset, just copy any CLOSED polygons to m_p and return ... if (NEAR_ZERO(delta)) { m_destPolys.reserve(m_polyNodes.ChildCount()); for (int i = 0; i < m_polyNodes.ChildCount(); i++) { PolyNode& node = *m_polyNodes.Childs[i]; if (node.m_endtype == etClosedPolygon) m_destPolys.push_back(node.Contour); } return; } //see offset_triginometry3.svg in the documentation folder ... if (MiterLimit > 2) m_miterLim = 2/(MiterLimit * MiterLimit); else m_miterLim = 0.5; double y; if (ArcTolerance <= 0.0) y = def_arc_tolerance; else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) y = std::fabs(delta) * def_arc_tolerance; else y = ArcTolerance; //see offset_triginometry2.svg in the documentation folder ... double steps = pi / std::acos(1 - y / std::fabs(delta)); if (steps > std::fabs(delta) * pi) steps = std::fabs(delta) * pi; //ie excessive precision check m_sin = std::sin(two_pi / steps); m_cos = std::cos(two_pi / steps); m_StepsPerRad = steps / two_pi; if (delta < 0.0) m_sin = -m_sin; m_destPolys.reserve(m_polyNodes.ChildCount() * 2); for (int i = 0; i < m_polyNodes.ChildCount(); i++) { PolyNode& node = *m_polyNodes.Childs[i]; m_srcPoly = node.Contour; int len = (int)m_srcPoly.size(); if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) continue; m_destPoly.clear(); if (len == 1) { if (node.m_jointype == jtRound) { double X = 1.0, Y = 0.0; for (cInt j = 1; j <= steps; j++) { m_destPoly.push_back(IntPoint( Round(m_srcPoly[0].X + X * delta), Round(m_srcPoly[0].Y + Y * delta))); double X2 = X; X = X * m_cos - m_sin * Y; Y = X2 * m_sin + Y * m_cos; } } else { double X = -1.0, Y = -1.0; for (int j = 0; j < 4; ++j) { m_destPoly.push_back(IntPoint( Round(m_srcPoly[0].X + X * delta), Round(m_srcPoly[0].Y + Y * delta))); if (X < 0) X = 1; else if (Y < 0) Y = 1; else X = -1; } } m_destPolys.push_back(m_destPoly); continue; } //build m_normals ... m_normals.clear(); m_normals.reserve(len); for (int j = 0; j < len - 1; ++j) m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); else m_normals.push_back(DoublePoint(m_normals[len - 2])); if (node.m_endtype == etClosedPolygon) { int k = len - 1; for (int j = 0; j < len; ++j) OffsetPoint(j, k, node.m_jointype); m_destPolys.push_back(m_destPoly); } else if (node.m_endtype == etClosedLine) { int k = len - 1; for (int j = 0; j < len; ++j) OffsetPoint(j, k, node.m_jointype); m_destPolys.push_back(m_destPoly); m_destPoly.clear(); //re-build m_normals ... DoublePoint n = m_normals[len -1]; for (int j = len - 1; j > 0; j--) m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); m_normals[0] = DoublePoint(-n.X, -n.Y); k = 0; for (int j = len - 1; j >= 0; j--) OffsetPoint(j, k, node.m_jointype); m_destPolys.push_back(m_destPoly); } else { int k = 0; for (int j = 1; j < len - 1; ++j) OffsetPoint(j, k, node.m_jointype); IntPoint pt1; if (node.m_endtype == etOpenButt) { int j = len - 1; pt1 = IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * delta), Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); m_destPoly.push_back(pt1); pt1 = IntPoint(Round(m_srcPoly[j].X - m_normals[j].X * delta), Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); m_destPoly.push_back(pt1); } else { int j = len - 1; k = len - 2; m_sinA = 0; m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); if (node.m_endtype == etOpenSquare) DoSquare(j, k); else DoRound(j, k); } //re-build m_normals ... for (int j = len - 1; j > 0; j--) m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); k = len - 1; for (int j = k - 1; j > 0; --j) OffsetPoint(j, k, node.m_jointype); if (node.m_endtype == etOpenButt) { pt1 = IntPoint(Round(m_srcPoly[0].X - m_normals[0].X * delta), Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); m_destPoly.push_back(pt1); pt1 = IntPoint(Round(m_srcPoly[0].X + m_normals[0].X * delta), Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); m_destPoly.push_back(pt1); } else { k = 1; m_sinA = 0; if (node.m_endtype == etOpenSquare) DoSquare(0, 1); else DoRound(0, 1); } m_destPolys.push_back(m_destPoly); } } } //------------------------------------------------------------------------------ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) { //cross product ... m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); if (std::fabs(m_sinA * m_delta) < 1.0) { //dot product ... double cosA = (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y ); if (cosA > 0) // angle => 0 degrees { m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); return; } //else angle => 180 degrees } else if (m_sinA > 1.0) m_sinA = 1.0; else if (m_sinA < -1.0) m_sinA = -1.0; if (m_sinA * m_delta < 0) { m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); m_destPoly.push_back(m_srcPoly[j]); m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); } else switch (jointype) { case jtMiter: { double r = 1 + (m_normals[j].X * m_normals[k].X + m_normals[j].Y * m_normals[k].Y); if (r >= m_miterLim) DoMiter(j, k, r); else DoSquare(j, k); break; } case jtSquare: DoSquare(j, k); break; case jtRound: DoRound(j, k); break; } k = j; } //------------------------------------------------------------------------------ void ClipperOffset::DoSquare(int j, int k) { double dx = std::tan(std::atan2(m_sinA, m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y) / 4); m_destPoly.push_back(IntPoint( Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), Round(m_srcPoly[j].Y + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); m_destPoly.push_back(IntPoint( Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), Round(m_srcPoly[j].Y + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); } //------------------------------------------------------------------------------ void ClipperOffset::DoMiter(int j, int k, double r) { double q = m_delta / r; m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); } //------------------------------------------------------------------------------ void ClipperOffset::DoRound(int j, int k) { double a = std::atan2(m_sinA, m_normals[k].X * m_normals[j].X + m_normals[k].Y * m_normals[j].Y); int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1); double X = m_normals[k].X, Y = m_normals[k].Y, X2; for (int i = 0; i < steps; ++i) { m_destPoly.push_back(IntPoint( Round(m_srcPoly[j].X + X * m_delta), Round(m_srcPoly[j].Y + Y * m_delta))); X2 = X; X = X * m_cos - m_sin * Y; Y = X2 * m_sin + Y * m_cos; } m_destPoly.push_back(IntPoint( Round(m_srcPoly[j].X + m_normals[j].X * m_delta), Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); } //------------------------------------------------------------------------------ // Miscellaneous public functions //------------------------------------------------------------------------------ // Called by Clipper::ExecuteInternal() // For each polygon, search for exactly duplicate non-successive points. // If such a point is found, the loop is split into two pieces. // Search for the duplicate points is O(n^2)! // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/Clipper/Properties/StrictlySimple.htm void Clipper::DoSimplePolygons() { PROFILE_FUNC(); size_t i = 0; while (i < m_PolyOuts.size()) { OutRec* outrec = m_PolyOuts[i++]; OutPt* op = outrec->Pts; if (!op || outrec->IsOpen) continue; do //for each Pt in Polygon until duplicate found do ... { OutPt* op2 = op->Next; while (op2 != outrec->Pts) { if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) { //split the polygon into two ... OutPt* op3 = op->Prev; OutPt* op4 = op2->Prev; op->Prev = op4; op4->Next = op; op2->Prev = op3; op3->Next = op2; outrec->Pts = op; OutRec* outrec2 = CreateOutRec(); outrec2->Pts = op2; UpdateOutPtIdxs(*outrec2); if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) { //OutRec2 is contained by OutRec1 ... outrec2->IsHole = !outrec->IsHole; outrec2->FirstLeft = outrec; // For each m_PolyOuts, replace FirstLeft from outRec2 to outrec. if (m_UsingPolyTree) FixupFirstLefts2(outrec2, outrec); } else if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) { //OutRec1 is contained by OutRec2 ... outrec2->IsHole = outrec->IsHole; outrec->IsHole = !outrec2->IsHole; outrec2->FirstLeft = outrec->FirstLeft; outrec->FirstLeft = outrec2; // For each m_PolyOuts, replace FirstLeft from outrec to outrec2. if (m_UsingPolyTree) FixupFirstLefts2(outrec, outrec2); } else { //the 2 polygons are separate ... outrec2->IsHole = outrec->IsHole; outrec2->FirstLeft = outrec->FirstLeft; // For each polygon of m_PolyOuts, replace FirstLeft from outrec to outrec2 if the polygon is inside outRec2. //FIXME This is potentially very expensive! O(n^3)! if (m_UsingPolyTree) FixupFirstLefts1(outrec, outrec2); } op2 = op; //ie get ready for the Next iteration } op2 = op2->Next; } op = op->Next; } while (op != outrec->Pts); } } //------------------------------------------------------------------------------ void ReversePath(Path& p) { std::reverse(p.begin(), p.end()); } //------------------------------------------------------------------------------ void ReversePaths(Paths& p) { for (Paths::size_type i = 0; i < p.size(); ++i) ReversePath(p[i]); } //------------------------------------------------------------------------------ void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType) { Clipper c; c.StrictlySimple(true); c.AddPath(in_poly, ptSubject, true); c.Execute(ctUnion, out_polys, fillType, fillType); } //------------------------------------------------------------------------------ void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType) { Clipper c; c.StrictlySimple(true); c.AddPaths(in_polys, ptSubject, true); c.Execute(ctUnion, out_polys, fillType, fillType); } //------------------------------------------------------------------------------ void SimplifyPolygons(Paths &polys, PolyFillType fillType) { SimplifyPolygons(polys, polys, fillType); } //------------------------------------------------------------------------------ inline double DistanceSqrd(const IntPoint& pt1, const IntPoint& pt2) { double Dx = ((double)pt1.X - pt2.X); double dy = ((double)pt1.Y - pt2.Y); return (Dx*Dx + dy*dy); } //------------------------------------------------------------------------------ double DistanceFromLineSqrd( const IntPoint& pt, const IntPoint& ln1, const IntPoint& ln2) { //The equation of a line in general form (Ax + By + C = 0) //given 2 points (x¹,y¹) & (x²,y²) is ... //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0 //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹ //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) //see http://en.wikipedia.org/wiki/Perpendicular_distance double A = double(ln1.Y - ln2.Y); double B = double(ln2.X - ln1.X); double C = A * ln1.X + B * ln1.Y; C = A * pt.X + B * pt.Y - C; return (C * C) / (A * A + B * B); } //--------------------------------------------------------------------------- bool SlopesNearCollinear(const IntPoint& pt1, const IntPoint& pt2, const IntPoint& pt3, double distSqrd) { //this function is more accurate when the point that's geometrically //between the other 2 points is the one that's tested for distance. //ie makes it more likely to pick up 'spikes' ... if (std::abs(pt1.X - pt2.X) > std::abs(pt1.Y - pt2.Y)) { if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; else return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; } else { if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; else return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; } } //------------------------------------------------------------------------------ bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) { double Dx = (double)pt1.X - pt2.X; double dy = (double)pt1.Y - pt2.Y; return ((Dx * Dx) + (dy * dy) <= distSqrd); } //------------------------------------------------------------------------------ OutPt* ExcludeOp(OutPt* op) { OutPt* result = op->Prev; result->Next = op->Next; op->Next->Prev = result; result->Idx = 0; return result; } //------------------------------------------------------------------------------ // Simplify a polygon using a linked list of points. void CleanPolygon(const Path& in_poly, Path& out_poly, double distance) { //distance = proximity in units/pixels below which vertices //will be stripped. Default ~= sqrt(2). size_t size = in_poly.size(); if (size == 0) { out_poly.clear(); return; } std::vector outPts(size); for (size_t i = 0; i < size; ++i) { outPts[i].Pt = in_poly[i]; outPts[i].Next = &outPts[(i + 1) % size]; outPts[i].Next->Prev = &outPts[i]; outPts[i].Idx = 0; } double distSqrd = distance * distance; OutPt* op = &outPts[0]; while (op->Idx == 0 && op->Next != op->Prev) { if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) { op = ExcludeOp(op); size--; } else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) { ExcludeOp(op->Next); op = ExcludeOp(op); size -= 2; } else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, distSqrd)) { op = ExcludeOp(op); size--; } else { op->Idx = 1; op = op->Next; } } if (size < 3) size = 0; out_poly.resize(size); for (size_t i = 0; i < size; ++i) { out_poly[i] = op->Pt; op = op->Next; } } //------------------------------------------------------------------------------ void CleanPolygon(Path& poly, double distance) { CleanPolygon(poly, poly, distance); } //------------------------------------------------------------------------------ void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance) { for (Paths::size_type i = 0; i < in_polys.size(); ++i) CleanPolygon(in_polys[i], out_polys[i], distance); } //------------------------------------------------------------------------------ void CleanPolygons(Paths& polys, double distance) { CleanPolygons(polys, polys, distance); } //------------------------------------------------------------------------------ void Minkowski(const Path& poly, const Path& path, Paths& solution, bool isSum, bool isClosed) { int delta = (isClosed ? 1 : 0); size_t polyCnt = poly.size(); size_t pathCnt = path.size(); Paths pp; pp.reserve(pathCnt); if (isSum) for (size_t i = 0; i < pathCnt; ++i) { Path p; p.reserve(polyCnt); for (size_t j = 0; j < poly.size(); ++j) p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); pp.push_back(p); } else for (size_t i = 0; i < pathCnt; ++i) { Path p; p.reserve(polyCnt); for (size_t j = 0; j < poly.size(); ++j) p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); pp.push_back(p); } solution.clear(); solution.reserve((pathCnt + delta) * (polyCnt + 1)); for (size_t i = 0; i < pathCnt - 1 + delta; ++i) for (size_t j = 0; j < polyCnt; ++j) { Path quad; quad.reserve(4); quad.push_back(pp[i % pathCnt][j % polyCnt]); quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); if (!Orientation(quad)) ReversePath(quad); solution.push_back(quad); } } //------------------------------------------------------------------------------ void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed) { Minkowski(pattern, path, solution, true, pathIsClosed); Clipper c; c.AddPaths(solution, ptSubject, true); c.Execute(ctUnion, solution, pftNonZero, pftNonZero); } //------------------------------------------------------------------------------ void TranslatePath(const Path& input, Path& output, const IntPoint& delta) { //precondition: input != output output.resize(input.size()); for (size_t i = 0; i < input.size(); ++i) output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y); } //------------------------------------------------------------------------------ void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed) { Clipper c; for (size_t i = 0; i < paths.size(); ++i) { Paths tmp; Minkowski(pattern, paths[i], tmp, true, pathIsClosed); c.AddPaths(tmp, ptSubject, true); if (pathIsClosed) { Path tmp2; TranslatePath(paths[i], tmp2, pattern[0]); c.AddPath(tmp2, ptClip, true); } } c.Execute(ctUnion, solution, pftNonZero, pftNonZero); } //------------------------------------------------------------------------------ void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution) { Minkowski(poly1, poly2, solution, false, true); Clipper c; c.AddPaths(solution, ptSubject, true); c.Execute(ctUnion, solution, pftNonZero, pftNonZero); } //------------------------------------------------------------------------------ enum NodeType {ntAny, ntOpen, ntClosed}; void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& paths) { bool match = true; if (nodetype == ntClosed) match = !polynode.IsOpen(); else if (nodetype == ntOpen) return; if (!polynode.Contour.empty() && match) paths.push_back(polynode.Contour); for (int i = 0; i < polynode.ChildCount(); ++i) AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths); } //------------------------------------------------------------------------------ void PolyTreeToPaths(const PolyTree& polytree, Paths& paths) { paths.resize(0); paths.reserve(polytree.Total()); AddPolyNodeToPaths(polytree, ntAny, paths); } //------------------------------------------------------------------------------ void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths) { paths.resize(0); paths.reserve(polytree.Total()); AddPolyNodeToPaths(polytree, ntClosed, paths); } //------------------------------------------------------------------------------ void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) { paths.resize(0); paths.reserve(polytree.Total()); //Open paths are top level only, so ... for (int i = 0; i < polytree.ChildCount(); ++i) if (polytree.Childs[i]->IsOpen()) paths.push_back(polytree.Childs[i]->Contour); } //------------------------------------------------------------------------------ std::ostream& operator <<(std::ostream &s, const IntPoint &p) { s << "(" << p.X << "," << p.Y << ")"; return s; } //------------------------------------------------------------------------------ std::ostream& operator <<(std::ostream &s, const Path &p) { if (p.empty()) return s; Path::size_type last = p.size() -1; for (Path::size_type i = 0; i < last; i++) s << "(" << p[i].X << "," << p[i].Y << "), "; s << "(" << p[last].X << "," << p[last].Y << ")\n"; return s; } //------------------------------------------------------------------------------ std::ostream& operator <<(std::ostream &s, const Paths &p) { for (Paths::size_type i = 0; i < p.size(); i++) s << p[i]; s << "\n"; return s; } //------------------------------------------------------------------------------ } //ClipperLib namespace Slic3r-version_1.39.1/xs/src/clipper.hpp000066400000000000000000000465161324354444700201270ustar00rootroot00000000000000/******************************************************************************* * * * Author : Angus Johnson * * Version : 6.2.9 * * Date : 16 February 2015 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2015 * * * * License: * * Use, modification & distribution is subject to Boost Software License Ver 1. * * http://www.boost.org/LICENSE_1_0.txt * * * * Attributions: * * The code in this library is an extension of Bala Vatti's clipping algorithm: * * "A generic solution to polygon clipping" * * Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * * http://portal.acm.org/citation.cfm?id=129906 * * * * Computer graphics and geometric modeling: implementation and algorithms * * By Max K. Agoston * * Springer; 1 edition (January 4, 2005) * * http://books.google.com/books?q=vatti+clipping+agoston * * * * See also: * * "Polygon Offsetting by Computing Winding Numbers" * * Paper no. DETC2005-85513 pp. 565-575 * * ASME 2005 International Design Engineering Technical Conferences * * and Computers and Information in Engineering Conference (IDETC/CIE2005) * * September 24-28, 2005 , Long Beach, California, USA * * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * * * *******************************************************************************/ #ifndef clipper_hpp #define clipper_hpp #include #define CLIPPER_VERSION "6.2.6" //use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance. //#define use_xyz //use_lines: Enables line clipping. Adds a very minor cost to performance. #define use_lines //use_deprecated: Enables temporary support for the obsolete functions //#define use_deprecated #include #include #include #include #include #include #include #include namespace ClipperLib { enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; enum PolyType { ptSubject, ptClip }; //By far the most widely used winding rules for polygon filling are //EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) //Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) //see http://glprogramming.com/red/chapter11.html enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; // Point coordinate type typedef int64_t cInt; // Maximum cInt value to allow a cross product calculation using 32bit expressions. static cInt const loRange = 0x3FFFFFFF; // Maximum allowed cInt value. static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; struct IntPoint { cInt X; cInt Y; #ifdef use_xyz cInt Z; IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {}; #else IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {}; #endif friend inline bool operator== (const IntPoint& a, const IntPoint& b) { return a.X == b.X && a.Y == b.Y; } friend inline bool operator!= (const IntPoint& a, const IntPoint& b) { return a.X != b.X || a.Y != b.Y; } }; //------------------------------------------------------------------------------ typedef std::vector< IntPoint > Path; typedef std::vector< Path > Paths; inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;} inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;} std::ostream& operator <<(std::ostream &s, const IntPoint &p); std::ostream& operator <<(std::ostream &s, const Path &p); std::ostream& operator <<(std::ostream &s, const Paths &p); struct DoublePoint { double X; double Y; DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} }; //------------------------------------------------------------------------------ #ifdef use_xyz typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt); #endif enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; enum JoinType {jtSquare, jtRound, jtMiter}; enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound}; class PolyNode; typedef std::vector< PolyNode* > PolyNodes; class PolyNode { public: PolyNode() : Childs(), Parent(0), Index(0), m_IsOpen(false) {} virtual ~PolyNode(){}; Path Contour; PolyNodes Childs; PolyNode* Parent; // Traversal of the polygon tree in a depth first fashion. PolyNode* GetNext() const { return Childs.empty() ? GetNextSiblingUp() : Childs.front(); } bool IsHole() const; bool IsOpen() const { return m_IsOpen; } int ChildCount() const { return (int)Childs.size(); } private: unsigned Index; //node index in Parent.Childs bool m_IsOpen; JoinType m_jointype; EndType m_endtype; PolyNode* GetNextSiblingUp() const { return Parent ? ((Index == Parent->Childs.size() - 1) ? Parent->GetNextSiblingUp() : Parent->Childs[Index + 1]) : nullptr; } void AddChild(PolyNode& child); friend class Clipper; //to access Index friend class ClipperOffset; friend class PolyTree; //to implement the PolyTree::move operator }; class PolyTree: public PolyNode { public: PolyTree() {} PolyTree(PolyTree &&src) { *this = std::move(src); } virtual ~PolyTree(){Clear();}; PolyTree& operator=(PolyTree &&src) { AllNodes = std::move(src.AllNodes); Contour = std::move(src.Contour); Childs = std::move(src.Childs); Parent = nullptr; Index = src.Index; m_IsOpen = src.m_IsOpen; m_jointype = src.m_jointype; m_endtype = src.m_endtype; for (size_t i = 0; i < Childs.size(); ++ i) Childs[i]->Parent = this; return *this; } PolyNode* GetFirst() const { return Childs.empty() ? nullptr : Childs.front(); } void Clear() { AllNodes.clear(); Childs.clear(); } int Total() const; private: PolyTree(const PolyTree &src) = delete; PolyTree& operator=(const PolyTree &src) = delete; std::vector AllNodes; friend class Clipper; //to access AllNodes }; double Area(const Path &poly); inline bool Orientation(const Path &poly) { return Area(poly) >= 0; } int PointInPolygon(const IntPoint &pt, const Path &path); void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd); void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd); void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415); void CleanPolygon(Path& poly, double distance = 1.415); void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415); void CleanPolygons(Paths& polys, double distance = 1.415); void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed); void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed); void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution); void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); void ReversePath(Path& p); void ReversePaths(Paths& p); struct IntRect { cInt left; cInt top; cInt right; cInt bottom; }; //enums that are used internally ... enum EdgeSide { esLeft = 1, esRight = 2}; // namespace Internal { //forward declarations (for stuff used internally) ... struct TEdge { // Bottom point of this edge (with minimum Y). IntPoint Bot; // Current position. IntPoint Curr; // Top point of this edge (with maximum Y). IntPoint Top; // Vector from Bot to Top. IntPoint Delta; // Slope (dx/dy). For horiontal edges, the slope is set to HORIZONTAL (-1.0E+40). double Dx; PolyType PolyTyp; EdgeSide Side; // Winding number delta. 1 or -1 depending on winding direction, 0 for open paths and flat closed paths. int WindDelta; int WindCnt; int WindCnt2; //winding count of the opposite polytype int OutIdx; // Next edge in the input path. TEdge *Next; // Previous edge in the input path. TEdge *Prev; // Next edge in the Local Minima List chain. TEdge *NextInLML; TEdge *NextInAEL; TEdge *PrevInAEL; TEdge *NextInSEL; TEdge *PrevInSEL; }; struct IntersectNode { IntersectNode(TEdge *Edge1, TEdge *Edge2, IntPoint Pt) : Edge1(Edge1), Edge2(Edge2), Pt(Pt) {} TEdge *Edge1; TEdge *Edge2; IntPoint Pt; }; struct LocalMinimum { cInt Y; TEdge *LeftBound; TEdge *RightBound; }; // Point of an output polygon. // 36B on 64bit system without use_xyz. struct OutPt { // 4B int Idx; // 16B without use_xyz / 24B with use_xyz IntPoint Pt; // 4B on 32bit system, 8B on 64bit system OutPt *Next; // 4B on 32bit system, 8B on 64bit system OutPt *Prev; }; struct OutRec; struct Join { Join(OutPt *OutPt1, OutPt *OutPt2, IntPoint OffPt) : OutPt1(OutPt1), OutPt2(OutPt2), OffPt(OffPt) {} OutPt *OutPt1; OutPt *OutPt2; IntPoint OffPt; }; // }; // namespace Internal //------------------------------------------------------------------------------ //ClipperBase is the ancestor to the Clipper class. It should not be //instantiated directly. This class simply abstracts the conversion of sets of //polygon coordinates into edge objects that are stored in a LocalMinima list. class ClipperBase { public: ClipperBase() : m_UseFullRange(false), m_HasOpenPaths(false) {} ~ClipperBase() { Clear(); } bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); void Clear(); IntRect GetBounds(); // By default, when three or more vertices are collinear in input polygons (subject or clip), the Clipper object removes the 'inner' vertices before clipping. // When enabled the PreserveCollinear property prevents this default behavior to allow these inner vertices to appear in the solution. bool PreserveCollinear() const {return m_PreserveCollinear;}; void PreserveCollinear(bool value) {m_PreserveCollinear = value;}; protected: bool AddPathInternal(const Path &pg, int highI, PolyType PolyTyp, bool Closed, TEdge* edges); TEdge* AddBoundsToLML(TEdge *e, bool IsClosed); void Reset(); TEdge* ProcessBound(TEdge* E, bool IsClockwise); TEdge* DescendToMin(TEdge *&E); void AscendToMax(TEdge *&E, bool Appending, bool IsClosed); // Local minima (Y, left edge, right edge) sorted by ascending Y. std::vector m_MinimaList; // True if the input polygons have abs values higher than loRange, but lower than hiRange. // False if the input polygons have abs values lower or equal to loRange. bool m_UseFullRange; // A vector of edges per each input path. std::vector> m_edges; // Don't remove intermediate vertices of a collinear sequence of points. bool m_PreserveCollinear; // Is any of the paths inserted by AddPath() or AddPaths() open? bool m_HasOpenPaths; }; //------------------------------------------------------------------------------ class Clipper : public ClipperBase { public: Clipper(int initOptions = 0); ~Clipper() { Clear(); } void Clear() { ClipperBase::Clear(); DisposeAllOutRecs(); } bool Execute(ClipType clipType, Paths &solution, PolyFillType fillType = pftEvenOdd) { return Execute(clipType, solution, fillType, fillType); } bool Execute(ClipType clipType, Paths &solution, PolyFillType subjFillType, PolyFillType clipFillType); bool Execute(ClipType clipType, PolyTree &polytree, PolyFillType fillType = pftEvenOdd) { return Execute(clipType, polytree, fillType, fillType); } bool Execute(ClipType clipType, PolyTree &polytree, PolyFillType subjFillType, PolyFillType clipFillType); bool ReverseSolution() const { return m_ReverseOutput; }; void ReverseSolution(bool value) {m_ReverseOutput = value;}; bool StrictlySimple() const {return m_StrictSimple;}; void StrictlySimple(bool value) {m_StrictSimple = value;}; //set the callback function for z value filling on intersections (otherwise Z is 0) #ifdef use_xyz void ZFillFunction(ZFillCallback zFillFunc) { m_ZFill = zFillFunc; } #endif protected: void Reset(); virtual bool ExecuteInternal(); private: // Output polygons. std::vector m_PolyOuts; // Output points, allocated by a continuous sets of m_OutPtsChunkSize. std::vector m_OutPts; // List of free output points, to be used before taking a point from m_OutPts or allocating a new chunk. OutPt *m_OutPtsFree; size_t m_OutPtsChunkSize; size_t m_OutPtsChunkLast; std::vector m_Joins; std::vector m_GhostJoins; std::vector m_IntersectList; ClipType m_ClipType; // A priority queue (a binary heap) of Y coordinates. std::priority_queue m_Scanbeam; // Maxima are collected by ProcessEdgesAtTopOfScanbeam(), consumed by ProcessHorizontal(). std::vector m_Maxima; TEdge *m_ActiveEdges; TEdge *m_SortedEdges; PolyFillType m_ClipFillType; PolyFillType m_SubjFillType; bool m_ReverseOutput; // Does the result go to a PolyTree or Paths? bool m_UsingPolyTree; bool m_StrictSimple; #ifdef use_xyz ZFillCallback m_ZFill; //custom callback #endif void SetWindingCount(TEdge& edge) const; bool IsEvenOddFillType(const TEdge& edge) const { return (edge.PolyTyp == ptSubject) ? m_SubjFillType == pftEvenOdd : m_ClipFillType == pftEvenOdd; } bool IsEvenOddAltFillType(const TEdge& edge) const { return (edge.PolyTyp == ptSubject) ? m_ClipFillType == pftEvenOdd : m_SubjFillType == pftEvenOdd; } void InsertLocalMinimaIntoAEL(const cInt botY); void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge); void AddEdgeToSEL(TEdge *edge); void CopyAELToSEL(); void DeleteFromSEL(TEdge *e); void DeleteFromAEL(TEdge *e); void UpdateEdgeIntoAEL(TEdge *&e); void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); bool IsContributing(const TEdge& edge) const; bool IsTopHorz(const cInt XPos); void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); void DoMaxima(TEdge *e); void ProcessHorizontals(); void ProcessHorizontal(TEdge *horzEdge); void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); OutRec* GetOutRec(int idx); void AppendPolygon(TEdge *e1, TEdge *e2) const; void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); OutRec* CreateOutRec(); OutPt* AddOutPt(TEdge *e, const IntPoint &pt); OutPt* GetLastOutPt(TEdge *e); OutPt* AllocateOutPt(); OutPt* DupOutPt(OutPt* outPt, bool InsertAfter); // Add the point to a list of free points. void DisposeOutPt(OutPt *pt) { pt->Next = m_OutPtsFree; m_OutPtsFree = pt; } void DisposeOutPts(OutPt*& pp) { if (pp != nullptr) { pp->Prev->Next = m_OutPtsFree; m_OutPtsFree = pp; } } void DisposeAllOutRecs(); bool ProcessIntersections(const cInt topY); void BuildIntersectList(const cInt topY); void ProcessEdgesAtTopOfScanbeam(const cInt topY); void BuildResult(Paths& polys); void BuildResult2(PolyTree& polytree); void SetHoleState(TEdge *e, OutRec *outrec) const; bool FixupIntersectionOrder(); void FixupOutPolygon(OutRec &outrec); void FixupOutPolyline(OutRec &outrec); bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); void FixHoleLinkage(OutRec &outrec); bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2); bool JoinHorz(OutPt* op1, OutPt* op1b, OutPt* op2, OutPt* op2b, const IntPoint &Pt, bool DiscardLeft); void JoinCommonEdges(); void DoSimplePolygons(); void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec) const; void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec) const; #ifdef use_xyz void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2); #endif }; //------------------------------------------------------------------------------ class ClipperOffset { public: ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25, double shortestEdgeLength = 0.) : MiterLimit(miterLimit), ArcTolerance(roundPrecision), ShortestEdgeLength(shortestEdgeLength), m_lowest(-1, 0) {} ~ClipperOffset() { Clear(); } void AddPath(const Path& path, JoinType joinType, EndType endType); void AddPaths(const Paths& paths, JoinType joinType, EndType endType); void Execute(Paths& solution, double delta); void Execute(PolyTree& solution, double delta); void Clear(); double MiterLimit; double ArcTolerance; double ShortestEdgeLength; private: Paths m_destPolys; Path m_srcPoly; Path m_destPoly; std::vector m_normals; double m_delta, m_sinA, m_sin, m_cos; double m_miterLim, m_StepsPerRad; IntPoint m_lowest; PolyNode m_polyNodes; void FixOrientations(); void DoOffset(double delta); void OffsetPoint(int j, int& k, JoinType jointype); void DoSquare(int j, int k); void DoMiter(int j, int k, double r); void DoRound(int j, int k); }; //------------------------------------------------------------------------------ class clipperException : public std::exception { public: clipperException(const char* description): m_descr(description) {} virtual ~clipperException() throw() {} virtual const char* what() const throw() {return m_descr.c_str();} private: std::string m_descr; }; //------------------------------------------------------------------------------ } //ClipperLib namespace #endif //clipper_hpp Slic3r-version_1.39.1/xs/src/libslic3r/000077500000000000000000000000001324354444700176325ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/libslic3r/BoundingBox.cpp000066400000000000000000000216551324354444700225650ustar00rootroot00000000000000#include "BoundingBox.hpp" #include #include namespace Slic3r { template BoundingBoxBase::BoundingBoxBase(const std::vector &points) { if (points.empty()) CONFESS("Empty point set supplied to BoundingBoxBase constructor"); typename std::vector::const_iterator it = points.begin(); this->min.x = this->max.x = it->x; this->min.y = this->max.y = it->y; for (++it; it != points.end(); ++it) { this->min.x = std::min(it->x, this->min.x); this->min.y = std::min(it->y, this->min.y); this->max.x = std::max(it->x, this->max.x); this->max.y = std::max(it->y, this->max.y); } this->defined = true; } template BoundingBoxBase::BoundingBoxBase(const std::vector &points); template BoundingBoxBase::BoundingBoxBase(const std::vector &points); template BoundingBox3Base::BoundingBox3Base(const std::vector &points) : BoundingBoxBase(points) { if (points.empty()) CONFESS("Empty point set supplied to BoundingBox3Base constructor"); typename std::vector::const_iterator it = points.begin(); this->min.z = this->max.z = it->z; for (++it; it != points.end(); ++it) { this->min.z = std::min(it->z, this->min.z); this->max.z = std::max(it->z, this->max.z); } } template BoundingBox3Base::BoundingBox3Base(const std::vector &points); BoundingBox::BoundingBox(const Lines &lines) { Points points; points.reserve(lines.size()); for (const Line &line : lines) { points.emplace_back(line.a); points.emplace_back(line.b); } *this = BoundingBox(points); } void BoundingBox::polygon(Polygon* polygon) const { polygon->points.clear(); polygon->points.resize(4); polygon->points[0].x = this->min.x; polygon->points[0].y = this->min.y; polygon->points[1].x = this->max.x; polygon->points[1].y = this->min.y; polygon->points[2].x = this->max.x; polygon->points[2].y = this->max.y; polygon->points[3].x = this->min.x; polygon->points[3].y = this->max.y; } Polygon BoundingBox::polygon() const { Polygon p; this->polygon(&p); return p; } BoundingBox BoundingBox::rotated(double angle) const { BoundingBox out; out.merge(this->min.rotated(angle)); out.merge(this->max.rotated(angle)); out.merge(Point(this->min.x, this->max.y).rotated(angle)); out.merge(Point(this->max.x, this->min.y).rotated(angle)); return out; } BoundingBox BoundingBox::rotated(double angle, const Point ¢er) const { BoundingBox out; out.merge(this->min.rotated(angle, center)); out.merge(this->max.rotated(angle, center)); out.merge(Point(this->min.x, this->max.y).rotated(angle, center)); out.merge(Point(this->max.x, this->min.y).rotated(angle, center)); return out; } template void BoundingBoxBase::scale(double factor) { this->min.scale(factor); this->max.scale(factor); } template void BoundingBoxBase::scale(double factor); template void BoundingBoxBase::scale(double factor); template void BoundingBoxBase::scale(double factor); template void BoundingBoxBase::merge(const PointClass &point) { if (this->defined) { this->min.x = std::min(point.x, this->min.x); this->min.y = std::min(point.y, this->min.y); this->max.x = std::max(point.x, this->max.x); this->max.y = std::max(point.y, this->max.y); } else { this->min = this->max = point; this->defined = true; } } template void BoundingBoxBase::merge(const Point &point); template void BoundingBoxBase::merge(const Pointf &point); template void BoundingBoxBase::merge(const std::vector &points) { this->merge(BoundingBoxBase(points)); } template void BoundingBoxBase::merge(const Points &points); template void BoundingBoxBase::merge(const Pointfs &points); template void BoundingBoxBase::merge(const BoundingBoxBase &bb) { assert(bb.defined || bb.min.x >= bb.max.x || bb.min.y >= bb.max.y); if (bb.defined) { if (this->defined) { this->min.x = std::min(bb.min.x, this->min.x); this->min.y = std::min(bb.min.y, this->min.y); this->max.x = std::max(bb.max.x, this->max.x); this->max.y = std::max(bb.max.y, this->max.y); } else { this->min = bb.min; this->max = bb.max; this->defined = true; } } } template void BoundingBoxBase::merge(const BoundingBoxBase &bb); template void BoundingBoxBase::merge(const BoundingBoxBase &bb); template void BoundingBox3Base::merge(const PointClass &point) { if (this->defined) { this->min.z = std::min(point.z, this->min.z); this->max.z = std::max(point.z, this->max.z); } BoundingBoxBase::merge(point); } template void BoundingBox3Base::merge(const Pointf3 &point); template void BoundingBox3Base::merge(const std::vector &points) { this->merge(BoundingBox3Base(points)); } template void BoundingBox3Base::merge(const Pointf3s &points); template void BoundingBox3Base::merge(const BoundingBox3Base &bb) { assert(bb.defined || bb.min.x >= bb.max.x || bb.min.y >= bb.max.y || bb.min.z >= bb.max.z); if (bb.defined) { if (this->defined) { this->min.z = std::min(bb.min.z, this->min.z); this->max.z = std::max(bb.max.z, this->max.z); } BoundingBoxBase::merge(bb); } } template void BoundingBox3Base::merge(const BoundingBox3Base &bb); template PointClass BoundingBoxBase::size() const { return PointClass(this->max.x - this->min.x, this->max.y - this->min.y); } template Point BoundingBoxBase::size() const; template Pointf BoundingBoxBase::size() const; template PointClass BoundingBox3Base::size() const { return PointClass(this->max.x - this->min.x, this->max.y - this->min.y, this->max.z - this->min.z); } template Pointf3 BoundingBox3Base::size() const; template double BoundingBoxBase::radius() const { assert(this->defined); double x = this->max.x - this->min.x; double y = this->max.y - this->min.y; return 0.5 * sqrt(x*x+y*y); } template double BoundingBoxBase::radius() const; template double BoundingBoxBase::radius() const; template double BoundingBox3Base::radius() const { double x = this->max.x - this->min.x; double y = this->max.y - this->min.y; double z = this->max.z - this->min.z; return 0.5 * sqrt(x*x+y*y+z*z); } template double BoundingBox3Base::radius() const; template void BoundingBoxBase::offset(coordf_t delta) { this->min.translate(-delta, -delta); this->max.translate(delta, delta); } template void BoundingBoxBase::offset(coordf_t delta); template void BoundingBoxBase::offset(coordf_t delta); template void BoundingBox3Base::offset(coordf_t delta) { this->min.translate(-delta, -delta, -delta); this->max.translate(delta, delta, delta); } template void BoundingBox3Base::offset(coordf_t delta); template PointClass BoundingBoxBase::center() const { return PointClass( (this->max.x + this->min.x)/2, (this->max.y + this->min.y)/2 ); } template Point BoundingBoxBase::center() const; template Pointf BoundingBoxBase::center() const; template PointClass BoundingBox3Base::center() const { return PointClass( (this->max.x + this->min.x)/2, (this->max.y + this->min.y)/2, (this->max.z + this->min.z)/2 ); } template Pointf3 BoundingBox3Base::center() const; // Align a coordinate to a grid. The coordinate may be negative, // the aligned value will never be bigger than the original one. static inline coord_t _align_to_grid(const coord_t coord, const coord_t spacing) { // Current C++ standard defines the result of integer division to be rounded to zero, // for both positive and negative numbers. Here we want to round down for negative // numbers as well. coord_t aligned = (coord < 0) ? ((coord - spacing + 1) / spacing) * spacing : (coord / spacing) * spacing; assert(aligned <= coord); return aligned; } void BoundingBox::align_to_grid(const coord_t cell_size) { if (this->defined) { min.x = _align_to_grid(min.x, cell_size); min.y = _align_to_grid(min.y, cell_size); } } } Slic3r-version_1.39.1/xs/src/libslic3r/BoundingBox.hpp000066400000000000000000000113531324354444700225640ustar00rootroot00000000000000#ifndef slic3r_BoundingBox_hpp_ #define slic3r_BoundingBox_hpp_ #include "libslic3r.h" #include "Point.hpp" #include "Polygon.hpp" namespace Slic3r { typedef Point Size; typedef Point3 Size3; typedef Pointf Sizef; typedef Pointf3 Sizef3; template class BoundingBoxBase { public: PointClass min; PointClass max; bool defined; BoundingBoxBase() : defined(false) {}; BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : min(pmin), max(pmax), defined(pmin.x < pmax.x && pmin.y < pmax.y) {} BoundingBoxBase(const std::vector &points); void merge(const PointClass &point); void merge(const std::vector &points); void merge(const BoundingBoxBase &bb); void scale(double factor); PointClass size() const; double radius() const; void translate(coordf_t x, coordf_t y) { assert(this->defined); this->min.translate(x, y); this->max.translate(x, y); } void translate(const Pointf &pos) { this->translate(pos.x, pos.y); } void offset(coordf_t delta); PointClass center() const; bool contains(const PointClass &point) const { return point.x >= this->min.x && point.x <= this->max.x && point.y >= this->min.y && point.y <= this->max.y; } bool overlap(const BoundingBoxBase &other) const { return ! (this->max.x < other.min.x || this->min.x > other.max.x || this->max.y < other.min.y || this->min.y > other.max.y); } bool operator==(const BoundingBoxBase &rhs) { return this->min == rhs.min && this->max == rhs.max; } bool operator!=(const BoundingBoxBase &rhs) { return ! (*this == rhs); } }; template class BoundingBox3Base : public BoundingBoxBase { public: BoundingBox3Base() : BoundingBoxBase() {}; BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) : BoundingBoxBase(pmin, pmax) { if (pmin.z >= pmax.z) BoundingBoxBase::defined = false; } BoundingBox3Base(const std::vector &points); void merge(const PointClass &point); void merge(const std::vector &points); void merge(const BoundingBox3Base &bb); PointClass size() const; double radius() const; void translate(coordf_t x, coordf_t y, coordf_t z) { this->min.translate(x, y, z); this->max.translate(x, y, z); } void translate(const Pointf3 &pos) { this->translate(pos.x, pos.y, pos.z); } void offset(coordf_t delta); PointClass center() const; }; class BoundingBox : public BoundingBoxBase { public: void polygon(Polygon* polygon) const; Polygon polygon() const; BoundingBox rotated(double angle) const; BoundingBox rotated(double angle, const Point ¢er) const; void rotate(double angle) { (*this) = this->rotated(angle); } void rotate(double angle, const Point ¢er) { (*this) = this->rotated(angle, center); } // Align the min corner to a grid of cell_size x cell_size cells, // to encompass the original bounding box. void align_to_grid(const coord_t cell_size); BoundingBox() : BoundingBoxBase() {}; BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {}; BoundingBox(const Points &points) : BoundingBoxBase(points) {}; BoundingBox(const Lines &lines); friend BoundingBox get_extents_rotated(const Points &points, double angle); }; class BoundingBox3 : public BoundingBox3Base { public: BoundingBox3() : BoundingBox3Base() {}; BoundingBox3(const Point3 &pmin, const Point3 &pmax) : BoundingBox3Base(pmin, pmax) {}; BoundingBox3(const std::vector &points) : BoundingBox3Base(points) {}; }; class BoundingBoxf : public BoundingBoxBase { public: BoundingBoxf() : BoundingBoxBase() {}; BoundingBoxf(const Pointf &pmin, const Pointf &pmax) : BoundingBoxBase(pmin, pmax) {}; BoundingBoxf(const std::vector &points) : BoundingBoxBase(points) {}; }; class BoundingBoxf3 : public BoundingBox3Base { public: BoundingBoxf3() : BoundingBox3Base() {}; BoundingBoxf3(const Pointf3 &pmin, const Pointf3 &pmax) : BoundingBox3Base(pmin, pmax) {}; BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {}; }; template inline bool empty(const BoundingBoxBase &bb) { return ! bb.defined || bb.min.x >= bb.max.x || bb.min.y >= bb.max.y; } template inline bool empty(const BoundingBox3Base &bb) { return ! bb.defined || bb.min.x >= bb.max.x || bb.min.y >= bb.max.y || bb.min.z >= bb.max.z; } } // namespace Slic3r #endif Slic3r-version_1.39.1/xs/src/libslic3r/BridgeDetector.cpp000066400000000000000000000316211324354444700232270ustar00rootroot00000000000000#include "BridgeDetector.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" #include namespace Slic3r { BridgeDetector::BridgeDetector( ExPolygon _expolygon, const ExPolygonCollection &_lower_slices, coord_t _spacing) : // The original infill polygon, not inflated. expolygons(expolygons_owned), // All surfaces of the object supporting this region. lower_slices(_lower_slices), spacing(_spacing) { this->expolygons_owned.push_back(std::move(_expolygon)); initialize(); } BridgeDetector::BridgeDetector( const ExPolygons &_expolygons, const ExPolygonCollection &_lower_slices, coord_t _spacing) : // The original infill polygon, not inflated. expolygons(_expolygons), // All surfaces of the object supporting this region. lower_slices(_lower_slices), spacing(_spacing) { initialize(); } void BridgeDetector::initialize() { // 5 degrees stepping this->resolution = PI/36.0; // output angle not known this->angle = -1.; // Outset our bridge by an arbitrary amout; we'll use this outer margin for detecting anchors. Polygons grown = offset(to_polygons(this->expolygons), float(this->spacing)); // Detect possible anchoring edges of this bridging region. // Detect what edges lie on lower slices by turning bridge contour and holes // into polylines and then clipping them with each lower slice's contour. // Currently _edges are only used to set a candidate direction of the bridge (see bridge_direction_candidates()). this->_edges = intersection_pl(to_polylines(grown), this->lower_slices.contours()); #ifdef SLIC3R_DEBUG printf(" bridge has " PRINTF_ZU " support(s)\n", this->_edges.size()); #endif // detect anchors as intersection between our bridge expolygon and the lower slices // safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some edges this->_anchor_regions = intersection_ex(grown, to_polygons(this->lower_slices.expolygons), true); /* if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output("bridge.svg", expolygons => [ $self->expolygon ], red_expolygons => $self->lower_slices, polylines => $self->_edges, ); } */ } bool BridgeDetector::detect_angle(double bridge_direction_override) { if (this->_edges.empty() || this->_anchor_regions.empty()) // The bridging region is completely in the air, there are no anchors available at the layer below. return false; std::vector candidates; if (bridge_direction_override == 0.) { std::vector angles = bridge_direction_candidates(); candidates.reserve(angles.size()); for (size_t i = 0; i < angles.size(); ++ i) candidates.emplace_back(BridgeDirection(angles[i])); } else candidates.emplace_back(BridgeDirection(bridge_direction_override)); /* Outset the bridge expolygon by half the amount we used for detecting anchors; we'll use this one to clip our test lines and be sure that their endpoints are inside the anchors and not on their contours leading to false negatives. */ Polygons clip_area = offset(this->expolygons, 0.5f * float(this->spacing)); /* we'll now try several directions using a rudimentary visibility check: bridge in several directions and then sum the length of lines having both endpoints within anchors */ bool have_coverage = false; for (size_t i_angle = 0; i_angle < candidates.size(); ++ i_angle) { const double angle = candidates[i_angle].angle; Lines lines; { // Get an oriented bounding box around _anchor_regions. BoundingBox bbox = get_extents_rotated(this->_anchor_regions, - angle); // Cover the region with line segments. lines.reserve((bbox.max.y - bbox.min.y + this->spacing) / this->spacing); double s = sin(angle); double c = cos(angle); //FIXME Vojtech: The lines shall be spaced half the line width from the edge, but then // some of the test cases fail. Need to adjust the test cases then? // for (coord_t y = bbox.min.y + this->spacing / 2; y <= bbox.max.y; y += this->spacing) for (coord_t y = bbox.min.y; y <= bbox.max.y; y += this->spacing) lines.push_back(Line( Point((coord_t)round(c * bbox.min.x - s * y), (coord_t)round(c * y + s * bbox.min.x)), Point((coord_t)round(c * bbox.max.x - s * y), (coord_t)round(c * y + s * bbox.max.x)))); } double total_length = 0; double max_length = 0; { Lines clipped_lines = intersection_ln(lines, clip_area); for (size_t i = 0; i < clipped_lines.size(); ++i) { const Line &line = clipped_lines[i]; if (expolygons_contain(this->_anchor_regions, line.a) && expolygons_contain(this->_anchor_regions, line.b)) { // This line could be anchored. double len = line.length(); total_length += len; max_length = std::max(max_length, len); } } } if (total_length == 0.) continue; have_coverage = true; // Sum length of bridged lines. candidates[i_angle].coverage = total_length; /* The following produces more correct results in some cases and more broken in others. TODO: investigate, as it looks more reliable than line clipping. */ // $directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0; // max length of bridged lines candidates[i_angle].max_length = max_length; } // if no direction produced coverage, then there's no bridge direction if (! have_coverage) return false; // sort directions by coverage - most coverage first std::sort(candidates.begin(), candidates.end()); // if any other direction is within extrusion width of coverage, prefer it if shorter // TODO: There are two options here - within width of the angle with most coverage, or within width of the currently perferred? size_t i_best = 0; for (size_t i = 1; i < candidates.size() && candidates[i_best].coverage - candidates[i].coverage < this->spacing; ++ i) if (candidates[i].max_length < candidates[i_best].max_length) i_best = i; this->angle = candidates[i_best].angle; if (this->angle >= PI) this->angle -= PI; #ifdef SLIC3R_DEBUG printf(" Optimal infill angle is %d degrees\n", (int)Slic3r::Geometry::rad2deg(this->angle)); #endif return true; } std::vector BridgeDetector::bridge_direction_candidates() const { // we test angles according to configured resolution std::vector angles; for (int i = 0; i <= PI/this->resolution; ++i) angles.push_back(i * this->resolution); // we also test angles of each bridge contour { Lines lines = to_lines(this->expolygons); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) angles.push_back(line->direction()); } /* we also test angles of each open supporting edge (this finds the optimal angle for C-shaped supports) */ for (Polylines::const_iterator edge = this->_edges.begin(); edge != this->_edges.end(); ++edge) if (! edge->first_point().coincides_with(edge->last_point())) angles.push_back(Line(edge->first_point(), edge->last_point()).direction()); // remove duplicates double min_resolution = PI/180.0; // 1 degree std::sort(angles.begin(), angles.end()); for (size_t i = 1; i < angles.size(); ++i) { if (Slic3r::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) { angles.erase(angles.begin() + i); --i; } } /* compare first value with last one and remove the greatest one (PI) in case they are parallel (PI, 0) */ if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution)) angles.pop_back(); return angles; } Polygons BridgeDetector::coverage(double angle) const { if (angle == -1) angle = this->angle; Polygons covered; if (angle != -1) { // Get anchors, convert them to Polygons and rotate them. Polygons anchors = to_polygons(this->_anchor_regions); polygons_rotate(anchors, PI/2.0 - angle); for (ExPolygon expolygon : this->expolygons) { // Clone our expolygon and rotate it so that we work with vertical lines. expolygon.rotate(PI/2.0 - angle); // Outset the bridge expolygon by half the amount we used for detecting anchors; // we'll use this one to generate our trapezoids and be sure that their vertices // are inside the anchors and not on their contours leading to false negatives. for (ExPolygon &expoly : offset_ex(expolygon, 0.5f * float(this->spacing))) { // Compute trapezoids according to a vertical orientation Polygons trapezoids; expoly.get_trapezoids2(&trapezoids, PI/2.0); for (const Polygon &trapezoid : trapezoids) { // not nice, we need a more robust non-numeric check size_t n_supported = 0; for (const Line &supported_line : intersection_ln(trapezoid.lines(), anchors)) if (supported_line.length() >= this->spacing) ++ n_supported; if (n_supported >= 2) covered.push_back(std::move(trapezoid)); } } } // Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids // instead of exact overlaps. covered = union_(covered); // Intersect trapezoids with actual bridge area to remove extra margins and append it to result. polygons_rotate(covered, -(PI/2.0 - angle)); covered = intersection(covered, to_polygons(this->expolygons)); #if 0 { my @lines = map @{$_->lines}, @$trapezoids; $_->rotate(-(PI/2 - $angle), [0,0]) for @lines; require "Slic3r/SVG.pm"; Slic3r::SVG::output( "coverage_" . rad2deg($angle) . ".svg", expolygons => [$self->expolygon], green_expolygons => $self->_anchor_regions, red_expolygons => $coverage, lines => \@lines, ); } #endif } return covered; } /* This method returns the bridge edges (as polylines) that are not supported but would allow the entire bridge area to be bridged with detected angle if supported too */ void BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const { if (angle == -1) angle = this->angle; if (angle == -1) return; Polygons grown_lower = offset(this->lower_slices.expolygons, float(this->spacing)); for (ExPolygons::const_iterator it_expoly = this->expolygons.begin(); it_expoly != this->expolygons.end(); ++ it_expoly) { // get unsupported bridge edges (both contour and holes) Lines unsupported_lines = to_lines(diff_pl(to_polylines(*it_expoly), grown_lower)); /* Split into individual segments and filter out edges parallel to the bridging angle TODO: angle tolerance should probably be based on segment length and flow width, so that we build supports whenever there's a chance that at least one or two bridge extrusions would be anchored within such length (i.e. a slightly non-parallel bridging direction might still benefit from anchors if long enough) double angle_tolerance = PI / 180.0 * 5.0; */ for (Lines::const_iterator line = unsupported_lines.begin(); line != unsupported_lines.end(); ++line) { if (!Slic3r::Geometry::directions_parallel(line->direction(), angle)) unsupported->push_back(*line); } } /* if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "unsupported_" . rad2deg($angle) . ".svg", expolygons => [$self->expolygon], green_expolygons => $self->_anchor_regions, red_expolygons => union_ex($grown_lower), no_arrows => 1, polylines => \@bridge_edges, red_polylines => $unsupported, ); } */ } Polylines BridgeDetector::unsupported_edges(double angle) const { Polylines pp; this->unsupported_edges(angle, &pp); return pp; } } Slic3r-version_1.39.1/xs/src/libslic3r/BridgeDetector.hpp000066400000000000000000000053641324354444700232410ustar00rootroot00000000000000#ifndef slic3r_BridgeDetector_hpp_ #define slic3r_BridgeDetector_hpp_ #include "libslic3r.h" #include "ExPolygon.hpp" #include "ExPolygonCollection.hpp" #include namespace Slic3r { // The bridge detector optimizes a direction of bridges over a region or a set of regions. // A bridge direction is considered optimal, if the length of the lines strang over the region is maximal. // This is optimal if the bridge is supported in a single direction only, but // it may not likely be optimal, if the bridge region is supported from all sides. Then an optimal // solution would find a direction with shortest bridges. // The bridge orientation is measured CCW from the X axis. class BridgeDetector { public: // The non-grown holes. const ExPolygons &expolygons; // In case the caller gaves us the input polygons by a value, make a copy. ExPolygons expolygons_owned; // Lower slices, all regions. const ExPolygonCollection &lower_slices; // Scaled extrusion width of the infill. coord_t spacing; // Angle resolution for the brute force search of the best bridging angle. double resolution; // The final optimal angle. double angle; BridgeDetector(ExPolygon _expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); BridgeDetector(const ExPolygons &_expolygons, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); // If bridge_direction_override != 0, then the angle is used instead of auto-detect. bool detect_angle(double bridge_direction_override = 0.); Polygons coverage(double angle = -1) const; void unsupported_edges(double angle, Polylines* unsupported) const; Polylines unsupported_edges(double angle = -1) const; private: // Suppress warning "assignment operator could not be generated" BridgeDetector& operator=(const BridgeDetector &); void initialize(); struct BridgeDirection { BridgeDirection(double a = -1.) : angle(a), coverage(0.), max_length(0.) {} // the best direction is the one causing most lines to be bridged (thus most coverage) bool operator<(const BridgeDirection &other) const { // Initial sort by coverage only - comparator must obey strict weak ordering return this->coverage > other.coverage; }; double angle; double coverage; double max_length; }; // Get possible briging direction candidates. std::vector bridge_direction_candidates() const; // Open lines representing the supporting edges. Polylines _edges; // Closed polygons representing the supporting areas. ExPolygons _anchor_regions; }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/ClipperUtils.cpp000066400000000000000000000754221324354444700227670ustar00rootroot00000000000000#include "ClipperUtils.hpp" #include "Geometry.hpp" // #define CLIPPER_UTILS_DEBUG #ifdef CLIPPER_UTILS_DEBUG #include "SVG.hpp" #endif /* CLIPPER_UTILS_DEBUG */ #include #define CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR (0.005f) namespace Slic3r { #ifdef CLIPPER_UTILS_DEBUG bool clipper_export_enabled = false; // For debugging the Clipper library, for providing bug reports to the Clipper author. bool export_clipper_input_polygons_bin(const char *path, const ClipperLib::Paths &input_subject, const ClipperLib::Paths &input_clip) { FILE *pfile = fopen(path, "wb"); if (pfile == NULL) return false; uint32_t sz = uint32_t(input_subject.size()); fwrite(&sz, 1, sizeof(sz), pfile); for (size_t i = 0; i < input_subject.size(); ++i) { const ClipperLib::Path &path = input_subject[i]; sz = uint32_t(path.size()); ::fwrite(&sz, 1, sizeof(sz), pfile); ::fwrite(path.data(), sizeof(ClipperLib::IntPoint), sz, pfile); } sz = uint32_t(input_clip.size()); ::fwrite(&sz, 1, sizeof(sz), pfile); for (size_t i = 0; i < input_clip.size(); ++i) { const ClipperLib::Path &path = input_clip[i]; sz = uint32_t(path.size()); ::fwrite(&sz, 1, sizeof(sz), pfile); ::fwrite(path.data(), sizeof(ClipperLib::IntPoint), sz, pfile); } ::fclose(pfile); return true; err: ::fclose(pfile); return false; } #endif /* CLIPPER_UTILS_DEBUG */ void scaleClipperPolygon(ClipperLib::Path &polygon) { PROFILE_FUNC(); for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) { pit->X <<= CLIPPER_OFFSET_POWER_OF_2; pit->Y <<= CLIPPER_OFFSET_POWER_OF_2; } } void scaleClipperPolygons(ClipperLib::Paths &polygons) { PROFILE_FUNC(); for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it) for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) { pit->X <<= CLIPPER_OFFSET_POWER_OF_2; pit->Y <<= CLIPPER_OFFSET_POWER_OF_2; } } void unscaleClipperPolygon(ClipperLib::Path &polygon) { PROFILE_FUNC(); for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) { pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; pit->X >>= CLIPPER_OFFSET_POWER_OF_2; pit->Y >>= CLIPPER_OFFSET_POWER_OF_2; } } void unscaleClipperPolygons(ClipperLib::Paths &polygons) { PROFILE_FUNC(); for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it) for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) { pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; pit->X >>= CLIPPER_OFFSET_POWER_OF_2; pit->Y >>= CLIPPER_OFFSET_POWER_OF_2; } } //----------------------------------------------------------- // legacy code from Clipper documentation void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, ExPolygons* expolygons) { size_t cnt = expolygons->size(); expolygons->resize(cnt + 1); (*expolygons)[cnt].contour = ClipperPath_to_Slic3rPolygon(polynode.Contour); (*expolygons)[cnt].holes.resize(polynode.ChildCount()); for (int i = 0; i < polynode.ChildCount(); ++i) { (*expolygons)[cnt].holes[i] = ClipperPath_to_Slic3rPolygon(polynode.Childs[i]->Contour); //Add outer polygons contained by (nested within) holes ... for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++j) AddOuterPolyNodeToExPolygons(*polynode.Childs[i]->Childs[j], expolygons); } } ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree& polytree) { ExPolygons retval; for (int i = 0; i < polytree.ChildCount(); ++i) AddOuterPolyNodeToExPolygons(*polytree.Childs[i], &retval); return retval; } //----------------------------------------------------------- Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input) { Polygon retval; for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) retval.points.push_back(Point( (*pit).X, (*pit).Y )); return retval; } Slic3r::Polyline ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input) { Polyline retval; for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) retval.points.push_back(Point( (*pit).X, (*pit).Y )); return retval; } Slic3r::Polygons ClipperPaths_to_Slic3rPolygons(const ClipperLib::Paths &input) { Slic3r::Polygons retval; retval.reserve(input.size()); for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it) retval.push_back(ClipperPath_to_Slic3rPolygon(*it)); return retval; } Slic3r::Polylines ClipperPaths_to_Slic3rPolylines(const ClipperLib::Paths &input) { Slic3r::Polylines retval; retval.reserve(input.size()); for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it) retval.push_back(ClipperPath_to_Slic3rPolyline(*it)); return retval; } ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input) { // init Clipper ClipperLib::Clipper clipper; clipper.Clear(); // perform union clipper.AddPaths(input, ClipperLib::ptSubject, true); ClipperLib::PolyTree polytree; clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero // write to ExPolygons object return PolyTreeToExPolygons(polytree); } ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input) { ClipperLib::Path retval; for (Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit) retval.push_back(ClipperLib::IntPoint( (*pit).x, (*pit).y )); return retval; } ClipperLib::Path Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input) { ClipperLib::Path output; output.reserve(input.points.size()); for (Slic3r::Points::const_reverse_iterator pit = input.points.rbegin(); pit != input.points.rend(); ++pit) output.push_back(ClipperLib::IntPoint( (*pit).x, (*pit).y )); return output; } ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input) { ClipperLib::Paths retval; for (Polygons::const_iterator it = input.begin(); it != input.end(); ++it) retval.push_back(Slic3rMultiPoint_to_ClipperPath(*it)); return retval; } ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input) { ClipperLib::Paths retval; for (Polylines::const_iterator it = input.begin(); it != input.end(); ++it) retval.push_back(Slic3rMultiPoint_to_ClipperPath(*it)); return retval; } ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) { // scale input scaleClipperPolygons(input); // perform offset ClipperLib::ClipperOffset co; if (joinType == jtRound) co.ArcTolerance = miterLimit; else co.MiterLimit = miterLimit; float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); co.AddPaths(input, joinType, endType); ClipperLib::Paths retval; co.Execute(retval, delta_scaled); // unscale output unscaleClipperPolygons(retval); return retval; } ClipperLib::Paths _offset(ClipperLib::Path &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) { ClipperLib::Paths paths; paths.push_back(std::move(input)); return _offset(std::move(paths), endType, delta, joinType, miterLimit); } // This is a safe variant of the polygon offset, tailored for a single ExPolygon: // a single polygon with multiple non-overlapping holes. // Each contour and hole is offsetted separately, then the holes are subtracted from the outer contours. ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit) { // printf("new ExPolygon offset\n"); // 1) Offset the outer contour. const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); ClipperLib::Paths contours; { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(expolygon.contour); scaleClipperPolygon(input); ClipperLib::ClipperOffset co; if (joinType == jtRound) co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); co.AddPath(input, joinType, ClipperLib::etClosedPolygon); co.Execute(contours, delta_scaled); } // 2) Offset the holes one by one, collect the results. ClipperLib::Paths holes; { holes.reserve(expolygon.holes.size()); for (Polygons::const_iterator it_hole = expolygon.holes.begin(); it_hole != expolygon.holes.end(); ++ it_hole) { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole); scaleClipperPolygon(input); ClipperLib::ClipperOffset co; if (joinType == jtRound) co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); co.AddPath(input, joinType, ClipperLib::etClosedPolygon); ClipperLib::Paths out; co.Execute(out, - delta_scaled); holes.insert(holes.end(), out.begin(), out.end()); } } // 3) Subtract holes from the contours. ClipperLib::Paths output; if (holes.empty()) { output = std::move(contours); } else { ClipperLib::Clipper clipper; clipper.Clear(); clipper.AddPaths(contours, ClipperLib::ptSubject, true); clipper.AddPaths(holes, ClipperLib::ptClip, true); clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } // 4) Unscale the output. unscaleClipperPolygons(output); return output; } // This is a safe variant of the polygons offset, tailored for multiple ExPolygons. // It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours. // Each ExPolygon is offsetted separately, then the offsetted ExPolygons are united. ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit) { const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); // Offsetted ExPolygons before they are united. ClipperLib::Paths contours_cummulative; contours_cummulative.reserve(expolygons.size()); // How many non-empty offsetted expolygons were actually collected into contours_cummulative? // If only one, then there is no need to do a final union. size_t expolygons_collected = 0; for (Slic3r::ExPolygons::const_iterator it_expoly = expolygons.begin(); it_expoly != expolygons.end(); ++ it_expoly) { // 1) Offset the outer contour. ClipperLib::Paths contours; { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(it_expoly->contour); scaleClipperPolygon(input); ClipperLib::ClipperOffset co; if (joinType == jtRound) co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); co.AddPath(input, joinType, ClipperLib::etClosedPolygon); co.Execute(contours, delta_scaled); } if (contours.empty()) // No need to try to offset the holes. continue; if (it_expoly->holes.empty()) { // No need to subtract holes from the offsetted expolygon, we are done. contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end()); ++ expolygons_collected; } else { // 2) Offset the holes one by one, collect the offsetted holes. ClipperLib::Paths holes; { for (Polygons::const_iterator it_hole = it_expoly->holes.begin(); it_hole != it_expoly->holes.end(); ++ it_hole) { ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole); scaleClipperPolygon(input); ClipperLib::ClipperOffset co; if (joinType == jtRound) co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); else co.MiterLimit = miterLimit; co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR)); co.AddPath(input, joinType, ClipperLib::etClosedPolygon); ClipperLib::Paths out; co.Execute(out, - delta_scaled); holes.insert(holes.end(), out.begin(), out.end()); } } // 3) Subtract holes from the contours. if (holes.empty()) { // No hole remaining after an offset. Just copy the outer contour. contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end()); ++ expolygons_collected; } else if (delta < 0) { // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. // Subtract the offsetted holes from the offsetted contours. ClipperLib::Clipper clipper; clipper.Clear(); clipper.AddPaths(contours, ClipperLib::ptSubject, true); clipper.AddPaths(holes, ClipperLib::ptClip, true); ClipperLib::Paths output; clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); if (! output.empty()) { contours_cummulative.insert(contours_cummulative.end(), output.begin(), output.end()); ++ expolygons_collected; } else { // The offsetted holes have eaten up the offsetted outer contour. } } else { // Positive offset. As long as the Clipper offset does what one expects it to do, the offsetted hole will have a smaller // area than the original hole or even disappear, therefore there will be no new intersections. // Just collect the reversed holes. contours_cummulative.reserve(contours.size() + holes.size()); contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end()); // Reverse the holes in place. for (size_t i = 0; i < holes.size(); ++ i) std::reverse(holes[i].begin(), holes[i].end()); contours_cummulative.insert(contours_cummulative.end(), holes.begin(), holes.end()); ++ expolygons_collected; } } } // 4) Unite the offsetted expolygons. ClipperLib::Paths output; if (expolygons_collected > 1 && delta > 0) { // There is a chance that the outwards offsetted expolygons may intersect. Perform a union. ClipperLib::Clipper clipper; clipper.Clear(); clipper.AddPaths(contours_cummulative, ClipperLib::ptSubject, true); clipper.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } else { // Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output. output = std::move(contours_cummulative); } // 4) Unscale the output. unscaleClipperPolygons(output); return output; } ClipperLib::Paths _offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) { // read input ClipperLib::Paths input = Slic3rMultiPoints_to_ClipperPaths(polygons); // scale input scaleClipperPolygons(input); // prepare ClipperOffset object ClipperLib::ClipperOffset co; if (joinType == jtRound) { co.ArcTolerance = miterLimit; } else { co.MiterLimit = miterLimit; } float delta_scaled1 = delta1 * float(CLIPPER_OFFSET_SCALE); float delta_scaled2 = delta2 * float(CLIPPER_OFFSET_SCALE); co.ShortestEdgeLength = double(std::max(std::abs(delta_scaled1), std::abs(delta_scaled2)) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR); // perform first offset ClipperLib::Paths output1; co.AddPaths(input, joinType, ClipperLib::etClosedPolygon); co.Execute(output1, delta_scaled1); // perform second offset co.Clear(); co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon); ClipperLib::Paths retval; co.Execute(retval, delta_scaled2); // unscale output unscaleClipperPolygons(retval); return retval; } Polygons offset2(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) { // perform offset ClipperLib::Paths output = _offset2(polygons, delta1, delta2, joinType, miterLimit); // convert into ExPolygons return ClipperPaths_to_Slic3rPolygons(output); } ExPolygons offset2_ex(const Polygons &polygons, const float delta1, const float delta2, const ClipperLib::JoinType joinType, const double miterLimit) { // perform offset ClipperLib::Paths output = _offset2(polygons, delta1, delta2, joinType, miterLimit); // convert into ExPolygons return ClipperPaths_to_Slic3rExPolygons(output); } template T _clipper_do(const ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_) { // read input ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip); // perform safety offset if (safety_offset_) { if (clipType == ClipperLib::ctUnion) { safety_offset(&input_subject); } else { safety_offset(&input_clip); } } // init Clipper ClipperLib::Clipper clipper; clipper.Clear(); // add polygons clipper.AddPaths(input_subject, ClipperLib::ptSubject, true); clipper.AddPaths(input_clip, ClipperLib::ptClip, true); // perform operation T retval; clipper.Execute(clipType, retval, fillType, fillType); return retval; } // Fix of #117: A large fractal pyramid takes ages to slice // The Clipper library has difficulties processing overlapping polygons. // Namely, the function Clipper::JoinCommonEdges() has potentially a terrible time complexity if the output // of the operation is of the PolyTree type. // This function implmenets a following workaround: // 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time. // 2) Run Clipper Union once again to extract the PolyTree from the result of 1). inline ClipperLib::PolyTree _clipper_do_polytree2(const ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_) { // read input ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip); // perform safety offset if (safety_offset_) safety_offset((clipType == ClipperLib::ctUnion) ? &input_subject : &input_clip); ClipperLib::Clipper clipper; clipper.AddPaths(input_subject, ClipperLib::ptSubject, true); clipper.AddPaths(input_clip, ClipperLib::ptClip, true); // Perform the operation with the output to input_subject. // This pass does not generate a PolyTree, which is a very expensive operation with the current Clipper library // if there are overapping edges. clipper.Execute(clipType, input_subject, fillType, fillType); // Perform an additional Union operation to generate the PolyTree ordering. clipper.Clear(); clipper.AddPaths(input_subject, ClipperLib::ptSubject, true); ClipperLib::PolyTree retval; clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType); return retval; } ClipperLib::PolyTree _clipper_do_pl(const ClipperLib::ClipType clipType, const Polylines &subject, const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_) { // read input ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip); // perform safety offset if (safety_offset_) safety_offset(&input_clip); // init Clipper ClipperLib::Clipper clipper; clipper.Clear(); // add polygons clipper.AddPaths(input_subject, ClipperLib::ptSubject, false); clipper.AddPaths(input_clip, ClipperLib::ptClip, true); // perform operation ClipperLib::PolyTree retval; clipper.Execute(clipType, retval, fillType, fillType); return retval; } Polygons _clipper(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_) { return ClipperPaths_to_Slic3rPolygons(_clipper_do(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_)); } ExPolygons _clipper_ex(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_) { ClipperLib::PolyTree polytree = _clipper_do_polytree2(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_); return PolyTreeToExPolygons(polytree); } Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polylines &subject, const Polygons &clip, bool safety_offset_) { ClipperLib::Paths output; ClipperLib::PolyTreeToPaths(_clipper_do_pl(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_), output); return ClipperPaths_to_Slic3rPolylines(output); } Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_) { // transform input polygons into polylines Polylines polylines; polylines.reserve(subject.size()); for (Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon) polylines.push_back(*polygon); // implicit call to split_at_first_point() // perform clipping Polylines retval = _clipper_pl(clipType, polylines, clip, safety_offset_); /* If the split_at_first_point() call above happens to split the polygon inside the clipping area we would get two consecutive polylines instead of a single one, so we go through them in order to recombine continuous polylines. */ for (size_t i = 0; i < retval.size(); ++i) { for (size_t j = i+1; j < retval.size(); ++j) { if (retval[i].points.back().coincides_with(retval[j].points.front())) { /* If last point of i coincides with first point of j, append points of j to i and delete j */ retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end()); retval.erase(retval.begin() + j); --j; } else if (retval[i].points.front().coincides_with(retval[j].points.back())) { /* If first point of i coincides with last point of j, prepend points of j to i and delete j */ retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1); retval.erase(retval.begin() + j); --j; } else if (retval[i].points.front().coincides_with(retval[j].points.front())) { /* Since Clipper does not preserve orientation of polylines, also check the case when first point of i coincides with first point of j. */ retval[j].reverse(); retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1); retval.erase(retval.begin() + j); --j; } else if (retval[i].points.back().coincides_with(retval[j].points.back())) { /* Since Clipper does not preserve orientation of polylines, also check the case when last point of i coincides with last point of j. */ retval[j].reverse(); retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end()); retval.erase(retval.begin() + j); --j; } } } return retval; } Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip, bool safety_offset_) { // convert Lines to Polylines Polylines polylines; polylines.reserve(subject.size()); for (Lines::const_iterator line = subject.begin(); line != subject.end(); ++line) polylines.push_back(*line); // perform operation polylines = _clipper_pl(clipType, polylines, clip, safety_offset_); // convert Polylines to Lines Lines retval; for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) retval.push_back(*polyline); return retval; } ClipperLib::PolyTree union_pt(const Polygons &subject, bool safety_offset_) { return _clipper_do(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_); } Polygons union_pt_chained(const Polygons &subject, bool safety_offset_) { ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_); Polygons retval; traverse_pt(polytree.Childs, &retval); return retval; } void traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* retval) { /* use a nearest neighbor search to order these children TODO: supply start_near to chained_path() too? */ // collect ordering points Points ordering_points; ordering_points.reserve(nodes.size()); for (ClipperLib::PolyNodes::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { Point p((*it)->Contour.front().X, (*it)->Contour.front().Y); ordering_points.push_back(p); } // perform the ordering ClipperLib::PolyNodes ordered_nodes; Slic3r::Geometry::chained_path_items(ordering_points, nodes, ordered_nodes); // push results recursively for (ClipperLib::PolyNodes::iterator it = ordered_nodes.begin(); it != ordered_nodes.end(); ++it) { // traverse the next depth traverse_pt((*it)->Childs, retval); retval->push_back(ClipperPath_to_Slic3rPolygon((*it)->Contour)); if ((*it)->IsHole()) retval->back().reverse(); // ccw } } Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear) { // convert into Clipper polygons ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); ClipperLib::Paths output; if (preserve_collinear) { ClipperLib::Clipper c; c.PreserveCollinear(true); c.StrictlySimple(true); c.AddPaths(input_subject, ClipperLib::ptSubject, true); c.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } else { ClipperLib::SimplifyPolygons(input_subject, output, ClipperLib::pftNonZero); } // convert into Slic3r polygons return ClipperPaths_to_Slic3rPolygons(output); } ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear) { if (!preserve_collinear) { return union_ex(simplify_polygons(subject, preserve_collinear)); } // convert into Clipper polygons ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); ClipperLib::PolyTree polytree; ClipperLib::Clipper c; c.PreserveCollinear(true); c.StrictlySimple(true); c.AddPaths(input_subject, ClipperLib::ptSubject, true); c.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); // convert into ExPolygons return PolyTreeToExPolygons(polytree); } void safety_offset(ClipperLib::Paths* paths) { PROFILE_FUNC(); // scale input scaleClipperPolygons(*paths); // perform offset (delta = scale 1e-05) ClipperLib::ClipperOffset co; #ifdef CLIPPER_UTILS_DEBUG if (clipper_export_enabled) { static int iRun = 0; export_clipper_input_polygons_bin(debug_out_path("safety_offset-polygons-%d", ++iRun).c_str(), *paths, ClipperLib::Paths()); } #endif /* CLIPPER_UTILS_DEBUG */ ClipperLib::Paths out; for (size_t i = 0; i < paths->size(); ++ i) { ClipperLib::Path &path = (*paths)[i]; co.Clear(); co.MiterLimit = 2; bool ccw = ClipperLib::Orientation(path); if (! ccw) std::reverse(path.begin(), path.end()); { PROFILE_BLOCK(safety_offset_AddPaths); co.AddPath((*paths)[i], ClipperLib::jtMiter, ClipperLib::etClosedPolygon); } { PROFILE_BLOCK(safety_offset_Execute); // offset outside by 10um ClipperLib::Paths out_this; co.Execute(out_this, ccw ? 10.f * float(CLIPPER_OFFSET_SCALE) : -10.f * float(CLIPPER_OFFSET_SCALE)); if (! ccw) { // Reverse the resulting contours once again. for (ClipperLib::Paths::iterator it = out_this.begin(); it != out_this.end(); ++ it) std::reverse(it->begin(), it->end()); } if (out.empty()) out = std::move(out_this); else std::move(std::begin(out_this), std::end(out_this), std::back_inserter(out)); } } *paths = std::move(out); // unscale output unscaleClipperPolygons(*paths); } Polygons top_level_islands(const Slic3r::Polygons &polygons) { // init Clipper ClipperLib::Clipper clipper; clipper.Clear(); // perform union clipper.AddPaths(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::ptSubject, true); ClipperLib::PolyTree polytree; clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // Convert only the top level islands to the output. Polygons out; out.reserve(polytree.ChildCount()); for (int i = 0; i < polytree.ChildCount(); ++i) out.push_back(ClipperPath_to_Slic3rPolygon(polytree.Childs[i]->Contour)); return out; } }Slic3r-version_1.39.1/xs/src/libslic3r/ClipperUtils.hpp000066400000000000000000000274731324354444700227770ustar00rootroot00000000000000#ifndef slic3r_ClipperUtils_hpp_ #define slic3r_ClipperUtils_hpp_ #include #include "clipper.hpp" #include "ExPolygon.hpp" #include "Polygon.hpp" #include "Surface.hpp" // import these wherever we're included using ClipperLib::jtMiter; using ClipperLib::jtRound; using ClipperLib::jtSquare; // Factor to convert from coord_t (which is int32) to an int64 type used by the Clipper library // for general offsetting (the offset(), offset2(), offset_ex() functions) and for the safety offset, // which is optionally executed by other functions (union, intersection, diff). // This scaling (cca 130t) is applied over the usual SCALING_FACTOR. // By the way, is the scalling for offset needed at all? // The reason to apply this scaling may be to match the resolution of the double mantissa. #define CLIPPER_OFFSET_POWER_OF_2 17 // 2^17=131072 #define CLIPPER_OFFSET_SCALE (1 << CLIPPER_OFFSET_POWER_OF_2) #define CLIPPER_OFFSET_SCALE_ROUNDING_DELTA ((1 << (CLIPPER_OFFSET_POWER_OF_2 - 1)) - 1) #define CLIPPER_MAX_COORD_UNSCALED (ClipperLib::hiRange / CLIPPER_OFFSET_SCALE) namespace Slic3r { //----------------------------------------------------------- // legacy code from Clipper documentation void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, Slic3r::ExPolygons& expolygons); void PolyTreeToExPolygons(ClipperLib::PolyTree& polytree, Slic3r::ExPolygons& expolygons); //----------------------------------------------------------- ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input); ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input); ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input); Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input); Slic3r::Polyline ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input); Slic3r::Polygons ClipperPaths_to_Slic3rPolygons(const ClipperLib::Paths &input); Slic3r::Polylines ClipperPaths_to_Slic3rPolylines(const ClipperLib::Paths &input); Slic3r::ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input); // offset Polygons ClipperLib::Paths _offset(ClipperLib::Path &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit); ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit); inline Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polygon), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } inline Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } // offset Polylines inline Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3) { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polyline), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } inline Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3) { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polylines), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } // offset expolygons and surfaces ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit); ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit); inline Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rPolygons(_offset(expolygon, delta, joinType, miterLimit)); } inline Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rPolygons(_offset(expolygons, delta, joinType, miterLimit)); } inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rExPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polygon), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rExPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } inline Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygon, delta, joinType, miterLimit)); } inline Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygons, delta, joinType, miterLimit)); } ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); Slic3r::ExPolygons _clipper_ex(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); // diff inline Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) { return _clipper(ClipperLib::ctDifference, subject, clip, safety_offset_); } inline Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) { return _clipper_ex(ClipperLib::ctDifference, subject, clip, safety_offset_); } inline Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) { return _clipper_ex(ClipperLib::ctDifference, to_polygons(subject), to_polygons(clip), safety_offset_); } inline Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) { return _clipper(ClipperLib::ctDifference, to_polygons(subject), to_polygons(clip), safety_offset_); } inline Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) { return _clipper_pl(ClipperLib::ctDifference, subject, clip, safety_offset_); } inline Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) { return _clipper_pl(ClipperLib::ctDifference, subject, clip, safety_offset_); } inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) { return _clipper_ln(ClipperLib::ctDifference, subject, clip, safety_offset_); } // intersection inline Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) { return _clipper(ClipperLib::ctIntersection, subject, clip, safety_offset_); } inline Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) { return _clipper_ex(ClipperLib::ctIntersection, subject, clip, safety_offset_); } inline Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) { return _clipper_ex(ClipperLib::ctIntersection, to_polygons(subject), to_polygons(clip), safety_offset_); } inline Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) { return _clipper(ClipperLib::ctIntersection, to_polygons(subject), to_polygons(clip), safety_offset_); } inline Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) { return _clipper_pl(ClipperLib::ctIntersection, subject, clip, safety_offset_); } inline Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) { return _clipper_pl(ClipperLib::ctIntersection, subject, clip, safety_offset_); } inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) { return _clipper_ln(ClipperLib::ctIntersection, subject, clip, safety_offset_); } inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) { Slic3r::Lines lines; lines.emplace_back(subject); return _clipper_ln(ClipperLib::ctIntersection, lines, clip, safety_offset_); } // union inline Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool safety_offset_ = false) { return _clipper(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_); } inline Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, bool safety_offset_ = false) { return _clipper(ClipperLib::ctUnion, subject, subject2, safety_offset_); } inline Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool safety_offset_ = false) { return _clipper_ex(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_); } inline Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, bool safety_offset_ = false) { return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_); } inline Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_offset_ = false) { return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_); } ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject, bool safety_offset_ = false); Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false); void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval); /* OTHER */ Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false); Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject, bool preserve_collinear = false); void safety_offset(ClipperLib::Paths* paths); Polygons top_level_islands(const Slic3r::Polygons &polygons); } #endifSlic3r-version_1.39.1/xs/src/libslic3r/Config.cpp000066400000000000000000000457541324354444700215620ustar00rootroot00000000000000#include "Config.hpp" #include "Utils.hpp" #include #include #include #include // std::runtime_error #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Slic3r { std::string escape_string_cstyle(const std::string &str) { // Allocate a buffer twice the input string length, // so the output will fit even if all input characters get escaped. std::vector out(str.size() * 2, 0); char *outptr = out.data(); for (size_t i = 0; i < str.size(); ++ i) { char c = str[i]; if (c == '\n' || c == '\r') { (*outptr ++) = '\\'; (*outptr ++) = 'n'; } else (*outptr ++) = c; } return std::string(out.data(), outptr - out.data()); } std::string escape_strings_cstyle(const std::vector &strs) { // 1) Estimate the output buffer size to avoid buffer reallocation. size_t outbuflen = 0; for (size_t i = 0; i < strs.size(); ++ i) // Reserve space for every character escaped + quotes + semicolon. outbuflen += strs[i].size() * 2 + 3; // 2) Fill in the buffer. std::vector out(outbuflen, 0); char *outptr = out.data(); for (size_t j = 0; j < strs.size(); ++ j) { if (j > 0) // Separate the strings. (*outptr ++) = ';'; const std::string &str = strs[j]; // Is the string simple or complex? Complex string contains spaces, tabs, new lines and other // escapable characters. Empty string shall be quoted as well, if it is the only string in strs. bool should_quote = strs.size() == 1 && str.empty(); for (size_t i = 0; i < str.size(); ++ i) { char c = str[i]; if (c == ' ' || c == '\t' || c == '\\' || c == '"' || c == '\r' || c == '\n') { should_quote = true; break; } } if (should_quote) { (*outptr ++) = '"'; for (size_t i = 0; i < str.size(); ++ i) { char c = str[i]; if (c == '\\' || c == '"') { (*outptr ++) = '\\'; (*outptr ++) = c; } else if (c == '\n' || c == '\r') { (*outptr ++) = '\\'; (*outptr ++) = 'n'; } else (*outptr ++) = c; } (*outptr ++) = '"'; } else { memcpy(outptr, str.data(), str.size()); outptr += str.size(); } } return std::string(out.data(), outptr - out.data()); } bool unescape_string_cstyle(const std::string &str, std::string &str_out) { std::vector out(str.size(), 0); char *outptr = out.data(); for (size_t i = 0; i < str.size(); ++ i) { char c = str[i]; if (c == '\\') { if (++ i == str.size()) return false; c = str[i]; if (c == 'n') (*outptr ++) = '\n'; } else (*outptr ++) = c; } str_out.assign(out.data(), outptr - out.data()); return true; } bool unescape_strings_cstyle(const std::string &str, std::vector &out) { if (str.empty()) return true; size_t i = 0; for (;;) { // Skip white spaces. char c = str[i]; while (c == ' ' || c == '\t') { if (++ i == str.size()) return true; c = str[i]; } // Start of a word. std::vector buf; buf.reserve(16); // Is it enclosed in quotes? c = str[i]; if (c == '"') { // Complex case, string is enclosed in quotes. for (++ i; i < str.size(); ++ i) { c = str[i]; if (c == '"') { // End of string. break; } if (c == '\\') { if (++ i == str.size()) return false; c = str[i]; if (c == 'n') c = '\n'; } buf.push_back(c); } if (i == str.size()) return false; ++ i; } else { for (; i < str.size(); ++ i) { c = str[i]; if (c == ';') break; buf.push_back(c); } } // Store the string into the output vector. out.push_back(std::string(buf.data(), buf.size())); if (i == str.size()) return true; // Skip white spaces. c = str[i]; while (c == ' ' || c == '\t') { if (++ i == str.size()) // End of string. This is correct. return true; c = str[i]; } if (c != ';') return false; if (++ i == str.size()) { // Emit one additional empty string. out.push_back(std::string()); return true; } } } void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent) { // loop through options and apply them for (const t_config_option_key &opt_key : keys) { // Create a new option with default value for the key. // If the key is not in the parameter definition, or this ConfigBase is a static type and it does not support the parameter, // an exception is thrown if not ignore_nonexistent. ConfigOption *my_opt = this->option(opt_key, true); if (my_opt == nullptr) { // opt_key does not exist in this ConfigBase and it cannot be created, because it is not defined by this->def(). // This is only possible if other is of DynamicConfig type. if (ignore_nonexistent) continue; throw UnknownOptionException(); } const ConfigOption *other_opt = other.option(opt_key); if (other_opt != nullptr) my_opt->set(other_opt); } } // this will *ignore* options not present in both configs t_config_option_keys ConfigBase::diff(const ConfigBase &other) const { t_config_option_keys diff; for (const t_config_option_key &opt_key : this->keys()) { const ConfigOption *this_opt = this->option(opt_key); const ConfigOption *other_opt = other.option(opt_key); if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) diff.emplace_back(opt_key); } return diff; } std::string ConfigBase::serialize(const t_config_option_key &opt_key) const { const ConfigOption* opt = this->option(opt_key); assert(opt != nullptr); return opt->serialize(); } bool ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append) { t_config_option_key opt_key = opt_key_src; std::string value = value_src; // Both opt_key and value may be modified by _handle_legacy(). // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by _handle_legacy(). this->handle_legacy(opt_key, value); if (opt_key.empty()) // Ignore the option. return true; return this->set_deserialize_raw(opt_key, value, append); } bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, bool append) { t_config_option_key opt_key = opt_key_src; // Try to deserialize the option by its name. const ConfigDef *def = this->def(); if (def == nullptr) throw NoDefinitionException(); const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) { // If we didn't find an option, look for any other option having this as an alias. for (const auto &opt : def->options) { for (const t_config_option_key &opt_key2 : opt.second.aliases) { if (opt_key2 == opt_key) { opt_key = opt.first; optdef = &opt.second; break; } } if (optdef != nullptr) break; } if (optdef == nullptr) throw UnknownOptionException(); } if (! optdef->shortcut.empty()) { // Aliasing for example "solid_layers" to "top_solid_layers" and "bottom_solid_layers". for (const t_config_option_key &shortcut : optdef->shortcut) // Recursive call. if (! this->set_deserialize_raw(shortcut, value, append)) return false; return true; } ConfigOption *opt = this->option(opt_key, true); assert(opt != nullptr); return opt->deserialize(value, append); } // Return an absolute value of a possibly relative config variable. // For example, return absolute infill extrusion width, either from an absolute value, or relative to the layer height. double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const { // Get stored option value. const ConfigOption *raw_opt = this->option(opt_key); assert(raw_opt != nullptr); if (raw_opt->type() == coFloat) return static_cast(raw_opt)->value; if (raw_opt->type() == coFloatOrPercent) { // Get option definition. const ConfigDef *def = this->def(); if (def == nullptr) throw NoDefinitionException(); const ConfigOptionDef *opt_def = def->get(opt_key); assert(opt_def != nullptr); // Compute absolute value over the absolute value of the base option. //FIXME there are some ratio_over chains, which end with empty ratio_with. // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly. return opt_def->ratio_over.empty() ? 0. : static_cast(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over)); } throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); } // Return an absolute value of a possibly relative config variable. // For example, return absolute infill extrusion width, either from an absolute value, or relative to a provided value. double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double ratio_over) const { // Get stored option value. const ConfigOption *raw_opt = this->option(opt_key); assert(raw_opt != nullptr); if (raw_opt->type() != coFloatOrPercent) throw std::runtime_error("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent"); // Compute absolute value. return static_cast(raw_opt)->get_abs_value(ratio_over); } void ConfigBase::setenv_() { t_config_option_keys opt_keys = this->keys(); for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) { // prepend the SLIC3R_ prefix std::ostringstream ss; ss << "SLIC3R_"; ss << *it; std::string envname = ss.str(); // capitalize environment variable name for (size_t i = 0; i < envname.size(); ++i) envname[i] = (envname[i] <= 'z' && envname[i] >= 'a') ? envname[i]-('a'-'A') : envname[i]; boost::nowide::setenv(envname.c_str(), this->serialize(*it).c_str(), 1); } } void ConfigBase::load(const std::string &file) { if (boost::iends_with(file, ".gcode") || boost::iends_with(file, ".g")) this->load_from_gcode(file); else this->load_from_ini(file); } void ConfigBase::load_from_ini(const std::string &file) { boost::property_tree::ptree tree; boost::nowide::ifstream ifs(file); boost::property_tree::read_ini(ifs, tree); this->load(tree); } void ConfigBase::load(const boost::property_tree::ptree &tree) { for (const boost::property_tree::ptree::value_type &v : tree) { try { t_config_option_key opt_key = v.first; this->set_deserialize(opt_key, v.second.get_value()); } catch (UnknownOptionException & /* e */) { // ignore } } } // Load the config keys from the tail of a G-code. void ConfigBase::load_from_gcode(const std::string &file) { // 1) Read a 64k block from the end of the G-code. boost::nowide::ifstream ifs(file); { const char slic3r_gcode_header[] = "; generated by Slic3r "; std::string firstline; std::getline(ifs, firstline); if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0) throw std::runtime_error("Not a Slic3r generated g-code."); } ifs.seekg(0, ifs.end); auto file_length = ifs.tellg(); auto data_length = std::min(65535, file_length); ifs.seekg(file_length - data_length, ifs.beg); std::vector data(size_t(data_length) + 1, 0); ifs.read(data.data(), data_length); ifs.close(); // 2) Walk line by line in reverse until a non-configuration key appears. char *data_start = data.data(); // boost::nowide::ifstream seems to cook the text data somehow, so less then the 64k of characters may be retrieved. char *end = data_start + strlen(data.data()); size_t num_key_value_pairs = 0; for (;;) { // Extract next line. for (-- end; end > data_start && (*end == '\r' || *end == '\n'); -- end); if (end == data_start) break; char *start = end; *(++ end) = 0; for (; start > data_start && *start != '\r' && *start != '\n'; -- start); if (start == data_start) break; // Extracted a line from start to end. Extract the key = value pair. if (end - (++ start) < 10 || start[0] != ';' || start[1] != ' ') break; char *key = start + 2; if (! (*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z')) // A key must start with a letter. break; char *sep = strchr(key, '='); if (sep == nullptr || sep[-1] != ' ' || sep[1] != ' ') break; char *value = sep + 2; if (value > end) break; char *key_end = sep - 1; if (key_end - key < 3) break; *key_end = 0; // The key may contain letters, digits and underscores. for (char *c = key; c != key_end; ++ c) if (! ((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || *c == '_')) { key = nullptr; break; } if (key == nullptr) break; try { this->set_deserialize(key, value); ++ num_key_value_pairs; } catch (UnknownOptionException & /* e */) { // ignore } end = start; } if (num_key_value_pairs < 90) { char msg[80]; sprintf(msg, "Suspiciously low number of configuration values extracted: %d", num_key_value_pairs); throw std::runtime_error(msg); } } void ConfigBase::save(const std::string &file) const { boost::nowide::ofstream c; c.open(file, std::ios::out | std::ios::trunc); c << "# " << Slic3r::header_slic3r_generated() << std::endl; for (const std::string &opt_key : this->keys()) c << opt_key << " = " << this->serialize(opt_key) << std::endl; c.close(); } bool DynamicConfig::operator==(const DynamicConfig &rhs) const { t_options_map::const_iterator it1 = this->options.begin(); t_options_map::const_iterator it1_end = this->options.end(); t_options_map::const_iterator it2 = rhs.options.begin(); t_options_map::const_iterator it2_end = rhs.options.end(); for (; it1 != it1_end && it2 != it2_end; ++ it1, ++ it2) if (*it1->second != *it2->second) return false; return it1 == it1_end && it2 == it2_end; } ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) { t_options_map::iterator it = options.find(opt_key); if (it != options.end()) // Option was found. return it->second; if (! create) // Option was not found and a new option shall not be created. return nullptr; // Try to create a new ConfigOption. const ConfigDef *def = this->def(); if (def == nullptr) throw NoDefinitionException(); const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) // throw std::runtime_error(std::string("Invalid option name: ") + opt_key); // Let the parent decide what to do if the opt_key is not defined by this->def(). return nullptr; ConfigOption *opt = nullptr; switch (optdef->type) { case coFloat: opt = new ConfigOptionFloat(); break; case coFloats: opt = new ConfigOptionFloats(); break; case coInt: opt = new ConfigOptionInt(); break; case coInts: opt = new ConfigOptionInts(); break; case coString: opt = new ConfigOptionString(); break; case coStrings: opt = new ConfigOptionStrings(); break; case coPercent: opt = new ConfigOptionPercent(); break; case coPercents: opt = new ConfigOptionPercents(); break; case coFloatOrPercent: opt = new ConfigOptionFloatOrPercent(); break; case coPoint: opt = new ConfigOptionPoint(); break; case coPoints: opt = new ConfigOptionPoints(); break; case coBool: opt = new ConfigOptionBool(); break; case coBools: opt = new ConfigOptionBools(); break; case coEnum: opt = new ConfigOptionEnumGeneric(optdef->enum_keys_map); break; default: throw std::runtime_error(std::string("Unknown option type for option ") + opt_key); } this->options[opt_key] = opt; return opt; } t_config_option_keys DynamicConfig::keys() const { t_config_option_keys keys; keys.reserve(this->options.size()); for (const auto &opt : this->options) keys.emplace_back(opt.first); return keys; } void StaticConfig::set_defaults() { // use defaults from definition auto *defs = this->def(); if (defs != nullptr) { for (const std::string &key : this->keys()) { const ConfigOptionDef *def = defs->get(key); ConfigOption *opt = this->option(key); if (def != nullptr && opt != nullptr && def->default_value != nullptr) opt->set(def->default_value); } } } t_config_option_keys StaticConfig::keys() const { t_config_option_keys keys; assert(this->def != nullptr); for (const auto &opt_def : this->def()->options) if (this->option(opt_def.first) != nullptr) keys.push_back(opt_def.first); return keys; } } Slic3r-version_1.39.1/xs/src/libslic3r/Config.hpp000066400000000000000000001532041324354444700215550ustar00rootroot00000000000000#ifndef slic3r_Config_hpp_ #define slic3r_Config_hpp_ #include #include #include #include #include #include #include #include #include #include "libslic3r.h" #include "Point.hpp" #include namespace Slic3r { // Name of the configuration option. typedef std::string t_config_option_key; typedef std::vector t_config_option_keys; extern std::string escape_string_cstyle(const std::string &str); extern std::string escape_strings_cstyle(const std::vector &strs); extern bool unescape_string_cstyle(const std::string &str, std::string &out); extern bool unescape_strings_cstyle(const std::string &str, std::vector &out); // Type of a configuration value. enum ConfigOptionType { coVectorType = 0x4000, coNone = 0, // single float coFloat = 1, // vector of floats coFloats = coFloat + coVectorType, // single int coInt = 2, // vector of ints coInts = coInt + coVectorType, // single string coString = 3, // vector of strings coStrings = coString + coVectorType, // percent value. Currently only used for infill. coPercent = 4, // percents value. Currently used for retract before wipe only. coPercents = coPercent + coVectorType, // a fraction or an absolute value coFloatOrPercent = 5, // single 2d point. Currently not used. coPoint = 6, // vector of 2d points. Currently used for the definition of the print bed and for the extruder offsets. coPoints = coPoint + coVectorType, // single boolean value coBool = 7, // vector of boolean values coBools = coBool + coVectorType, // a generic enum coEnum = 8, }; // A generic value of a configuration option. class ConfigOption { public: virtual ~ConfigOption() {} virtual ConfigOptionType type() const = 0; virtual std::string serialize() const = 0; virtual bool deserialize(const std::string &str, bool append = false) = 0; virtual ConfigOption* clone() const = 0; // Set a value from a ConfigOption. The two options should be compatible. virtual void set(const ConfigOption *option) = 0; virtual int getInt() const { throw std::runtime_error("Calling ConfigOption::getInt on a non-int ConfigOption"); return 0; } virtual double getFloat() const { throw std::runtime_error("Calling ConfigOption::getFloat on a non-float ConfigOption"); return 0; } virtual bool getBool() const { throw std::runtime_error("Calling ConfigOption::getBool on a non-boolean ConfigOption"); return 0; } virtual void setInt(int /* val */) { throw std::runtime_error("Calling ConfigOption::setInt on a non-int ConfigOption"); } virtual bool operator==(const ConfigOption &rhs) const = 0; bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); } bool is_scalar() const { return (int(this->type()) & int(coVectorType)) == 0; } bool is_vector() const { return ! this->is_scalar(); } }; typedef ConfigOption* ConfigOptionPtr; typedef const ConfigOption* ConfigOptionConstPtr; // Value of a single valued option (bool, int, float, string, point, enum) template class ConfigOptionSingle : public ConfigOption { public: T value; explicit ConfigOptionSingle(T value) : value(value) {} operator T() const { return this->value; } void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type"); assert(dynamic_cast*>(rhs)); this->value = static_cast*>(rhs)->value; } bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) throw std::runtime_error("ConfigOptionSingle: Comparing incompatible types"); assert(dynamic_cast*>(&rhs)); return this->value == static_cast*>(&rhs)->value; } bool operator==(const T &rhs) const { return this->value == rhs; } bool operator!=(const T &rhs) const { return this->value != rhs; } }; // Value of a vector valued option (bools, ints, floats, strings, points) class ConfigOptionVectorBase : public ConfigOption { public: // Currently used only to initialize the PlaceholderParser. virtual std::vector vserialize() const = 0; // Set from a vector of ConfigOptions. // If the rhs ConfigOption is scalar, then its value is used, // otherwise for each of rhs, the first value of a vector is used. // This function is useful to collect values for multiple extrder / filament settings. virtual void set(const std::vector &rhs) = 0; // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions. // This function is useful to split values from multiple extrder / filament settings into separate configurations. virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0; virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0; // Get size of this vector. virtual size_t size() const = 0; // Is this vector empty? virtual bool empty() const = 0; protected: // Used to verify type compatibility when assigning to / from a scalar ConfigOption. ConfigOptionType scalar_type() const { return static_cast(this->type() - coVectorType); } }; // Value of a vector valued option (bools, ints, floats, strings, points), template template class ConfigOptionVector : public ConfigOptionVectorBase { public: ConfigOptionVector() {} explicit ConfigOptionVector(size_t n, const T &value) : values(n, value) {} explicit ConfigOptionVector(std::initializer_list il) : values(std::move(il)) {} explicit ConfigOptionVector(const std::vector &values) : values(values) {} explicit ConfigOptionVector(std::vector &&values) : values(std::move(values)) {} std::vector values; void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type"); assert(dynamic_cast*>(rhs)); this->values = static_cast*>(rhs)->values; } // Set from a vector of ConfigOptions. // If the rhs ConfigOption is scalar, then its value is used, // otherwise for each of rhs, the first value of a vector is used. // This function is useful to collect values for multiple extrder / filament settings. void set(const std::vector &rhs) override { this->values.clear(); this->values.reserve(rhs.size()); for (const ConfigOption *opt : rhs) { if (opt->type() == this->type()) { auto other = static_cast*>(opt); if (other->values.empty()) throw std::runtime_error("ConfigOptionVector::set(): Assigning from an empty vector"); this->values.emplace_back(other->values.front()); } else if (opt->type() == this->scalar_type()) this->values.emplace_back(static_cast*>(opt)->value); else throw std::runtime_error("ConfigOptionVector::set():: Assigning an incompatible type"); } } // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions. // This function is useful to split values from multiple extrder / filament settings into separate configurations. void set_at(const ConfigOption *rhs, size_t i, size_t j) override { // It is expected that the vector value has at least one value, which is the default, if not overwritten. assert(! this->values.empty()); if (this->values.size() <= i) { // Resize this vector, fill in the new vector fields with the copy of the first field. T v = this->values.front(); this->values.resize(i + 1, v); } if (rhs->type() == this->type()) { // Assign the first value of the rhs vector. auto other = static_cast*>(rhs); if (other->values.empty()) throw std::runtime_error("ConfigOptionVector::set_at(): Assigning from an empty vector"); this->values[i] = other->get_at(j); } else if (rhs->type() == this->scalar_type()) this->values[i] = static_cast*>(rhs)->value; else throw std::runtime_error("ConfigOptionVector::set_at(): Assigning an incompatible type"); } T& get_at(size_t i) { assert(! this->values.empty()); return (i < this->values.size()) ? this->values[i] : this->values.front(); } const T& get_at(size_t i) const { return const_cast*>(this)->get_at(i); } // Resize this vector by duplicating the last value. // If the current vector is empty, the default value is used instead. void resize(size_t n, const ConfigOption *opt_default = nullptr) override { assert(opt_default == nullptr || opt_default->is_vector()); // assert(opt_default == nullptr || dynamic_cast>(opt_default)); assert(! this->values.empty() || opt_default != nullptr); if (n == 0) this->values.clear(); else if (n < this->values.size()) this->values.erase(this->values.begin() + n, this->values.end()); else if (n > this->values.size()) { if (this->values.empty()) { if (opt_default == nullptr) throw std::runtime_error("ConfigOptionVector::resize(): No default value provided."); if (opt_default->type() != this->type()) throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type."); this->values.resize(n, static_cast*>(opt_default)->values.front()); } else { // Resize by duplicating the last value. this->values.resize(n, this->values.back()); } } } size_t size() const override { return this->values.size(); } bool empty() const override { return this->values.empty(); } bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) throw std::runtime_error("ConfigOptionVector: Comparing incompatible types"); assert(dynamic_cast*>(&rhs)); return this->values == static_cast*>(&rhs)->values; } bool operator==(const std::vector &rhs) const { return this->values == rhs; } bool operator!=(const std::vector &rhs) const { return this->values != rhs; } }; class ConfigOptionFloat : public ConfigOptionSingle { public: ConfigOptionFloat() : ConfigOptionSingle(0) {} explicit ConfigOptionFloat(double _value) : ConfigOptionSingle(_value) {} static ConfigOptionType static_type() { return coFloat; } ConfigOptionType type() const override { return static_type(); } double getFloat() const override { return this->value; } ConfigOption* clone() const override { return new ConfigOptionFloat(*this); } bool operator==(const ConfigOptionFloat &rhs) const { return this->value == rhs.value; } std::string serialize() const override { std::ostringstream ss; ss << this->value; return ss.str(); } bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); std::istringstream iss(str); iss >> this->value; return !iss.fail(); } ConfigOptionFloat& operator=(const ConfigOption *opt) { this->set(opt); return *this; } }; class ConfigOptionFloats : public ConfigOptionVector { public: ConfigOptionFloats() : ConfigOptionVector() {} explicit ConfigOptionFloats(size_t n, double value) : ConfigOptionVector(n, value) {} explicit ConfigOptionFloats(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} static ConfigOptionType static_type() { return coFloats; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionFloats(*this); } bool operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; } std::string serialize() const override { std::ostringstream ss; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ","; ss << *it; } return ss.str(); } std::vector vserialize() const override { std::vector vv; vv.reserve(this->values.size()); for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { std::ostringstream ss; ss << *it; vv.push_back(ss.str()); } return vv; } bool deserialize(const std::string &str, bool append = false) override { if (! append) this->values.clear(); std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { std::istringstream iss(item_str); double value; iss >> value; this->values.push_back(value); } return true; } ConfigOptionFloats& operator=(const ConfigOption *opt) { this->set(opt); return *this; } }; class ConfigOptionInt : public ConfigOptionSingle { public: ConfigOptionInt() : ConfigOptionSingle(0) {} explicit ConfigOptionInt(int value) : ConfigOptionSingle(value) {} explicit ConfigOptionInt(double _value) : ConfigOptionSingle(int(floor(_value + 0.5))) {} static ConfigOptionType static_type() { return coInt; } ConfigOptionType type() const override { return static_type(); } int getInt() const override { return this->value; } void setInt(int val) { this->value = val; } ConfigOption* clone() const override { return new ConfigOptionInt(*this); } bool operator==(const ConfigOptionInt &rhs) const { return this->value == rhs.value; } std::string serialize() const override { std::ostringstream ss; ss << this->value; return ss.str(); } bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); std::istringstream iss(str); iss >> this->value; return !iss.fail(); } ConfigOptionInt& operator=(const ConfigOption *opt) { this->set(opt); return *this; } }; class ConfigOptionInts : public ConfigOptionVector { public: ConfigOptionInts() : ConfigOptionVector() {} explicit ConfigOptionInts(size_t n, int value) : ConfigOptionVector(n, value) {} explicit ConfigOptionInts(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} static ConfigOptionType static_type() { return coInts; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionInts(*this); } ConfigOptionInts& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionInts &rhs) const { return this->values == rhs.values; } std::string serialize() const override { std::ostringstream ss; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ","; ss << *it; } return ss.str(); } std::vector vserialize() const override { std::vector vv; vv.reserve(this->values.size()); for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { std::ostringstream ss; ss << *it; vv.push_back(ss.str()); } return vv; } bool deserialize(const std::string &str, bool append = false) override { if (! append) this->values.clear(); std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { std::istringstream iss(item_str); int value; iss >> value; this->values.push_back(value); } return true; } }; class ConfigOptionString : public ConfigOptionSingle { public: ConfigOptionString() : ConfigOptionSingle("") {} explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle(value) {} static ConfigOptionType static_type() { return coString; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionString(*this); } ConfigOptionString& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionString &rhs) const { return this->value == rhs.value; } std::string serialize() const override { return escape_string_cstyle(this->value); } bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); return unescape_string_cstyle(str, this->value); } }; // semicolon-separated strings class ConfigOptionStrings : public ConfigOptionVector { public: ConfigOptionStrings() : ConfigOptionVector() {} explicit ConfigOptionStrings(size_t n, const std::string &value) : ConfigOptionVector(n, value) {} explicit ConfigOptionStrings(const std::vector &values) : ConfigOptionVector(values) {} explicit ConfigOptionStrings(std::vector &&values) : ConfigOptionVector(std::move(values)) {} explicit ConfigOptionStrings(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} static ConfigOptionType static_type() { return coStrings; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionStrings(*this); } ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionStrings &rhs) const { return this->values == rhs.values; } std::string serialize() const override { return escape_strings_cstyle(this->values); } std::vector vserialize() const override { return this->values; } bool deserialize(const std::string &str, bool append = false) override { if (! append) this->values.clear(); return unescape_strings_cstyle(str, this->values); } }; class ConfigOptionPercent : public ConfigOptionFloat { public: ConfigOptionPercent() : ConfigOptionFloat(0) {} explicit ConfigOptionPercent(double _value) : ConfigOptionFloat(_value) {} static ConfigOptionType static_type() { return coPercent; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPercent(*this); } ConfigOptionPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionPercent &rhs) const { return this->value == rhs.value; } double get_abs_value(double ratio_over) const { return ratio_over * this->value / 100; } std::string serialize() const override { std::ostringstream ss; ss << this->value; std::string s(ss.str()); s += "%"; return s; } bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); // don't try to parse the trailing % since it's optional std::istringstream iss(str); iss >> this->value; return !iss.fail(); } }; class ConfigOptionPercents : public ConfigOptionFloats { public: ConfigOptionPercents() : ConfigOptionFloats() {} explicit ConfigOptionPercents(size_t n, double value) : ConfigOptionFloats(n, value) {} explicit ConfigOptionPercents(std::initializer_list il) : ConfigOptionFloats(std::move(il)) {} static ConfigOptionType static_type() { return coPercents; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPercents(*this); } ConfigOptionPercents& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionPercents &rhs) const { return this->values == rhs.values; } std::string serialize() const override { std::ostringstream ss; for (const auto &v : this->values) { if (&v != &this->values.front()) ss << ","; ss << v << "%"; } std::string str = ss.str(); return str; } std::vector vserialize() const override { std::vector vv; vv.reserve(this->values.size()); for (const auto v : this->values) { std::ostringstream ss; ss << v; std::string sout = ss.str() + "%"; vv.push_back(sout); } return vv; } bool deserialize(const std::string &str, bool append = false) override { if (! append) this->values.clear(); std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { std::istringstream iss(item_str); double value; // don't try to parse the trailing % since it's optional iss >> value; this->values.push_back(value); } return true; } }; class ConfigOptionFloatOrPercent : public ConfigOptionPercent { public: bool percent; ConfigOptionFloatOrPercent() : ConfigOptionPercent(0), percent(false) {} explicit ConfigOptionFloatOrPercent(double _value, bool _percent) : ConfigOptionPercent(_value), percent(_percent) {} static ConfigOptionType static_type() { return coFloatOrPercent; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionFloatOrPercent(*this); } ConfigOptionFloatOrPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) throw std::runtime_error("ConfigOptionFloatOrPercent: Comparing incompatible types"); assert(dynamic_cast(&rhs)); return *this == *static_cast(&rhs); } bool operator==(const ConfigOptionFloatOrPercent &rhs) const { return this->value == rhs.value && this->percent == rhs.percent; } double get_abs_value(double ratio_over) const { return this->percent ? (ratio_over * this->value / 100) : this->value; } void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) throw std::runtime_error("ConfigOptionFloatOrPercent: Assigning an incompatible type"); assert(dynamic_cast(rhs)); *this = *static_cast(rhs); } std::string serialize() const override { std::ostringstream ss; ss << this->value; std::string s(ss.str()); if (this->percent) s += "%"; return s; } bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); this->percent = str.find_first_of("%") != std::string::npos; std::istringstream iss(str); iss >> this->value; return !iss.fail(); } }; class ConfigOptionPoint : public ConfigOptionSingle { public: ConfigOptionPoint() : ConfigOptionSingle(Pointf(0,0)) {} explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle(value) {} static ConfigOptionType static_type() { return coPoint; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPoint(*this); } ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionPoint &rhs) const { return this->value == rhs.value; } std::string serialize() const override { std::ostringstream ss; ss << this->value.x; ss << ","; ss << this->value.y; return ss.str(); } bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); std::istringstream iss(str); iss >> this->value.x; iss.ignore(std::numeric_limits::max(), ','); iss.ignore(std::numeric_limits::max(), 'x'); iss >> this->value.y; return true; } }; class ConfigOptionPoints : public ConfigOptionVector { public: ConfigOptionPoints() : ConfigOptionVector() {} explicit ConfigOptionPoints(size_t n, const Pointf &value) : ConfigOptionVector(n, value) {} explicit ConfigOptionPoints(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} static ConfigOptionType static_type() { return coPoints; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionPoints(*this); } ConfigOptionPoints& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionPoints &rhs) const { return this->values == rhs.values; } std::string serialize() const override { std::ostringstream ss; for (Pointfs::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ","; ss << it->x; ss << "x"; ss << it->y; } return ss.str(); } std::vector vserialize() const override { std::vector vv; for (Pointfs::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { std::ostringstream ss; ss << *it; vv.push_back(ss.str()); } return vv; } bool deserialize(const std::string &str, bool append = false) override { if (! append) this->values.clear(); std::istringstream is(str); std::string point_str; while (std::getline(is, point_str, ',')) { Pointf point; std::istringstream iss(point_str); std::string coord_str; if (std::getline(iss, coord_str, 'x')) { std::istringstream(coord_str) >> point.x; if (std::getline(iss, coord_str, 'x')) { std::istringstream(coord_str) >> point.y; } } this->values.push_back(point); } return true; } }; class ConfigOptionBool : public ConfigOptionSingle { public: ConfigOptionBool() : ConfigOptionSingle(false) {} explicit ConfigOptionBool(bool _value) : ConfigOptionSingle(_value) {} static ConfigOptionType static_type() { return coBool; } ConfigOptionType type() const override { return static_type(); } bool getBool() const override { return this->value; } ConfigOption* clone() const override { return new ConfigOptionBool(*this); } ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionBool &rhs) const { return this->value == rhs.value; } std::string serialize() const override { return std::string(this->value ? "1" : "0"); } bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); this->value = (str.compare("1") == 0); return true; } }; class ConfigOptionBools : public ConfigOptionVector { public: ConfigOptionBools() : ConfigOptionVector() {} explicit ConfigOptionBools(size_t n, bool value) : ConfigOptionVector(n, (unsigned char)value) {} explicit ConfigOptionBools(std::initializer_list il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); } static ConfigOptionType static_type() { return coBools; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionBools(*this); } ConfigOptionBools& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionBools &rhs) const { return this->values == rhs.values; } bool& get_at(size_t i) { assert(! this->values.empty()); return *reinterpret_cast(&((i < this->values.size()) ? this->values[i] : this->values.front())); } //FIXME this smells, the parent class has the method declared returning (unsigned char&). bool get_at(size_t i) const { return bool((i < this->values.size()) ? this->values[i] : this->values.front()); } std::string serialize() const override { std::ostringstream ss; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { if (it - this->values.begin() != 0) ss << ","; ss << (*it ? "1" : "0"); } return ss.str(); } std::vector vserialize() const override { std::vector vv; for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { std::ostringstream ss; ss << (*it ? "1" : "0"); vv.push_back(ss.str()); } return vv; } bool deserialize(const std::string &str, bool append = false) override { if (! append) this->values.clear(); std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { this->values.push_back(item_str.compare("1") == 0); } return true; } }; // Map from an enum integer value to an enum name. typedef std::vector t_config_enum_names; // Map from an enum name to an enum integer value. typedef std::map t_config_enum_values; template class ConfigOptionEnum : public ConfigOptionSingle { public: // by default, use the first value (0) of the T enum type ConfigOptionEnum() : ConfigOptionSingle(static_cast(0)) {} explicit ConfigOptionEnum(T _value) : ConfigOptionSingle(_value) {} static ConfigOptionType static_type() { return coEnum; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionEnum(*this); } ConfigOptionEnum& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionEnum &rhs) const { return this->value == rhs.value; } std::string serialize() const override { const t_config_enum_names& names = ConfigOptionEnum::get_enum_names(); assert(static_cast(this->value) < int(names.size())); return names[static_cast(this->value)]; } bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); auto it = enum_keys_map.find(str); if (it == enum_keys_map.end()) return false; this->value = static_cast(it->second); return true; } static bool has(T value) { for (const std::pair &kvp : ConfigOptionEnum::get_enum_values()) if (kvp.second == value) return true; return false; } // Map from an enum name to an enum integer value. static t_config_enum_names& get_enum_names() { static t_config_enum_names names; if (names.empty()) { // Initialize the map. const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); int cnt = 0; for (const std::pair &kvp : enum_keys_map) cnt = std::max(cnt, kvp.second); cnt += 1; names.assign(cnt, ""); for (const std::pair &kvp : enum_keys_map) names[kvp.second] = kvp.first; } return names; } // Map from an enum name to an enum integer value. static t_config_enum_values& get_enum_values(); }; // Generic enum configuration value. // We use this one in DynamicConfig objects when creating a config value object for ConfigOptionType == coEnum. // In the StaticConfig, it is better to use the specialized ConfigOptionEnum containers. class ConfigOptionEnumGeneric : public ConfigOptionInt { public: ConfigOptionEnumGeneric(const t_config_enum_values* keys_map = nullptr) : keys_map(keys_map) {} const t_config_enum_values* keys_map; static ConfigOptionType static_type() { return coEnum; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionEnumGeneric(*this); } ConfigOptionEnumGeneric& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionEnumGeneric &rhs) const { return this->value == rhs.value; } std::string serialize() const override { for (const auto &kvp : *this->keys_map) if (kvp.second == this->value) return kvp.first; return std::string(); } bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); auto it = this->keys_map->find(str); if (it == this->keys_map->end()) return false; this->value = it->second; return true; } }; // Definition of a configuration value for the purpose of GUI presentation, editing, value mapping and config file handling. class ConfigOptionDef { public: // What type? bool, int, string etc. ConfigOptionType type = coNone; // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor. ConfigOption *default_value = nullptr; // Usually empty. // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection, // "select_open" - to open a selection dialog (currently only a serial port selection). std::string gui_type; // Usually empty. Otherwise "serialized" or "show_value" // The flags may be combined. // "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon. // "show_value" - even if enum_values / enum_labels are set, still display the value, not the enum label. std::string gui_flags; // Label of the GUI input field. // In case the GUI input fields are grouped in some views, the label defines a short label of a grouped value, // while full_label contains a label of a stand-alone field. // The full label is shown, when adding an override parameter for an object or a modified object. std::string label; std::string full_label; // Category of a configuration field, from the GUI perspective. // One of: "Layers and Perimeters", "Infill", "Support material", "Speed", "Extruders", "Advanced", "Extrusion Width" std::string category; // A tooltip text shown in the GUI. std::string tooltip; // Text right from the input field, usually a unit of measurement. std::string sidetext; // Format of this parameter on a command line. std::string cli; // Set for type == coFloatOrPercent. // It provides a link to a configuration value, of which this option provides a ratio. // For example, // For example external_perimeter_speed may be defined as a fraction of perimeter_speed. t_config_option_key ratio_over; // True for multiline strings. bool multiline = false; // For text input: If true, the GUI text box spans the complete page width. bool full_width = false; // Not editable. Currently only used for the display of the number of threads. bool readonly = false; // Height of a multiline GUI text box. int height = -1; // Optional width of an input field. int width = -1; // limit of a numeric input. // If not set, the is set to // By setting min=0, only nonnegative input is allowed. int min = INT_MIN; int max = INT_MAX; // Legacy names for this configuration option. // Used when parsing legacy configuration file. std::vector aliases; // Sometimes a single value may well define multiple values in a "beginner" mode. // Currently used for aliasing "solid_layers" to "top_solid_layers", "bottom_solid_layers". std::vector shortcut; // Definition of values / labels for a combo box. // Mostly used for enums (when type == coEnum), but may be used for ints resp. floats, if gui_type is set to "i_enum_open" resp. "f_enum_open". std::vector enum_values; std::vector enum_labels; // For enums (when type == coEnum). Maps enum_values to enums. // Initialized by ConfigOptionEnum::get_enum_values() t_config_enum_values *enum_keys_map = nullptr; bool has_enum_value(const std::string &value) const { for (const std::string &v : enum_values) if (v == value) return true; return false; } }; // Map from a config option name to its definition. // The definition does not carry an actual value of the config option, only its constant default value. // t_config_option_key is std::string typedef std::map t_optiondef_map; // Definition of configuration values for the purpose of GUI presentation, editing, value mapping and config file handling. // The configuration definition is static: It does not carry the actual configuration values, // but it carries the defaults of the configuration values. class ConfigDef { public: t_optiondef_map options; ~ConfigDef() { for (auto &opt : this->options) delete opt.second.default_value; } ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type) { ConfigOptionDef* opt = &this->options[opt_key]; opt->type = type; return opt; } bool has(const t_config_option_key &opt_key) const { return this->options.count(opt_key) > 0; } const ConfigOptionDef* get(const t_config_option_key &opt_key) const { t_optiondef_map::iterator it = const_cast(this)->options.find(opt_key); return (it == this->options.end()) ? nullptr : &it->second; } }; // An abstract configuration store. class ConfigBase { public: // Definition of configuration values for the purpose of GUI presentation, editing, value mapping and config file handling. // The configuration definition is static: It does not carry the actual configuration values, // but it carries the defaults of the configuration values. ConfigBase() {} virtual ~ConfigBase() {} // Virtual overridables: public: // Static configuration definition. Any value stored into this ConfigBase shall have its definition here. virtual const ConfigDef* def() const = 0; // Find ando/or create a ConfigOption instance for a given name. virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0; // Collect names of all configuration values maintained by this configuration store. virtual t_config_option_keys keys() const = 0; protected: // Verify whether the opt_key has not been obsoleted or renamed. // Both opt_key and value may be modified by handle_legacy(). // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). // handle_legacy() is called internally by set_deserialize(). virtual void handle_legacy(t_config_option_key &opt_key, std::string &value) const {} public: // Non-virtual methods: bool has(const t_config_option_key &opt_key) const { return this->option(opt_key) != nullptr; } const ConfigOption* option(const t_config_option_key &opt_key) const { return const_cast(this)->option(opt_key, false); } ConfigOption* option(const t_config_option_key &opt_key, bool create = false) { return this->optptr(opt_key, create); } template TYPE* option(const t_config_option_key &opt_key, bool create = false) { ConfigOption *opt = this->optptr(opt_key, create); assert(opt == nullptr || opt->type() == TYPE::static_type()); return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast(opt); } template const TYPE* option(const t_config_option_key &opt_key) const { return const_cast(this)->option(opt_key, false); } // Apply all keys of other ConfigBase defined by this->def() to this ConfigBase. // An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(), // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set. void apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->apply_only(other, other.keys(), ignore_nonexistent); } // Apply explicitely enumerated keys of other ConfigBase defined by this->def() to this ConfigBase. // An UnknownOptionException is thrown in case some option keys are not defined by this->def(), // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set. void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false); bool equals(const ConfigBase &other) const { return this->diff(other).empty(); } t_config_option_keys diff(const ConfigBase &other) const; std::string serialize(const t_config_option_key &opt_key) const; // Set a configuration value from a string, it will call an overridable handle_legacy() // to resolve renamed and removed configuration keys. bool set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false); double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; void setenv_(); void load(const std::string &file); void load_from_ini(const std::string &file); void load_from_gcode(const std::string &file); void load(const boost::property_tree::ptree &tree); void save(const std::string &file) const; private: // Set a configuration value from a string. bool set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &str, bool append); }; // Configuration store with dynamic number of configuration values. // In Slic3r, the dynamic config is mostly used at the user interface layer. class DynamicConfig : public virtual ConfigBase { public: DynamicConfig() {} DynamicConfig(const DynamicConfig& other) { *this = other; } DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); } virtual ~DynamicConfig() { clear(); } // Copy a content of one DynamicConfig to another DynamicConfig. // If rhs.def() is not null, then it has to be equal to this->def(). DynamicConfig& operator=(const DynamicConfig &rhs) { assert(this->def() == nullptr || this->def() == rhs.def()); this->clear(); for (const auto &kvp : rhs.options) this->options[kvp.first] = kvp.second->clone(); return *this; } // Move a content of one DynamicConfig to another DynamicConfig. // If rhs.def() is not null, then it has to be equal to this->def(). DynamicConfig& operator=(DynamicConfig &&rhs) { assert(this->def() == nullptr || this->def() == rhs.def()); this->clear(); this->options = std::move(rhs.options); rhs.options.clear(); return *this; } // Add a content of one DynamicConfig to another DynamicConfig. // If rhs.def() is not null, then it has to be equal to this->def(). DynamicConfig& operator+=(const DynamicConfig &rhs) { assert(this->def() == nullptr || this->def() == rhs.def()); for (const auto &kvp : rhs.options) { auto it = this->options.find(kvp.first); if (it == this->options.end()) this->options[kvp.first] = kvp.second->clone(); else { assert(it->second->type() == kvp.second->type()); if (it->second->type() == kvp.second->type()) *it->second = *kvp.second; else { delete it->second; it->second = kvp.second->clone(); } } } return *this; } // Move a content of one DynamicConfig to another DynamicConfig. // If rhs.def() is not null, then it has to be equal to this->def(). DynamicConfig& operator+=(DynamicConfig &&rhs) { assert(this->def() == nullptr || this->def() == rhs.def()); for (const auto &kvp : rhs.options) { auto it = this->options.find(kvp.first); if (it == this->options.end()) { this->options[kvp.first] = kvp.second; } else { assert(it->second->type() == kvp.second->type()); delete it->second; it->second = kvp.second; } } rhs.options.clear(); return *this; } bool operator==(const DynamicConfig &rhs) const; bool operator!=(const DynamicConfig &rhs) const { return ! (*this == rhs); } void swap(DynamicConfig &other) { std::swap(this->options, other.options); } void clear() { for (auto &opt : this->options) delete opt.second; this->options.clear(); } bool erase(const t_config_option_key &opt_key) { auto it = this->options.find(opt_key); if (it == this->options.end()) return false; delete it->second; this->options.erase(it); return true; } // Allow DynamicConfig to be instantiated on ints own without a definition. // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. const ConfigDef* def() const override { return nullptr; }; template T* opt(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast(this->option(opt_key, create)); } template const T* opt(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key)); } // Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override; // Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. t_config_option_keys keys() const override; // Set a value for an opt_key. Returns true if the value did not exist yet. // This DynamicConfig will take ownership of opt. // Be careful, as this method does not test the existence of opt_key in this->def(). bool set_key_value(const std::string &opt_key, ConfigOption *opt) { auto it = this->options.find(opt_key); if (it == this->options.end()) { this->options[opt_key] = opt; return true; } else { delete it->second; it->second = opt; return false; } } std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option(opt_key, create)->value; } const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast(this)->opt_string(opt_key); } std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast(this)->opt_string(opt_key, idx); } double& opt_float(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } const double opt_float(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } const double opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } int& opt_int(const t_config_option_key &opt_key) { return this->option(opt_key)->value; } const int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); } const int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } private: typedef std::map t_options_map; t_options_map options; }; /// Configuration store with a static definition of configuration values. /// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons, /// because the configuration values could be accessed directly. class StaticConfig : public virtual ConfigBase { public: StaticConfig() {} /// Gets list of config option names for each config option of this->def, which has a static counter-part defined by the derived object /// and which could be resolved by this->optptr(key) call. t_config_option_keys keys() const; protected: /// Set all statically defined config options to their defaults defined by this->def(). void set_defaults(); }; /// Specialization of std::exception to indicate that an unknown config option has been encountered. class UnknownOptionException : public std::exception { public: const char* what() const noexcept override { return "Unknown config option"; } }; /// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). class NoDefinitionException : public std::exception { public: const char* what() const noexcept override { return "No config definition"; } }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/EdgeGrid.cpp000066400000000000000000001262311324354444700220150ustar00rootroot00000000000000#include #include #include #include #if 0 // #ifdef SLIC3R_GUI #include #endif /* SLIC3R_GUI */ #include "libslic3r.h" #include "EdgeGrid.hpp" #if 0 // Enable debugging and assert in this file. #define DEBUG #define _DEBUG #undef NDEBUG #endif #include namespace Slic3r { EdgeGrid::Grid::Grid() : m_rows(0), m_cols(0) { } EdgeGrid::Grid::~Grid() { m_contours.clear(); m_cell_data.clear(); m_cells.clear(); } void EdgeGrid::Grid::create(const Polygons &polygons, coord_t resolution) { // Count the contours. size_t ncontours = 0; for (size_t j = 0; j < polygons.size(); ++ j) if (! polygons[j].points.empty()) ++ ncontours; // Collect the contours. m_contours.assign(ncontours, NULL); ncontours = 0; for (size_t j = 0; j < polygons.size(); ++ j) if (! polygons[j].points.empty()) m_contours[ncontours++] = &polygons[j].points; create_from_m_contours(resolution); } void EdgeGrid::Grid::create(const ExPolygon &expoly, coord_t resolution) { // Count the contours. size_t ncontours = 0; if (! expoly.contour.points.empty()) ++ ncontours; for (size_t j = 0; j < expoly.holes.size(); ++ j) if (! expoly.holes[j].points.empty()) ++ ncontours; // Collect the contours. m_contours.assign(ncontours, NULL); ncontours = 0; if (! expoly.contour.points.empty()) m_contours[ncontours++] = &expoly.contour.points; for (size_t j = 0; j < expoly.holes.size(); ++ j) if (! expoly.holes[j].points.empty()) m_contours[ncontours++] = &expoly.holes[j].points; create_from_m_contours(resolution); } void EdgeGrid::Grid::create(const ExPolygons &expolygons, coord_t resolution) { // Count the contours. size_t ncontours = 0; for (size_t i = 0; i < expolygons.size(); ++ i) { const ExPolygon &expoly = expolygons[i]; if (! expoly.contour.points.empty()) ++ ncontours; for (size_t j = 0; j < expoly.holes.size(); ++ j) if (! expoly.holes[j].points.empty()) ++ ncontours; } // Collect the contours. m_contours.assign(ncontours, NULL); ncontours = 0; for (size_t i = 0; i < expolygons.size(); ++ i) { const ExPolygon &expoly = expolygons[i]; if (! expoly.contour.points.empty()) m_contours[ncontours++] = &expoly.contour.points; for (size_t j = 0; j < expoly.holes.size(); ++ j) if (! expoly.holes[j].points.empty()) m_contours[ncontours++] = &expoly.holes[j].points; } create_from_m_contours(resolution); } void EdgeGrid::Grid::create(const ExPolygonCollection &expolygons, coord_t resolution) { create(expolygons.expolygons, resolution); } // m_contours has been initialized. Now fill in the edge grid. void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) { // 1) Measure the bounding box. for (size_t i = 0; i < m_contours.size(); ++ i) { const Slic3r::Points &pts = *m_contours[i]; for (size_t j = 0; j < pts.size(); ++ j) m_bbox.merge(pts[j]); } coord_t eps = 16; m_bbox.min.x -= eps; m_bbox.min.y -= eps; m_bbox.max.x += eps; m_bbox.max.y += eps; // 2) Initialize the edge grid. m_resolution = resolution; m_cols = (m_bbox.max.x - m_bbox.min.x + m_resolution - 1) / m_resolution; m_rows = (m_bbox.max.y - m_bbox.min.y + m_resolution - 1) / m_resolution; m_cells.assign(m_rows * m_cols, Cell()); // 3) First round of contour rasterization, count the edges per grid cell. for (size_t i = 0; i < m_contours.size(); ++ i) { const Slic3r::Points &pts = *m_contours[i]; for (size_t j = 0; j < pts.size(); ++ j) { // End points of the line segment. Slic3r::Point p1(pts[j]); Slic3r::Point p2 = pts[(j + 1 == pts.size()) ? 0 : j + 1]; p1.x -= m_bbox.min.x; p1.y -= m_bbox.min.y; p2.x -= m_bbox.min.x; p2.y -= m_bbox.min.y; // Get the cells of the end points. coord_t ix = p1.x / m_resolution; coord_t iy = p1.y / m_resolution; coord_t ixb = p2.x / m_resolution; coord_t iyb = p2.y / m_resolution; assert(ix >= 0 && ix < m_cols); assert(iy >= 0 && iy < m_rows); assert(ixb >= 0 && ixb < m_cols); assert(iyb >= 0 && iyb < m_rows); // Account for the end points. ++ m_cells[iy*m_cols+ix].end; if (ix == ixb && iy == iyb) // Both ends fall into the same cell. continue; // Raster the centeral part of the line. coord_t dx = std::abs(p2.x - p1.x); coord_t dy = std::abs(p2.y - p1.y); if (p1.x < p2.x) { int64_t ex = int64_t((ix + 1)*m_resolution - p1.x) * int64_t(dy); if (p1.y < p2.y) { // x positive, y positive int64_t ey = int64_t((iy + 1)*m_resolution - p1.y) * int64_t(dx); do { assert(ix <= ixb && iy <= iyb); if (ex < ey) { ey -= ex; ex = int64_t(dy) * m_resolution; ix += 1; } else if (ex == ey) { ex = int64_t(dy) * m_resolution; ey = int64_t(dx) * m_resolution; ix += 1; iy += 1; } else { assert(ex > ey); ex -= ey; ey = int64_t(dx) * m_resolution; iy += 1; } ++m_cells[iy*m_cols + ix].end; } while (ix != ixb || iy != iyb); } else { // x positive, y non positive int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); do { assert(ix <= ixb && iy >= iyb); if (ex <= ey) { ey -= ex; ex = int64_t(dy) * m_resolution; ix += 1; } else { ex -= ey; ey = int64_t(dx) * m_resolution; iy -= 1; } ++m_cells[iy*m_cols + ix].end; } while (ix != ixb || iy != iyb); } } else { int64_t ex = int64_t(p1.x - ix*m_resolution) * int64_t(dy); if (p1.y < p2.y) { // x non positive, y positive int64_t ey = int64_t((iy + 1)*m_resolution - p1.y) * int64_t(dx); do { assert(ix >= ixb && iy <= iyb); if (ex < ey) { ey -= ex; ex = int64_t(dy) * m_resolution; ix -= 1; } else { assert(ex >= ey); ex -= ey; ey = int64_t(dx) * m_resolution; iy += 1; } ++m_cells[iy*m_cols + ix].end; } while (ix != ixb || iy != iyb); } else { // x non positive, y non positive int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); do { assert(ix >= ixb && iy >= iyb); if (ex < ey) { ey -= ex; ex = int64_t(dy) * m_resolution; ix -= 1; } else if (ex == ey) { // The lower edge of a grid cell belongs to the cell. // Handle the case where the ray may cross the lower left corner of a cell in a general case, // or a left or lower edge in a degenerate case (horizontal or vertical line). if (dx > 0) { ex = int64_t(dy) * m_resolution; ix -= 1; } if (dy > 0) { ey = int64_t(dx) * m_resolution; iy -= 1; } } else { assert(ex > ey); ex -= ey; ey = int64_t(dx) * m_resolution; iy -= 1; } ++m_cells[iy*m_cols + ix].end; } while (ix != ixb || iy != iyb); } } } } // 4) Prefix sum the numbers of hits per cells to get an index into m_cell_data. size_t cnt = m_cells.front().end; for (size_t i = 1; i < m_cells.size(); ++ i) { m_cells[i].begin = cnt; cnt += m_cells[i].end; m_cells[i].end = cnt; } // 5) Allocate the cell data. m_cell_data.assign(cnt, std::pair(size_t(-1), size_t(-1))); // 6) Finally fill in m_cell_data by rasterizing the lines once again. for (size_t i = 0; i < m_cells.size(); ++i) m_cells[i].end = m_cells[i].begin; for (size_t i = 0; i < m_contours.size(); ++i) { const Slic3r::Points &pts = *m_contours[i]; for (size_t j = 0; j < pts.size(); ++j) { // End points of the line segment. Slic3r::Point p1(pts[j]); Slic3r::Point p2 = pts[(j + 1 == pts.size()) ? 0 : j + 1]; p1.x -= m_bbox.min.x; p1.y -= m_bbox.min.y; p2.x -= m_bbox.min.x; p2.y -= m_bbox.min.y; // Get the cells of the end points. coord_t ix = p1.x / m_resolution; coord_t iy = p1.y / m_resolution; coord_t ixb = p2.x / m_resolution; coord_t iyb = p2.y / m_resolution; assert(ix >= 0 && ix < m_cols); assert(iy >= 0 && iy < m_rows); assert(ixb >= 0 && ixb < m_cols); assert(iyb >= 0 && iyb < m_rows); // Account for the end points. m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); if (ix == ixb && iy == iyb) // Both ends fall into the same cell. continue; // Raster the centeral part of the line. coord_t dx = std::abs(p2.x - p1.x); coord_t dy = std::abs(p2.y - p1.y); if (p1.x < p2.x) { int64_t ex = int64_t((ix + 1)*m_resolution - p1.x) * int64_t(dy); if (p1.y < p2.y) { // x positive, y positive int64_t ey = int64_t((iy + 1)*m_resolution - p1.y) * int64_t(dx); do { assert(ix <= ixb && iy <= iyb); if (ex < ey) { ey -= ex; ex = int64_t(dy) * m_resolution; ix += 1; } else if (ex == ey) { ex = int64_t(dy) * m_resolution; ey = int64_t(dx) * m_resolution; ix += 1; iy += 1; } else { assert(ex > ey); ex -= ey; ey = int64_t(dx) * m_resolution; iy += 1; } m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); } while (ix != ixb || iy != iyb); } else { // x positive, y non positive int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); do { assert(ix <= ixb && iy >= iyb); if (ex <= ey) { ey -= ex; ex = int64_t(dy) * m_resolution; ix += 1; } else { ex -= ey; ey = int64_t(dx) * m_resolution; iy -= 1; } m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); } while (ix != ixb || iy != iyb); } } else { int64_t ex = int64_t(p1.x - ix*m_resolution) * int64_t(dy); if (p1.y < p2.y) { // x non positive, y positive int64_t ey = int64_t((iy + 1)*m_resolution - p1.y) * int64_t(dx); do { assert(ix >= ixb && iy <= iyb); if (ex < ey) { ey -= ex; ex = int64_t(dy) * m_resolution; ix -= 1; } else { assert(ex >= ey); ex -= ey; ey = int64_t(dx) * m_resolution; iy += 1; } m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); } while (ix != ixb || iy != iyb); } else { // x non positive, y non positive int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); do { assert(ix >= ixb && iy >= iyb); if (ex < ey) { ey -= ex; ex = int64_t(dy) * m_resolution; ix -= 1; } else if (ex == ey) { // The lower edge of a grid cell belongs to the cell. // Handle the case where the ray may cross the lower left corner of a cell in a general case, // or a left or lower edge in a degenerate case (horizontal or vertical line). if (dx > 0) { ex = int64_t(dy) * m_resolution; ix -= 1; } if (dy > 0) { ey = int64_t(dx) * m_resolution; iy -= 1; } } else { assert(ex > ey); ex -= ey; ey = int64_t(dx) * m_resolution; iy -= 1; } m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); } while (ix != ixb || iy != iyb); } } } } } #if 0 // Divide, round to a grid coordinate. // Divide x/y, round down. y is expected to be positive. static inline coord_t div_floor(coord_t x, coord_t y) { assert(y > 0); return ((x < 0) ? (x - y + 1) : x) / y; } // Walk the polyline, test whether any lines of this polyline does not intersect // any line stored into the grid. bool EdgeGrid::Grid::intersect(const MultiPoint &polyline, bool closed) { size_t n = polyline.points.size(); if (closed) ++ n; for (size_t i = 0; i < n; ++ i) { size_t j = i + 1; if (j == polyline.points.size()) j = 0; Point p1src = polyline.points[i]; Point p2src = polyline.points[j]; Point p1 = p1src; Point p2 = p2src; // Discretize the line segment p1, p2. p1.x -= m_bbox.min.x; p1.y -= m_bbox.min.y; p2.x -= m_bbox.min.x; p2.y -= m_bbox.min.y; // Get the cells of the end points. coord_t ix = div_floor(p1.x, m_resolution); coord_t iy = div_floor(p1.y, m_resolution); coord_t ixb = div_floor(p2.x, m_resolution); coord_t iyb = div_floor(p2.y, m_resolution); // assert(ix >= 0 && ix < m_cols); // assert(iy >= 0 && iy < m_rows); // assert(ixb >= 0 && ixb < m_cols); // assert(iyb >= 0 && iyb < m_rows); // Account for the end points. if (line_cell_intersect(p1src, p2src, m_cells[iy*m_cols + ix])) return true; if (ix == ixb && iy == iyb) // Both ends fall into the same cell. continue; // Raster the centeral part of the line. coord_t dx = std::abs(p2.x - p1.x); coord_t dy = std::abs(p2.y - p1.y); if (p1.x < p2.x) { int64_t ex = int64_t((ix + 1)*m_resolution - p1.x) * int64_t(dy); if (p1.y < p2.y) { int64_t ey = int64_t((iy + 1)*m_resolution - p1.y) * int64_t(dx); do { assert(ix <= ixb && iy <= iyb); if (ex < ey) { ey -= ex; ex = int64_t(dy) * m_resolution; ix += 1; } else if (ex == ey) { ex = int64_t(dy) * m_resolution; ey = int64_t(dx) * m_resolution; ix += 1; iy += 1; } else { assert(ex > ey); ex -= ey; ey = int64_t(dx) * m_resolution; iy += 1; } if (line_cell_intersect(p1src, p2src, m_cells[iy*m_cols + ix])) return true; } while (ix != ixb || iy != iyb); } else { int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); do { assert(ix <= ixb && iy >= iyb); if (ex <= ey) { ey -= ex; ex = int64_t(dy) * m_resolution; ix += 1; } else { ex -= ey; ey = int64_t(dx) * m_resolution; iy -= 1; } if (line_cell_intersect(p1src, p2src, m_cells[iy*m_cols + ix])) return true; } while (ix != ixb || iy != iyb); } } else { int64_t ex = int64_t(p1.x - ix*m_resolution) * int64_t(dy); if (p1.y < p2.y) { int64_t ey = int64_t((iy + 1)*m_resolution - p1.y) * int64_t(dx); do { assert(ix >= ixb && iy <= iyb); if (ex < ey) { ey -= ex; ex = int64_t(dy) * m_resolution; ix -= 1; } else { assert(ex >= ey); ex -= ey; ey = int64_t(dx) * m_resolution; iy += 1; } if (line_cell_intersect(p1src, p2src, m_cells[iy*m_cols + ix])) return true; } while (ix != ixb || iy != iyb); } else { int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); do { assert(ix >= ixb && iy >= iyb); if (ex < ey) { ey -= ex; ex = int64_t(dy) * m_resolution; ix -= 1; } else if (ex == ey) { if (dx > 0) { ex = int64_t(dy) * m_resolution; ix -= 1; } if (dy > 0) { ey = int64_t(dx) * m_resolution; iy -= 1; } } else { assert(ex > ey); ex -= ey; ey = int64_t(dx) * m_resolution; iy -= 1; } if (line_cell_intersect(p1src, p2src, m_cells[iy*m_cols + ix])) return true; } while (ix != ixb || iy != iyb); } } } return false; } bool EdgeGrid::Grid::line_cell_intersect(const Point &p1a, const Point &p2a, const Cell &cell) { BoundingBox bbox(p1a, p1a); bbox.merge(p2a); int64_t va_x = p2a.x - p1a.x; int64_t va_y = p2a.y - p1a.y; for (size_t i = cell.begin; i != cell.end; ++ i) { const std::pair &cell_data = m_cell_data[i]; // Contour indexed by the ith line of this cell. const Slic3r::Points &contour = *m_contours[cell_data.first]; // Point indices in contour indexed by the ith line of this cell. size_t idx1 = cell_data.second; size_t idx2 = idx1 + 1; if (idx2 == contour.size()) idx2 = 0; // The points of the ith line of this cell and its bounding box. const Point &p1b = contour[idx1]; const Point &p2b = contour[idx2]; BoundingBox bbox2(p1b, p1b); bbox2.merge(p2b); // Do the bounding boxes intersect? if (! bbox.overlap(bbox2)) continue; // Now intersect the two line segments using exact arithmetics. int64_t w1_x = p1b.x - p1a.x; int64_t w1_y = p1b.y - p1a.y; int64_t w2_x = p2b.x - p1a.x; int64_t w2_y = p2b.y - p1a.y; int64_t side1 = va_x * w1_y - va_y * w1_x; int64_t side2 = va_x * w2_y - va_y * w2_x; if (side1 == side2 && side1 != 0) // The line segments don't intersect. continue; w1_x = p1a.x - p1b.x; w1_y = p1a.y - p1b.y; w2_x = p2a.x - p1b.x; w2_y = p2a.y - p1b.y; int64_t vb_x = p2b.x - p1b.x; int64_t vb_y = p2b.y - p1b.y; side1 = vb_x * w1_y - vb_y * w1_x; side2 = vb_x * w2_y - vb_y * w2_x; if (side1 == side2 && side1 != 0) // The line segments don't intersect. continue; // The line segments intersect. return true; } // The line segment (p1a, p2a) does not intersect any of the line segments inside this cell. return false; } // Test, whether a point is inside a contour. bool EdgeGrid::Grid::inside(const Point &pt_src) { Point p = pt_src; p.x -= m_bbox.min.x; p.y -= m_bbox.min.y; // Get the cell of the point. if (p.x < 0 || p.y < 0) return false; coord_t ix = p.x / m_resolution; coord_t iy = p.y / m_resolution; if (ix >= this->m_cols || iy >= this->m_rows) return false; size_t i_closest = (size_t)-1; bool inside = false; { // Hit in the first cell? const Cell &cell = m_cells[iy * m_cols + ix]; for (size_t i = cell.begin; i != cell.end; ++ i) { const std::pair &cell_data = m_cell_data[i]; // Contour indexed by the ith line of this cell. const Slic3r::Points &contour = *m_contours[cell_data.first]; // Point indices in contour indexed by the ith line of this cell. size_t idx1 = cell_data.second; size_t idx2 = idx1 + 1; if (idx2 == contour.size()) idx2 = 0; const Point &p1 = contour[idx1]; const Point &p2 = contour[idx2]; if (p1.y < p2.y) { if (p.y < p1.y || p.y > p2.y) continue; //FIXME finish this! int64_t vx = 0;// pt_src //FIXME finish this! int64_t det = 0; } else if (p1.y != p2.y) { assert(p1.y > p2.y); if (p.y < p2.y || p.y > p1.y) continue; } else { assert(p1.y == p2.y); if (p1.y == p.y) { if (p.x >= p1.x && p.x <= p2.x) // On the segment. return true; // Before or after the segment. size_t idx0 = idx1 - 1; size_t idx2 = idx1 + 1; if (idx0 == (size_t)-1) idx0 = contour.size() - 1; if (idx2 == contour.size()) idx2 = 0; } } } } //FIXME This code follows only a single direction. Better to follow the direction closest to the bounding box. } #endif template struct PropagateDanielssonSingleStep { PropagateDanielssonSingleStep(float *aL, unsigned char *asigns, size_t astride, coord_t aresolution) : L(aL), signs(asigns), stride(astride), resolution(aresolution) {} inline void operator()(int r, int c, int addr_delta) { size_t addr = r * stride + c; if ((signs[addr] & 2) == 0) { float *v = &L[addr << 1]; float l = v[0] * v[0] + v[1] * v[1]; float *v2s = v + (addr_delta << 1); float v2[2] = { v2s[0] + INCX * resolution, v2s[1] + INCY * resolution }; float l2 = v2[0] * v2[0] + v2[1] * v2[1]; if (l2 < l) { v[0] = v2[0]; v[1] = v2[1]; } } } float *L; unsigned char *signs; size_t stride; coord_t resolution; }; struct PropagateDanielssonSingleVStep3 { PropagateDanielssonSingleVStep3(float *aL, unsigned char *asigns, size_t astride, coord_t aresolution) : L(aL), signs(asigns), stride(astride), resolution(aresolution) {} inline void operator()(int r, int c, int addr_delta, bool has_l, bool has_r) { size_t addr = r * stride + c; if ((signs[addr] & 2) == 0) { float *v = &L[addr<<1]; float l = v[0]*v[0]+v[1]*v[1]; float *v2s = v+(addr_delta<<1); float v2[2] = { v2s[0], v2s[1] + resolution }; float l2 = v2[0]*v2[0]+v2[1]*v2[1]; if (l2 < l) { v[0] = v2[0]; v[1] = v2[1]; } if (has_l) { float *v2sl = v2s - 1; v2[0] = v2sl[0] + resolution; v2[1] = v2sl[1] + resolution; l2 = v2[0]*v2[0]+v2[1]*v2[1]; if (l2 < l) { v[0] = v2[0]; v[1] = v2[1]; } } if (has_r) { float *v2sr = v2s + 1; v2[0] = v2sr[0] + resolution; v2[1] = v2sr[1] + resolution; l2 = v2[0]*v2[0]+v2[1]*v2[1]; if (l2 < l) { v[0] = v2[0]; v[1] = v2[1]; } } } } float *L; unsigned char *signs; size_t stride; coord_t resolution; }; void EdgeGrid::Grid::calculate_sdf() { // 1) Initialize a signum and an unsigned vector to a zero iso surface. size_t nrows = m_rows + 1; size_t ncols = m_cols + 1; // Unsigned vectors towards the closest point on the surface. std::vector L(nrows * ncols * 2, FLT_MAX); // Bit 0 set - negative. // Bit 1 set - original value, the distance value shall not be changed by the Danielsson propagation. // Bit 2 set - signum not propagated yet. std::vector signs(nrows * ncols, 4); // SDF will be initially filled with unsigned DF. // m_signed_distance_field.assign(nrows * ncols, FLT_MAX); float search_radius = float(m_resolution<<1); m_signed_distance_field.assign(nrows * ncols, search_radius); // For each cell: for (size_t r = 0; r < m_rows; ++ r) { for (size_t c = 0; c < m_cols; ++ c) { const Cell &cell = m_cells[r * m_cols + c]; // For each segment in the cell: for (size_t i = cell.begin; i != cell.end; ++ i) { const Slic3r::Points &pts = *m_contours[m_cell_data[i].first]; size_t ipt = m_cell_data[i].second; // End points of the line segment. const Slic3r::Point &p1 = pts[ipt]; const Slic3r::Point &p2 = pts[(ipt + 1 == pts.size()) ? 0 : ipt + 1]; // Segment vector const Slic3r::Point v_seg = p1.vector_to(p2); // l2 of v_seg const int64_t l2_seg = int64_t(v_seg.x) * int64_t(v_seg.x) + int64_t(v_seg.y) * int64_t(v_seg.y); // For each corner of this cell and its 1 ring neighbours: for (int corner_y = -1; corner_y < 3; ++ corner_y) { coord_t corner_r = r + corner_y; if (corner_r < 0 || corner_r >= nrows) continue; for (int corner_x = -1; corner_x < 3; ++ corner_x) { coord_t corner_c = c + corner_x; if (corner_c < 0 || corner_c >= ncols) continue; float &d_min = m_signed_distance_field[corner_r * ncols + corner_c]; Slic3r::Point pt(m_bbox.min.x + corner_c * m_resolution, m_bbox.min.y + corner_r * m_resolution); Slic3r::Point v_pt = p1.vector_to(pt); // dot(p2-p1, pt-p1) int64_t t_pt = int64_t(v_seg.x) * int64_t(v_pt.x) + int64_t(v_seg.y) * int64_t(v_pt.y); if (t_pt < 0) { // Closest to p1. double dabs = sqrt(int64_t(v_pt.x) * int64_t(v_pt.x) + int64_t(v_pt.y) * int64_t(v_pt.y)); if (dabs < d_min) { // Previous point. const Slic3r::Point &p0 = pts[(ipt == 0) ? (pts.size() - 1) : ipt - 1]; Slic3r::Point v_seg_prev = p0.vector_to(p1); int64_t t2_pt = int64_t(v_seg_prev.x) * int64_t(v_pt.x) + int64_t(v_seg_prev.y) * int64_t(v_pt.y); if (t2_pt > 0) { // Inside the wedge between the previous and the next segment. // Set the signum depending on whether the vertex is convex or reflex. int64_t det = int64_t(v_seg_prev.x) * int64_t(v_seg.y) - int64_t(v_seg_prev.y) * int64_t(v_seg.x); assert(det != 0); d_min = dabs; // Fill in an unsigned vector towards the zero iso surface. float *l = &L[(corner_r * ncols + corner_c) << 1]; l[0] = std::abs(v_pt.x); l[1] = std::abs(v_pt.y); #ifdef _DEBUG double dabs2 = sqrt(l[0]*l[0]+l[1]*l[1]); assert(std::abs(dabs-dabs2) < 1e-4 * std::max(dabs, dabs2)); #endif /* _DEBUG */ signs[corner_r * ncols + corner_c] = ((det < 0) ? 1 : 0) | 2; } } } else if (t_pt > l2_seg) { // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the same cell. continue; } else { // Closest to the segment. assert(t_pt >= 0 && t_pt <= l2_seg); int64_t d_seg = int64_t(v_seg.y) * int64_t(v_pt.x) - int64_t(v_seg.x) * int64_t(v_pt.y); double d = double(d_seg) / sqrt(double(l2_seg)); double dabs = std::abs(d); if (dabs < d_min) { d_min = dabs; // Fill in an unsigned vector towards the zero iso surface. float *l = &L[(corner_r * ncols + corner_c) << 1]; float linv = float(d_seg) / float(l2_seg); l[0] = std::abs(float(v_seg.y) * linv); l[1] = std::abs(float(v_seg.x) * linv); #ifdef _DEBUG double dabs2 = sqrt(l[0]*l[0]+l[1]*l[1]); assert(std::abs(dabs-dabs2) <= 1e-4 * std::max(dabs, dabs2)); #endif /* _DEBUG */ signs[corner_r * ncols + corner_c] = ((d_seg < 0) ? 1 : 0) | 2; } } } } } } } #if 0 static int iRun = 0; ++ iRun; //#ifdef SLIC3R_GUI { wxImage img(ncols, nrows); unsigned char *data = img.GetData(); memset(data, 0, ncols * nrows * 3); for (coord_t r = 0; r < nrows; ++r) { for (coord_t c = 0; c < ncols; ++c) { unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3; float d = m_signed_distance_field[r * ncols + c]; if (d != search_radius) { float s = 255 * d / search_radius; int is = std::max(0, std::min(255, int(floor(s + 0.5f)))); pxl[0] = 255; pxl[1] = 255 - is; pxl[2] = 255 - is; } else { pxl[0] = 0; pxl[1] = 255; pxl[2] = 0; } } } img.SaveFile(debug_out_path("unsigned_df-%d.png", iRun), wxBITMAP_TYPE_PNG); } { wxImage img(ncols, nrows); unsigned char *data = img.GetData(); memset(data, 0, ncols * nrows * 3); for (coord_t r = 0; r < nrows; ++r) { for (coord_t c = 0; c < ncols; ++c) { unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3; float d = m_signed_distance_field[r * ncols + c]; if (d != search_radius) { float s = 255 * d / search_radius; int is = std::max(0, std::min(255, int(floor(s + 0.5f)))); if ((signs[r * ncols + c] & 1) == 0) { // Positive pxl[0] = 255; pxl[1] = 255 - is; pxl[2] = 255 - is; } else { // Negative pxl[0] = 255 - is; pxl[1] = 255 - is; pxl[2] = 255; } } else { pxl[0] = 0; pxl[1] = 255; pxl[2] = 0; } } } img.SaveFile(debug_out_path("signed_df-%d.png", iRun), wxBITMAP_TYPE_PNG); } #endif /* SLIC3R_GUI */ // 2) Propagate the signum. #define PROPAGATE_SIGNUM_SINGLE_STEP(DELTA) do { \ size_t addr = r * ncols + c; \ unsigned char &cur_val = signs[addr]; \ if (cur_val & 4) { \ unsigned char old_val = signs[addr + (DELTA)]; \ if ((old_val & 4) == 0) \ cur_val = old_val & 1; \ } \ } while (0); // Top to bottom propagation. for (size_t r = 0; r < nrows; ++ r) { if (r > 0) for (size_t c = 0; c < ncols; ++ c) PROPAGATE_SIGNUM_SINGLE_STEP(- int(ncols)); for (size_t c = 1; c < ncols; ++ c) PROPAGATE_SIGNUM_SINGLE_STEP(- 1); for (int c = int(ncols) - 2; c >= 0; -- c) PROPAGATE_SIGNUM_SINGLE_STEP(+ 1); } // Bottom to top propagation. for (int r = int(nrows) - 2; r >= 0; -- r) { for (size_t c = 0; c < ncols; ++ c) PROPAGATE_SIGNUM_SINGLE_STEP(+ ncols); for (size_t c = 1; c < ncols; ++ c) PROPAGATE_SIGNUM_SINGLE_STEP(- 1); for (int c = int(ncols) - 2; c >= 0; -- c) PROPAGATE_SIGNUM_SINGLE_STEP(+ 1); } #undef PROPAGATE_SIGNUM_SINGLE_STEP // 3) Propagate the distance by the Danielsson chamfer metric. // Top to bottom propagation. PropagateDanielssonSingleStep<1, 0> danielsson_hstep(L.data(), signs.data(), ncols, m_resolution); PropagateDanielssonSingleStep<0, 1> danielsson_vstep(L.data(), signs.data(), ncols, m_resolution); PropagateDanielssonSingleVStep3 danielsson_vstep3(L.data(), signs.data(), ncols, m_resolution); // Top to bottom propagation. for (size_t r = 0; r < nrows; ++ r) { if (r > 0) for (size_t c = 0; c < ncols; ++ c) danielsson_vstep(r, c, -int(ncols)); // PROPAGATE_DANIELSSON_SINGLE_VSTEP3(-int(ncols), c != 0, c + 1 != ncols); for (size_t c = 1; c < ncols; ++ c) danielsson_hstep(r, c, -1); for (int c = int(ncols) - 2; c >= 0; -- c) danielsson_hstep(r, c, +1); } // Bottom to top propagation. for (int r = int(nrows) - 2; r >= 0; -- r) { for (size_t c = 0; c < ncols; ++ c) danielsson_vstep(r, c, +ncols); // PROPAGATE_DANIELSSON_SINGLE_VSTEP3(+int(ncols), c != 0, c + 1 != ncols); for (size_t c = 1; c < ncols; ++ c) danielsson_hstep(r, c, -1); for (int c = int(ncols) - 2; c >= 0; -- c) danielsson_hstep(r, c, +1); } // Update signed distance field from absolte vectors to the iso-surface. for (size_t r = 0; r < nrows; ++ r) { for (size_t c = 0; c < ncols; ++ c) { size_t addr = r * ncols + c; float *v = &L[addr<<1]; float d = sqrt(v[0]*v[0]+v[1]*v[1]); if (signs[addr] & 1) d = -d; m_signed_distance_field[addr] = d; } } #if 0 //#ifdef SLIC3R_GUI { wxImage img(ncols, nrows); unsigned char *data = img.GetData(); memset(data, 0, ncols * nrows * 3); float search_radius = float(m_resolution * 5); for (coord_t r = 0; r < nrows; ++r) { for (coord_t c = 0; c < ncols; ++c) { unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3; unsigned char sign = signs[r * ncols + c]; switch (sign) { case 0: // Positive, outside of a narrow band. pxl[0] = 0; pxl[1] = 0; pxl[2] = 255; break; case 1: // Negative, outside of a narrow band. pxl[0] = 255; pxl[1] = 0; pxl[2] = 0; break; case 2: // Positive, outside of a narrow band. pxl[0] = 100; pxl[1] = 100; pxl[2] = 255; break; case 3: // Negative, outside of a narrow band. pxl[0] = 255; pxl[1] = 100; pxl[2] = 100; break; case 4: // This shall not happen. Undefined signum. pxl[0] = 0; pxl[1] = 255; pxl[2] = 0; break; default: // This shall not happen. Invalid signum value. pxl[0] = 255; pxl[1] = 255; pxl[2] = 255; break; } } } img.SaveFile(debug_out_path("signed_df-signs-%d.png", iRun), wxBITMAP_TYPE_PNG); } #endif /* SLIC3R_GUI */ #if 0 //#ifdef SLIC3R_GUI { wxImage img(ncols, nrows); unsigned char *data = img.GetData(); memset(data, 0, ncols * nrows * 3); float search_radius = float(m_resolution * 5); for (coord_t r = 0; r < nrows; ++r) { for (coord_t c = 0; c < ncols; ++c) { unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3; float d = m_signed_distance_field[r * ncols + c]; float s = 255.f * fabs(d) / search_radius; int is = std::max(0, std::min(255, int(floor(s + 0.5f)))); if (d < 0.f) { pxl[0] = 255; pxl[1] = 255 - is; pxl[2] = 255 - is; } else { pxl[0] = 255 - is; pxl[1] = 255 - is; pxl[2] = 255; } } } img.SaveFile(debug_out_path("signed_df2-%d.png", iRun), wxBITMAP_TYPE_PNG); } #endif /* SLIC3R_GUI */ } float EdgeGrid::Grid::signed_distance_bilinear(const Point &pt) const { coord_t x = pt.x - m_bbox.min.x; coord_t y = pt.y - m_bbox.min.y; coord_t w = m_resolution * m_cols; coord_t h = m_resolution * m_rows; bool clamped = false; coord_t xcl = x; coord_t ycl = y; if (x < 0) { xcl = 0; clamped = true; } else if (x >= w) { xcl = w - 1; clamped = true; } if (y < 0) { ycl = 0; clamped = true; } else if (y >= h) { ycl = h - 1; clamped = true; } coord_t cell_c = coord_t(floor(xcl / m_resolution)); coord_t cell_r = coord_t(floor(ycl / m_resolution)); float tx = float(xcl - cell_c * m_resolution) / float(m_resolution); assert(tx >= -1e-5 && tx < 1.f + 1e-5); float ty = float(ycl - cell_r * m_resolution) / float(m_resolution); assert(ty >= -1e-5 && ty < 1.f + 1e-5); size_t addr = cell_r * (m_cols + 1) + cell_c; float f00 = m_signed_distance_field[addr]; float f01 = m_signed_distance_field[addr+1]; addr += m_cols + 1; float f10 = m_signed_distance_field[addr]; float f11 = m_signed_distance_field[addr+1]; float f0 = (1.f - tx) * f00 + tx * f01; float f1 = (1.f - tx) * f10 + tx * f11; float f = (1.f - ty) * f0 + ty * f1; if (clamped) { if (f > 0) { if (x < 0) f += -x; else if (x >= w) f += x - w + 1; if (y < 0) f += -y; else if (y >= h) f += y - h + 1; } else { if (x < 0) f -= -x; else if (x >= w) f -= x - w + 1; if (y < 0) f -= -y; else if (y >= h) f -= y - h + 1; } } return f; } bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment) const { BoundingBox bbox; bbox.min = bbox.max = Point(pt.x - m_bbox.min.x, pt.y - m_bbox.min.y); bbox.defined = true; // Upper boundary, round to grid and test validity. bbox.max.x += search_radius; bbox.max.y += search_radius; if (bbox.max.x < 0 || bbox.max.y < 0) return false; bbox.max.x /= m_resolution; bbox.max.y /= m_resolution; if (bbox.max.x >= m_cols) bbox.max.x = m_cols - 1; if (bbox.max.y >= m_rows) bbox.max.y = m_rows - 1; // Lower boundary, round to grid and test validity. bbox.min.x -= search_radius; bbox.min.y -= search_radius; if (bbox.min.x < 0) bbox.min.x = 0; if (bbox.min.y < 0) bbox.min.y = 0; bbox.min.x /= m_resolution; bbox.min.y /= m_resolution; // Is the interval empty? if (bbox.min.x > bbox.max.x || bbox.min.y > bbox.max.y) return false; // Traverse all cells in the bounding box. float d_min = search_radius; // Signum of the distance field at pt. int sign_min = 0; bool on_segment = false; for (int r = bbox.min.y; r <= bbox.max.y; ++ r) { for (int c = bbox.min.x; c <= bbox.max.x; ++ c) { const Cell &cell = m_cells[r * m_cols + c]; for (size_t i = cell.begin; i < cell.end; ++ i) { const Slic3r::Points &pts = *m_contours[m_cell_data[i].first]; size_t ipt = m_cell_data[i].second; // End points of the line segment. const Slic3r::Point &p1 = pts[ipt]; const Slic3r::Point &p2 = pts[(ipt + 1 == pts.size()) ? 0 : ipt + 1]; Slic3r::Point v_seg = p1.vector_to(p2); Slic3r::Point v_pt = p1.vector_to(pt); // dot(p2-p1, pt-p1) int64_t t_pt = int64_t(v_seg.x) * int64_t(v_pt.x) + int64_t(v_seg.y) * int64_t(v_pt.y); // l2 of seg int64_t l2_seg = int64_t(v_seg.x) * int64_t(v_seg.x) + int64_t(v_seg.y) * int64_t(v_seg.y); if (t_pt < 0) { // Closest to p1. double dabs = sqrt(int64_t(v_pt.x) * int64_t(v_pt.x) + int64_t(v_pt.y) * int64_t(v_pt.y)); if (dabs < d_min) { // Previous point. const Slic3r::Point &p0 = pts[(ipt == 0) ? (pts.size() - 1) : ipt - 1]; Slic3r::Point v_seg_prev = p0.vector_to(p1); int64_t t2_pt = int64_t(v_seg_prev.x) * int64_t(v_pt.x) + int64_t(v_seg_prev.y) * int64_t(v_pt.y); if (t2_pt > 0) { // Inside the wedge between the previous and the next segment. d_min = dabs; // Set the signum depending on whether the vertex is convex or reflex. int64_t det = int64_t(v_seg_prev.x) * int64_t(v_seg.y) - int64_t(v_seg_prev.y) * int64_t(v_seg.x); assert(det != 0); sign_min = (det > 0) ? 1 : -1; on_segment = false; } } } else if (t_pt > l2_seg) { // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the same cell. continue; } else { // Closest to the segment. assert(t_pt >= 0 && t_pt <= l2_seg); int64_t d_seg = int64_t(v_seg.y) * int64_t(v_pt.x) - int64_t(v_seg.x) * int64_t(v_pt.y); double d = double(d_seg) / sqrt(double(l2_seg)); double dabs = std::abs(d); if (dabs < d_min) { d_min = dabs; sign_min = (d_seg < 0) ? -1 : ((d_seg == 0) ? 0 : 1); on_segment = true; } } } } } if (d_min >= search_radius) return false; result_min_dist = d_min * sign_min; if (pon_segment != NULL) *pon_segment = on_segment; return true; } bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coordf_t &result_min_dist) const { if (signed_distance_edges(pt, search_radius, result_min_dist)) return true; if (m_signed_distance_field.empty()) return false; result_min_dist = signed_distance_bilinear(pt); return true; } Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const { typedef std::unordered_multimap EndPointMapType; // 0) Prepare a binary grid. size_t cell_rows = m_rows + 2; size_t cell_cols = m_cols + 2; std::vector cell_inside(cell_rows * cell_cols, false); for (int r = 0; r < int(cell_rows); ++ r) for (int c = 0; c < int(cell_cols); ++ c) cell_inside[r * cell_cols + c] = cell_inside_or_crossing(r - 1, c - 1); // Fill in empty cells, which have a left / right neighbor filled. // Fill in empty cells, which have the top / bottom neighbor filled. { std::vector cell_inside2(cell_inside); for (int r = 1; r + 1 < int(cell_rows); ++ r) { for (int c = 1; c + 1 < int(cell_cols); ++ c) { int addr = r * cell_cols + c; if ((cell_inside2[addr - 1] && cell_inside2[addr + 1]) || (cell_inside2[addr - cell_cols] && cell_inside2[addr + cell_cols])) cell_inside[addr] = true; } } } // 1) Collect the lines. std::vector lines; EndPointMapType start_point_to_line_idx; for (int r = 0; r <= int(m_rows); ++ r) { for (int c = 0; c <= int(m_cols); ++ c) { int addr = (r + 1) * cell_cols + c + 1; bool left = cell_inside[addr - 1]; bool top = cell_inside[addr - cell_cols]; bool current = cell_inside[addr]; if (left != current) { lines.push_back( left ? Line(Point(c, r+1), Point(c, r )) : Line(Point(c, r ), Point(c, r+1))); start_point_to_line_idx.insert(std::pair(lines.back().a, int(lines.size()) - 1)); } if (top != current) { lines.push_back( top ? Line(Point(c , r), Point(c+1, r)) : Line(Point(c+1, r), Point(c , r))); start_point_to_line_idx.insert(std::pair(lines.back().a, int(lines.size()) - 1)); } } } // 2) Chain the lines. std::vector line_processed(lines.size(), false); Polygons out; for (int i_candidate = 0; i_candidate < int(lines.size()); ++ i_candidate) { if (line_processed[i_candidate]) continue; Polygon poly; line_processed[i_candidate] = true; poly.points.push_back(lines[i_candidate].b); int i_line_current = i_candidate; for (;;) { std::pair line_range = start_point_to_line_idx.equal_range(lines[i_line_current].b); // The interval has to be non empty, there shall be at least one line continuing the current one. assert(line_range.first != line_range.second); int i_next = -1; for (EndPointMapType::iterator it = line_range.first; it != line_range.second; ++ it) { if (it->second == i_candidate) { // closing the loop. goto end_of_poly; } if (line_processed[it->second]) continue; if (i_next == -1) { i_next = it->second; } else { // This is a corner, where two lines meet exactly. Pick the line, which encloses a smallest angle with // the current edge. const Line &line_current = lines[i_line_current]; const Line &line_next = lines[it->second]; const Vector v1 = line_current.vector(); const Vector v2 = line_next.vector(); int64_t cross = int64_t(v1.x) * int64_t(v2.y) - int64_t(v2.x) * int64_t(v1.y); if (cross > 0) { // This has to be a convex right angle. There is no better next line. i_next = it->second; break; } } } line_processed[i_next] = true; i_line_current = i_next; poly.points.push_back(lines[i_line_current].b); } end_of_poly: out.push_back(std::move(poly)); } // 3) Scale the polygons back into world, shrink slightly and remove collinear points. for (size_t i = 0; i < out.size(); ++ i) { Polygon &poly = out[i]; for (size_t j = 0; j < poly.points.size(); ++ j) { Point &p = poly.points[j]; p.x *= m_resolution; p.y *= m_resolution; p.x += m_bbox.min.x; p.y += m_bbox.min.y; } // Shrink the contour slightly, so if the same contour gets discretized and simplified again, one will get the same result. // Remove collineaer points. Points pts; pts.reserve(poly.points.size()); for (size_t j = 0; j < poly.points.size(); ++ j) { size_t j0 = (j == 0) ? poly.points.size() - 1 : j - 1; size_t j2 = (j + 1 == poly.points.size()) ? 0 : j + 1; Point v = poly.points[j2] - poly.points[j0]; if (v.x != 0 && v.y != 0) { // This is a corner point. Copy it to the output contour. Point p = poly.points[j]; p.y += (v.x < 0) ? - offset : offset; p.x += (v.y > 0) ? - offset : offset; pts.push_back(p); } } poly.points = std::move(pts); } return out; } #if 0 void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path) { unsigned int w = (bbox.max.x - bbox.min.x + resolution - 1) / resolution; unsigned int h = (bbox.max.y - bbox.min.y + resolution - 1) / resolution; wxImage img(w, h); unsigned char *data = img.GetData(); memset(data, 0, w * h * 3); static int iRun = 0; ++iRun; const coord_t search_radius = grid.resolution() * 2; const coord_t display_blend_radius = grid.resolution() * 2; for (coord_t r = 0; r < h; ++r) { for (coord_t c = 0; c < w; ++ c) { unsigned char *pxl = data + (((h - r - 1) * w) + c) * 3; Point pt(c * resolution + bbox.min.x, r * resolution + bbox.min.y); coordf_t min_dist; bool on_segment = true; #if 0 if (grid.signed_distance_edges(pt, search_radius, min_dist, &on_segment)) { #else if (grid.signed_distance(pt, search_radius, min_dist)) { #endif float s = 255 * std::abs(min_dist) / float(display_blend_radius); int is = std::max(0, std::min(255, int(floor(s + 0.5f)))); if (min_dist < 0) { if (on_segment) { pxl[0] = 255; pxl[1] = 255 - is; pxl[2] = 255 - is; } else { pxl[0] = 255; pxl[1] = 0; pxl[2] = 255 - is; } } else { if (on_segment) { pxl[0] = 255 - is; pxl[1] = 255 - is; pxl[2] = 255; } else { pxl[0] = 255 - is; pxl[1] = 0; pxl[2] = 255; } } } else { pxl[0] = 0; pxl[1] = 255; pxl[2] = 0; } float gridx = float(pt.x - grid.bbox().min.x) / float(grid.resolution()); float gridy = float(pt.y - grid.bbox().min.y) / float(grid.resolution()); if (gridx >= -0.4f && gridy >= -0.4f && gridx <= grid.cols() + 0.4f && gridy <= grid.rows() + 0.4f) { int ix = int(floor(gridx + 0.5f)); int iy = int(floor(gridy + 0.5f)); float dx = gridx - float(ix); float dy = gridy - float(iy); float d = sqrt(dx*dx + dy*dy) * float(grid.resolution()) / float(resolution); if (d < 1.f) { // Less than 1 pixel from the grid point. float t = 0.5f + 0.5f * d; pxl[0] = (unsigned char)(t * pxl[0]); pxl[1] = (unsigned char)(t * pxl[1]); pxl[2] = (unsigned char)(t * pxl[2]); } } float dgrid = fabs(min_dist) / float(grid.resolution()); float igrid = floor(dgrid + 0.5f); dgrid = std::abs(dgrid - igrid) * float(grid.resolution()) / float(resolution); if (dgrid < 1.f) { // Less than 1 pixel from the grid point. float t = 0.5f + 0.5f * dgrid; pxl[0] = (unsigned char)(t * pxl[0]); pxl[1] = (unsigned char)(t * pxl[1]); pxl[2] = (unsigned char)(t * pxl[2]); if (igrid > 0.f) { // Other than zero iso contour. int g = pxl[1] + 255.f * (1.f - t); pxl[1] = std::min(g, 255); } } } } img.SaveFile(path, wxBITMAP_TYPE_PNG); } #endif /* SLIC3R_GUI */ } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/EdgeGrid.hpp000066400000000000000000000104721324354444700220210ustar00rootroot00000000000000#ifndef slic3r_EdgeGrid_hpp_ #define slic3r_EdgeGrid_hpp_ #include #include #include "Point.hpp" #include "BoundingBox.hpp" #include "ExPolygon.hpp" #include "ExPolygonCollection.hpp" namespace Slic3r { namespace EdgeGrid { class Grid { public: Grid(); ~Grid(); void set_bbox(const BoundingBox &bbox) { m_bbox = bbox; } void create(const Polygons &polygons, coord_t resolution); void create(const ExPolygon &expoly, coord_t resolution); void create(const ExPolygons &expolygons, coord_t resolution); void create(const ExPolygonCollection &expolygons, coord_t resolution); #if 0 // Test, whether the edges inside the grid intersect with the polygons provided. bool intersect(const MultiPoint &polyline, bool closed); bool intersect(const Polygon &polygon) { return intersect(static_cast(polygon), true); } bool intersect(const Polygons &polygons) { for (size_t i = 0; i < polygons.size(); ++ i) if (intersect(polygons[i])) return true; return false; } bool intersect(const ExPolygon &expoly) { if (intersect(expoly.contour)) return true; for (size_t i = 0; i < expoly.holes.size(); ++ i) if (intersect(expoly.holes[i])) return true; return false; } bool intersect(const ExPolygons &expolygons) { for (size_t i = 0; i < expolygons.size(); ++ i) if (intersect(expolygons[i])) return true; return false; } bool intersect(const ExPolygonCollection &expolygons) { return intersect(expolygons.expolygons); } // Test, whether a point is inside a contour. bool inside(const Point &pt); #endif // Fill in a rough m_signed_distance_field from the edge grid. // The rough SDF is used by signed_distance() for distances outside of the search_radius. void calculate_sdf(); // Return an estimate of the signed distance based on m_signed_distance_field grid. float signed_distance_bilinear(const Point &pt) const; // Calculate a signed distance to the contours in search_radius from the point. bool signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment = NULL) const; // Calculate a signed distance to the contours in search_radius from the point. If no edge is found in search_radius, // return an interpolated value from m_signed_distance_field, if it exists. bool signed_distance(const Point &pt, coord_t search_radius, coordf_t &result_min_dist) const; const BoundingBox& bbox() const { return m_bbox; } const coord_t resolution() const { return m_resolution; } const size_t rows() const { return m_rows; } const size_t cols() const { return m_cols; } // For supports: Contours enclosing the rasterized edges. Polygons contours_simplified(coord_t offset) const; protected: struct Cell { Cell() : begin(0), end(0) {} size_t begin; size_t end; }; void create_from_m_contours(coord_t resolution); #if 0 bool line_cell_intersect(const Point &p1, const Point &p2, const Cell &cell); #endif bool cell_inside_or_crossing(int r, int c) const { if (r < 0 || r >= m_rows || c < 0 || c >= m_cols) // The cell is outside the domain. Hoping that the contours were correctly oriented, so // there is a CCW outmost contour so the out of domain cells are outside. return false; const Cell &cell = m_cells[r * m_cols + c]; return (cell.begin < cell.end) || (! m_signed_distance_field.empty() && m_signed_distance_field[r * (m_cols + 1) + c] <= 0.f); } // Bounding box around the contours. BoundingBox m_bbox; // Grid dimensions. coord_t m_resolution; size_t m_rows; size_t m_cols; // Referencing the source contours. // This format allows one to work with any Slic3r fixed point contour format // (Polygon, ExPolygon, ExPolygonCollection etc). std::vector m_contours; // Referencing a contour and a line segment of m_contours. std::vector > m_cell_data; // Full grid of cells. std::vector m_cells; // Distance field derived from the edge grid, seed filled by the Danielsson chamfer metric. // May be empty. std::vector m_signed_distance_field; }; #if 0 // Debugging utility. Save the signed distance field. extern void save_png(const Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path); #endif /* SLIC3R_GUI */ } // namespace EdgeGrid } // namespace Slic3r #endif /* slic3r_EdgeGrid_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/ExPolygon.cpp000066400000000000000000000454021324354444700222670ustar00rootroot00000000000000#include "BoundingBox.hpp" #include "ExPolygon.hpp" #include "Geometry.hpp" #include "Polygon.hpp" #include "Line.hpp" #include "ClipperUtils.hpp" #include "SVG.hpp" #include "polypartition.h" #include "poly2tri/poly2tri.h" #include #include #include namespace Slic3r { ExPolygon::operator Points() const { Points points; Polygons pp = *this; for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) { for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point) points.push_back(*point); } return points; } ExPolygon::operator Polygons() const { return to_polygons(*this); } ExPolygon::operator Polylines() const { return to_polylines(*this); } void ExPolygon::scale(double factor) { contour.scale(factor); for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { (*it).scale(factor); } } void ExPolygon::translate(double x, double y) { contour.translate(x, y); for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { (*it).translate(x, y); } } void ExPolygon::rotate(double angle) { contour.rotate(angle); for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { (*it).rotate(angle); } } void ExPolygon::rotate(double angle, const Point ¢er) { contour.rotate(angle, center); for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { (*it).rotate(angle, center); } } double ExPolygon::area() const { double a = this->contour.area(); for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { a -= -(*it).area(); // holes have negative area } return a; } bool ExPolygon::is_valid() const { if (!this->contour.is_valid() || !this->contour.is_counter_clockwise()) return false; for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { if (!(*it).is_valid() || (*it).is_counter_clockwise()) return false; } return true; } bool ExPolygon::contains(const Line &line) const { return this->contains((Polyline)line); } bool ExPolygon::contains(const Polyline &polyline) const { return diff_pl((Polylines)polyline, *this).empty(); } bool ExPolygon::contains(const Polylines &polylines) const { #if 0 BoundingBox bbox = get_extents(polylines); bbox.merge(get_extents(*this)); SVG svg(debug_out_path("ExPolygon_contains.svg"), bbox); svg.draw(*this); svg.draw_outline(*this); svg.draw(polylines, "blue"); #endif Polylines pl_out = diff_pl(polylines, *this); #if 0 svg.draw(pl_out, "red"); #endif return pl_out.empty(); } bool ExPolygon::contains(const Point &point) const { if (!this->contour.contains(point)) return false; for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { if (it->contains(point)) return false; } return true; } // inclusive version of contains() that also checks whether point is on boundaries bool ExPolygon::contains_b(const Point &point) const { return this->contains(point) || this->has_boundary_point(point); } bool ExPolygon::has_boundary_point(const Point &point) const { if (this->contour.has_boundary_point(point)) return true; for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) { if (h->has_boundary_point(point)) return true; } return false; } bool ExPolygon::overlaps(const ExPolygon &other) const { #if 0 BoundingBox bbox = get_extents(other); bbox.merge(get_extents(*this)); static int iRun = 0; SVG svg(debug_out_path("ExPolygon_overlaps-%d.svg", iRun ++), bbox); svg.draw(*this); svg.draw_outline(*this); svg.draw_outline(other, "blue"); #endif Polylines pl_out = intersection_pl((Polylines)other, *this); #if 0 svg.draw(pl_out, "red"); #endif if (! pl_out.empty()) return true; return ! other.contour.points.empty() && this->contains_b(other.contour.points.front()); } void ExPolygon::simplify_p(double tolerance, Polygons* polygons) const { Polygons pp = this->simplify_p(tolerance); polygons->insert(polygons->end(), pp.begin(), pp.end()); } Polygons ExPolygon::simplify_p(double tolerance) const { Polygons pp; pp.reserve(this->holes.size() + 1); // contour { Polygon p = this->contour; p.points.push_back(p.points.front()); p.points = MultiPoint::_douglas_peucker(p.points, tolerance); p.points.pop_back(); pp.push_back(p); } // holes for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { Polygon p = *it; p.points.push_back(p.points.front()); p.points = MultiPoint::_douglas_peucker(p.points, tolerance); p.points.pop_back(); pp.push_back(p); } pp = simplify_polygons(pp); return pp; } ExPolygons ExPolygon::simplify(double tolerance) const { Polygons pp = this->simplify_p(tolerance); return union_ex(pp); } void ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const { ExPolygons ep = this->simplify(tolerance); expolygons->insert(expolygons->end(), ep.begin(), ep.end()); } void ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const { // init helper object Slic3r::Geometry::MedialAxis ma(max_width, min_width, this); ma.lines = this->lines(); // compute the Voronoi diagram and extract medial axis polylines ThickPolylines pp; ma.build(&pp); /* SVG svg("medial_axis.svg"); svg.draw(*this); svg.draw(pp); svg.Close(); */ /* Find the maximum width returned; we're going to use this for validating and filtering the output segments. */ double max_w = 0; for (ThickPolylines::const_iterator it = pp.begin(); it != pp.end(); ++it) max_w = fmaxf(max_w, *std::max_element(it->width.begin(), it->width.end())); /* Loop through all returned polylines in order to extend their endpoints to the expolygon boundaries */ bool removed = false; for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline& polyline = pp[i]; // extend initial and final segments of each polyline if they're actual endpoints /* We assign new endpoints to temporary variables because in case of a single-line polyline, after we extend the start point it will be caught by the intersection() call, so we keep the inner point until we perform the second intersection() as well */ Point new_front = polyline.points.front(); Point new_back = polyline.points.back(); if (polyline.endpoints.first && !this->has_boundary_point(new_front)) { Line line(polyline.points.front(), polyline.points[1]); // prevent the line from touching on the other side, otherwise intersection() might return that solution if (polyline.points.size() == 2) line.b = line.midpoint(); line.extend_start(max_width); (void)this->contour.intersection(line, &new_front); } if (polyline.endpoints.second && !this->has_boundary_point(new_back)) { Line line( *(polyline.points.end() - 2), polyline.points.back() ); // prevent the line from touching on the other side, otherwise intersection() might return that solution if (polyline.points.size() == 2) line.a = line.midpoint(); line.extend_end(max_width); (void)this->contour.intersection(line, &new_back); } polyline.points.front() = new_front; polyline.points.back() = new_back; /* remove too short polylines (we can't do this check before endpoints extension and clipping because we don't know how long will the endpoints be extended since it depends on polygon thickness which is variable - extension will be <= max_width/2 on each side) */ if ((polyline.endpoints.first || polyline.endpoints.second) && polyline.length() < max_w*2) { pp.erase(pp.begin() + i); --i; removed = true; continue; } } /* If we removed any short polylines we now try to connect consecutive polylines in order to allow loop detection. Note that this algorithm is greedier than MedialAxis::process_edge_neighbors() as it will connect random pairs of polylines even when more than two start from the same point. This has no drawbacks since we optimize later using nearest-neighbor which would do the same, but should we use a more sophisticated optimization algorithm we should not connect polylines when more than two meet. */ if (removed) { for (size_t i = 0; i < pp.size(); ++i) { ThickPolyline& polyline = pp[i]; if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization // find another polyline starting here for (size_t j = i+1; j < pp.size(); ++j) { ThickPolyline& other = pp[j]; if (polyline.last_point().coincides_with(other.last_point())) { other.reverse(); } else if (polyline.first_point().coincides_with(other.last_point())) { polyline.reverse(); other.reverse(); } else if (polyline.first_point().coincides_with(other.first_point())) { polyline.reverse(); } else if (!polyline.last_point().coincides_with(other.first_point())) { continue; } polyline.points.insert(polyline.points.end(), other.points.begin() + 1, other.points.end()); polyline.width.insert(polyline.width.end(), other.width.begin(), other.width.end()); polyline.endpoints.second = other.endpoints.second; assert(polyline.width.size() == polyline.points.size()*2 - 2); pp.erase(pp.begin() + j); j = i; // restart search from i+1 } } } polylines->insert(polylines->end(), pp.begin(), pp.end()); } void ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const { ThickPolylines tp; this->medial_axis(max_width, min_width, &tp); polylines->insert(polylines->end(), tp.begin(), tp.end()); } void ExPolygon::get_trapezoids(Polygons* polygons) const { ExPolygons expp; expp.push_back(*this); boost::polygon::get_trapezoids(*polygons, expp); } void ExPolygon::get_trapezoids(Polygons* polygons, double angle) const { ExPolygon clone = *this; clone.rotate(PI/2 - angle, Point(0,0)); clone.get_trapezoids(polygons); for (Polygons::iterator polygon = polygons->begin(); polygon != polygons->end(); ++polygon) polygon->rotate(-(PI/2 - angle), Point(0,0)); } // This algorithm may return more trapezoids than necessary // (i.e. it may break a single trapezoid in several because // other parts of the object have x coordinates in the middle) void ExPolygon::get_trapezoids2(Polygons* polygons) const { // get all points of this ExPolygon Points pp = *this; // build our bounding box BoundingBox bb(pp); // get all x coordinates std::vector xx; xx.reserve(pp.size()); for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p) xx.push_back(p->x); std::sort(xx.begin(), xx.end()); // find trapezoids by looping from first to next-to-last coordinate for (std::vector::const_iterator x = xx.begin(); x != xx.end()-1; ++x) { coord_t next_x = *(x + 1); if (*x == next_x) continue; // build rectangle Polygon poly; poly.points.resize(4); poly[0].x = *x; poly[0].y = bb.min.y; poly[1].x = next_x; poly[1].y = bb.min.y; poly[2].x = next_x; poly[2].y = bb.max.y; poly[3].x = *x; poly[3].y = bb.max.y; // intersect with this expolygon // append results to return value polygons_append(*polygons, intersection(poly, to_polygons(*this))); } } void ExPolygon::get_trapezoids2(Polygons* polygons, double angle) const { ExPolygon clone = *this; clone.rotate(PI/2 - angle, Point(0,0)); clone.get_trapezoids2(polygons); for (Polygons::iterator polygon = polygons->begin(); polygon != polygons->end(); ++polygon) polygon->rotate(-(PI/2 - angle), Point(0,0)); } // While this triangulates successfully, it's NOT a constrained triangulation // as it will create more vertices on the boundaries than the ones supplied. void ExPolygon::triangulate(Polygons* polygons) const { // first make trapezoids Polygons trapezoids; this->get_trapezoids2(&trapezoids); // then triangulate each trapezoid for (Polygons::iterator polygon = trapezoids.begin(); polygon != trapezoids.end(); ++polygon) polygon->triangulate_convex(polygons); } void ExPolygon::triangulate_pp(Polygons* polygons) const { // convert polygons std::list input; ExPolygons expp = union_ex(simplify_polygons(to_polygons(*this), true)); for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) { // contour { TPPLPoly p; p.Init(int(ex->contour.points.size())); //printf(PRINTF_ZU "\n0\n", ex->contour.points.size()); for (Points::const_iterator point = ex->contour.points.begin(); point != ex->contour.points.end(); ++point) { p[ point-ex->contour.points.begin() ].x = point->x; p[ point-ex->contour.points.begin() ].y = point->y; //printf("%ld %ld\n", point->x, point->y); } p.SetHole(false); input.push_back(p); } // holes for (Polygons::const_iterator hole = ex->holes.begin(); hole != ex->holes.end(); ++hole) { TPPLPoly p; p.Init(hole->points.size()); //printf(PRINTF_ZU "\n1\n", hole->points.size()); for (Points::const_iterator point = hole->points.begin(); point != hole->points.end(); ++point) { p[ point-hole->points.begin() ].x = point->x; p[ point-hole->points.begin() ].y = point->y; //printf("%ld %ld\n", point->x, point->y); } p.SetHole(true); input.push_back(p); } } // perform triangulation std::list output; int res = TPPLPartition().Triangulate_MONO(&input, &output); if (res != 1) CONFESS("Triangulation failed"); // convert output polygons for (std::list::iterator poly = output.begin(); poly != output.end(); ++poly) { long num_points = poly->GetNumPoints(); Polygon p; p.points.resize(num_points); for (long i = 0; i < num_points; ++i) { p.points[i].x = coord_t((*poly)[i].x); p.points[i].y = coord_t((*poly)[i].y); } polygons->push_back(p); } } void ExPolygon::triangulate_p2t(Polygons* polygons) const { ExPolygons expp = simplify_polygons_ex(*this, true); for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) { // TODO: prevent duplicate points // contour std::vector ContourPoints; for (Points::const_iterator point = ex->contour.points.begin(); point != ex->contour.points.end(); ++point) { // We should delete each p2t::Point object ContourPoints.push_back(new p2t::Point(point->x, point->y)); } p2t::CDT cdt(ContourPoints); // holes for (Polygons::const_iterator hole = ex->holes.begin(); hole != ex->holes.end(); ++hole) { std::vector points; for (Points::const_iterator point = hole->points.begin(); point != hole->points.end(); ++point) { // will be destructed in SweepContext::~SweepContext points.push_back(new p2t::Point(point->x, point->y)); } cdt.AddHole(points); } // perform triangulation cdt.Triangulate(); std::vector triangles = cdt.GetTriangles(); for (std::vector::const_iterator triangle = triangles.begin(); triangle != triangles.end(); ++triangle) { Polygon p; for (int i = 0; i <= 2; ++i) { p2t::Point* point = (*triangle)->GetPoint(i); p.points.push_back(Point(point->x, point->y)); } polygons->push_back(p); } for(std::vector::iterator it = ContourPoints.begin(); it != ContourPoints.end(); ++it) { delete *it; } } } Lines ExPolygon::lines() const { Lines lines = this->contour.lines(); for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) { Lines hole_lines = h->lines(); lines.insert(lines.end(), hole_lines.begin(), hole_lines.end()); } return lines; } std::string ExPolygon::dump_perl() const { std::ostringstream ret; ret << "[" << this->contour.dump_perl(); for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) ret << "," << h->dump_perl(); ret << "]"; return ret.str(); } BoundingBox get_extents(const ExPolygon &expolygon) { return get_extents(expolygon.contour); } BoundingBox get_extents(const ExPolygons &expolygons) { BoundingBox bbox; if (! expolygons.empty()) { for (size_t i = 0; i < expolygons.size(); ++ i) if (! expolygons[i].contour.points.empty()) bbox.merge(get_extents(expolygons[i])); } return bbox; } BoundingBox get_extents_rotated(const ExPolygon &expolygon, double angle) { return get_extents_rotated(expolygon.contour, angle); } BoundingBox get_extents_rotated(const ExPolygons &expolygons, double angle) { BoundingBox bbox; if (! expolygons.empty()) { bbox = get_extents_rotated(expolygons.front().contour, angle); for (size_t i = 1; i < expolygons.size(); ++ i) bbox.merge(get_extents_rotated(expolygons[i].contour, angle)); } return bbox; } extern std::vector get_extents_vector(const ExPolygons &polygons) { std::vector out; out.reserve(polygons.size()); for (ExPolygons::const_iterator it = polygons.begin(); it != polygons.end(); ++ it) out.push_back(get_extents(*it)); return out; } bool remove_sticks(ExPolygon &poly) { return remove_sticks(poly.contour) || remove_sticks(poly.holes); } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/ExPolygon.hpp000066400000000000000000000342561324354444700223010ustar00rootroot00000000000000#ifndef slic3r_ExPolygon_hpp_ #define slic3r_ExPolygon_hpp_ #include "libslic3r.h" #include "Polygon.hpp" #include "Polyline.hpp" #include namespace Slic3r { class ExPolygon; typedef std::vector ExPolygons; class ExPolygon { public: ExPolygon() {} ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {} ExPolygon(ExPolygon &&other) : contour(std::move(other.contour)), holes(std::move(other.holes)) {} ExPolygon& operator=(const ExPolygon &other) { contour = other.contour; holes = other.holes; return *this; } ExPolygon& operator=(ExPolygon &&other) { contour = std::move(other.contour); holes = std::move(other.holes); return *this; } Polygon contour; Polygons holes; operator Points() const; operator Polygons() const; operator Polylines() const; void clear() { contour.points.clear(); holes.clear(); } void scale(double factor); void translate(double x, double y); void rotate(double angle); void rotate(double angle, const Point ¢er); double area() const; bool empty() const { return contour.points.empty(); } bool is_valid() const; // Contains the line / polyline / polylines etc COMPLETELY. bool contains(const Line &line) const; bool contains(const Polyline &polyline) const; bool contains(const Polylines &polylines) const; bool contains(const Point &point) const; bool contains_b(const Point &point) const; bool has_boundary_point(const Point &point) const; // Does this expolygon overlap another expolygon? // Either the ExPolygons intersect, or one is fully inside the other, // and it is not inside a hole of the other expolygon. bool overlaps(const ExPolygon &other) const; void simplify_p(double tolerance, Polygons* polygons) const; Polygons simplify_p(double tolerance) const; ExPolygons simplify(double tolerance) const; void simplify(double tolerance, ExPolygons* expolygons) const; void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const; void medial_axis(double max_width, double min_width, Polylines* polylines) const; void get_trapezoids(Polygons* polygons) const; void get_trapezoids(Polygons* polygons, double angle) const; void get_trapezoids2(Polygons* polygons) const; void get_trapezoids2(Polygons* polygons, double angle) const; void triangulate(Polygons* polygons) const; void triangulate_pp(Polygons* polygons) const; void triangulate_p2t(Polygons* polygons) const; Lines lines() const; std::string dump_perl() const; }; // Count a nuber of polygons stored inside the vector of expolygons. // Useful for allocating space for polygons when converting expolygons to polygons. inline size_t number_polygons(const ExPolygons &expolys) { size_t n_polygons = 0; for (ExPolygons::const_iterator it = expolys.begin(); it != expolys.end(); ++ it) n_polygons += it->holes.size() + 1; return n_polygons; } inline Lines to_lines(const ExPolygon &src) { size_t n_lines = src.contour.points.size(); for (size_t i = 0; i < src.holes.size(); ++ i) n_lines += src.holes[i].points.size(); Lines lines; lines.reserve(n_lines); for (size_t i = 0; i <= src.holes.size(); ++ i) { const Polygon &poly = (i == 0) ? src.contour : src.holes[i - 1]; for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) lines.push_back(Line(*it, *(it + 1))); lines.push_back(Line(poly.points.back(), poly.points.front())); } return lines; } inline Lines to_lines(const ExPolygons &src) { size_t n_lines = 0; for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) { n_lines += it_expoly->contour.points.size(); for (size_t i = 0; i < it_expoly->holes.size(); ++ i) n_lines += it_expoly->holes[i].points.size(); } Lines lines; lines.reserve(n_lines); for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) { for (size_t i = 0; i <= it_expoly->holes.size(); ++ i) { const Points &points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points; for (Points::const_iterator it = points.begin(); it != points.end()-1; ++it) lines.push_back(Line(*it, *(it + 1))); lines.push_back(Line(points.back(), points.front())); } } return lines; } inline Polylines to_polylines(const ExPolygon &src) { Polylines polylines; polylines.assign(src.holes.size() + 1, Polyline()); size_t idx = 0; Polyline &pl = polylines[idx ++]; pl.points = src.contour.points; pl.points.push_back(pl.points.front()); for (Polygons::const_iterator ith = src.holes.begin(); ith != src.holes.end(); ++ith) { Polyline &pl = polylines[idx ++]; pl.points = ith->points; pl.points.push_back(ith->points.front()); } assert(idx == polylines.size()); return polylines; } inline Polylines to_polylines(const ExPolygons &src) { Polylines polylines; polylines.assign(number_polygons(src), Polyline()); size_t idx = 0; for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++it) { Polyline &pl = polylines[idx ++]; pl.points = it->contour.points; pl.points.push_back(pl.points.front()); for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) { Polyline &pl = polylines[idx ++]; pl.points = ith->points; pl.points.push_back(ith->points.front()); } } assert(idx == polylines.size()); return polylines; } inline Polylines to_polylines(ExPolygon &&src) { Polylines polylines; polylines.assign(src.holes.size() + 1, Polyline()); size_t idx = 0; Polyline &pl = polylines[idx ++]; pl.points = std::move(src.contour.points); pl.points.push_back(pl.points.front()); for (Polygons::const_iterator ith = src.holes.begin(); ith != src.holes.end(); ++ith) { Polyline &pl = polylines[idx ++]; pl.points = std::move(ith->points); pl.points.push_back(ith->points.front()); } assert(idx == polylines.size()); return polylines; } inline Polylines to_polylines(ExPolygons &&src) { Polylines polylines; polylines.assign(number_polygons(src), Polyline()); size_t idx = 0; for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++it) { Polyline &pl = polylines[idx ++]; pl.points = std::move(it->contour.points); pl.points.push_back(pl.points.front()); for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) { Polyline &pl = polylines[idx ++]; pl.points = std::move(ith->points); pl.points.push_back(ith->points.front()); } } assert(idx == polylines.size()); return polylines; } inline Polygons to_polygons(const ExPolygon &src) { Polygons polygons; polygons.reserve(src.holes.size() + 1); polygons.push_back(src.contour); polygons.insert(polygons.end(), src.holes.begin(), src.holes.end()); return polygons; } inline Polygons to_polygons(const ExPolygons &src) { Polygons polygons; polygons.reserve(number_polygons(src)); for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++it) { polygons.push_back(it->contour); polygons.insert(polygons.end(), it->holes.begin(), it->holes.end()); } return polygons; } inline Polygons to_polygons(ExPolygon &&src) { Polygons polygons; polygons.reserve(src.holes.size() + 1); polygons.push_back(std::move(src.contour)); std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(polygons)); src.holes.clear(); return polygons; } inline Polygons to_polygons(ExPolygons &&src) { Polygons polygons; polygons.reserve(number_polygons(src)); for (ExPolygons::iterator it = src.begin(); it != src.end(); ++it) { polygons.push_back(std::move(it->contour)); std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(polygons)); it->holes.clear(); } return polygons; } inline void polygons_append(Polygons &dst, const ExPolygon &src) { dst.reserve(dst.size() + src.holes.size() + 1); dst.push_back(src.contour); dst.insert(dst.end(), src.holes.begin(), src.holes.end()); } inline void polygons_append(Polygons &dst, const ExPolygons &src) { dst.reserve(dst.size() + number_polygons(src)); for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) { dst.push_back(it->contour); dst.insert(dst.end(), it->holes.begin(), it->holes.end()); } } inline void polygons_append(Polygons &dst, ExPolygon &&src) { dst.reserve(dst.size() + src.holes.size() + 1); dst.push_back(std::move(src.contour)); std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(dst)); src.holes.clear(); } inline void polygons_append(Polygons &dst, ExPolygons &&src) { dst.reserve(dst.size() + number_polygons(src)); for (ExPolygons::iterator it = src.begin(); it != src.end(); ++ it) { dst.push_back(std::move(it->contour)); std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(dst)); it->holes.clear(); } } inline void expolygons_append(ExPolygons &dst, const ExPolygons &src) { dst.insert(dst.end(), src.begin(), src.end()); } inline void expolygons_append(ExPolygons &dst, ExPolygons &&src) { if (dst.empty()) { dst = std::move(src); } else { std::move(std::begin(src), std::end(src), std::back_inserter(dst)); src.clear(); } } inline void expolygons_rotate(ExPolygons &expolys, double angle) { for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p) p->rotate(angle); } inline bool expolygons_contain(ExPolygons &expolys, const Point &pt) { for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p) if (p->contains(pt)) return true; return false; } extern BoundingBox get_extents(const ExPolygon &expolygon); extern BoundingBox get_extents(const ExPolygons &expolygons); extern BoundingBox get_extents_rotated(const ExPolygon &poly, double angle); extern BoundingBox get_extents_rotated(const ExPolygons &polygons, double angle); extern std::vector get_extents_vector(const ExPolygons &polygons); extern bool remove_sticks(ExPolygon &poly); } // namespace Slic3r // start Boost #include namespace boost { namespace polygon { template <> struct polygon_traits { typedef coord_t coordinate_type; typedef Slic3r::Points::const_iterator iterator_type; typedef Slic3r::Point point_type; // Get the begin iterator static inline iterator_type begin_points(const Slic3r::ExPolygon& t) { return t.contour.points.begin(); } // Get the end iterator static inline iterator_type end_points(const Slic3r::ExPolygon& t) { return t.contour.points.end(); } // Get the number of sides of the polygon static inline std::size_t size(const Slic3r::ExPolygon& t) { return t.contour.points.size(); } // Get the winding direction of the polygon static inline winding_direction winding(const Slic3r::ExPolygon& /* t */) { return unknown_winding; } }; template <> struct polygon_mutable_traits { //expects stl style iterators template static inline Slic3r::ExPolygon& set_points(Slic3r::ExPolygon& expolygon, iT input_begin, iT input_end) { expolygon.contour.points.assign(input_begin, input_end); // skip last point since Boost will set last point = first point expolygon.contour.points.pop_back(); return expolygon; } }; template <> struct geometry_concept { typedef polygon_with_holes_concept type; }; template <> struct polygon_with_holes_traits { typedef Slic3r::Polygons::const_iterator iterator_holes_type; typedef Slic3r::Polygon hole_type; static inline iterator_holes_type begin_holes(const Slic3r::ExPolygon& t) { return t.holes.begin(); } static inline iterator_holes_type end_holes(const Slic3r::ExPolygon& t) { return t.holes.end(); } static inline unsigned int size_holes(const Slic3r::ExPolygon& t) { return (int)t.holes.size(); } }; template <> struct polygon_with_holes_mutable_traits { template static inline Slic3r::ExPolygon& set_holes(Slic3r::ExPolygon& t, iT inputBegin, iT inputEnd) { t.holes.assign(inputBegin, inputEnd); return t; } }; //first we register CPolygonSet as a polygon set template <> struct geometry_concept { typedef polygon_set_concept type; }; //next we map to the concept through traits template <> struct polygon_set_traits { typedef coord_t coordinate_type; typedef Slic3r::ExPolygons::const_iterator iterator_type; typedef Slic3r::ExPolygons operator_arg_type; static inline iterator_type begin(const Slic3r::ExPolygons& polygon_set) { return polygon_set.begin(); } static inline iterator_type end(const Slic3r::ExPolygons& polygon_set) { return polygon_set.end(); } //don't worry about these, just return false from them static inline bool clean(const Slic3r::ExPolygons& /* polygon_set */) { return false; } static inline bool sorted(const Slic3r::ExPolygons& /* polygon_set */) { return false; } }; template <> struct polygon_set_mutable_traits { template static inline void set(Slic3r::ExPolygons& expolygons, input_iterator_type input_begin, input_iterator_type input_end) { expolygons.assign(input_begin, input_end); } }; } } // end Boost #endif Slic3r-version_1.39.1/xs/src/libslic3r/ExPolygonCollection.cpp000066400000000000000000000071721324354444700243050ustar00rootroot00000000000000#include "ExPolygonCollection.hpp" #include "Geometry.hpp" namespace Slic3r { ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon) { this->expolygons.push_back(expolygon); } ExPolygonCollection::operator Points() const { Points points; Polygons pp = *this; for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) { for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point) points.push_back(*point); } return points; } ExPolygonCollection::operator Polygons() const { Polygons polygons; for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { polygons.push_back(it->contour); for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) { polygons.push_back(*ith); } } return polygons; } ExPolygonCollection::operator ExPolygons&() { return this->expolygons; } void ExPolygonCollection::scale(double factor) { for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { (*it).scale(factor); } } void ExPolygonCollection::translate(double x, double y) { for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { (*it).translate(x, y); } } void ExPolygonCollection::rotate(double angle, const Point ¢er) { for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { (*it).rotate(angle, center); } } template bool ExPolygonCollection::contains(const T &item) const { for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { if (it->contains(item)) return true; } return false; } template bool ExPolygonCollection::contains(const Point &item) const; template bool ExPolygonCollection::contains(const Line &item) const; template bool ExPolygonCollection::contains(const Polyline &item) const; bool ExPolygonCollection::contains_b(const Point &point) const { for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { if (it->contains_b(point)) return true; } return false; } void ExPolygonCollection::simplify(double tolerance) { ExPolygons expp; for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { it->simplify(tolerance, &expp); } this->expolygons = expp; } Polygon ExPolygonCollection::convex_hull() const { Points pp; for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) pp.insert(pp.end(), it->contour.points.begin(), it->contour.points.end()); return Slic3r::Geometry::convex_hull(pp); } Lines ExPolygonCollection::lines() const { Lines lines; for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) { Lines ex_lines = it->lines(); lines.insert(lines.end(), ex_lines.begin(), ex_lines.end()); } return lines; } Polygons ExPolygonCollection::contours() const { Polygons contours; contours.reserve(this->expolygons.size()); for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) contours.push_back(it->contour); return contours; } void ExPolygonCollection::append(const ExPolygons &expp) { this->expolygons.insert(this->expolygons.end(), expp.begin(), expp.end()); } BoundingBox get_extents(const ExPolygonCollection &expolygon) { return get_extents(expolygon.expolygons); } } Slic3r-version_1.39.1/xs/src/libslic3r/ExPolygonCollection.hpp000066400000000000000000000021121324354444700242770ustar00rootroot00000000000000#ifndef slic3r_ExPolygonCollection_hpp_ #define slic3r_ExPolygonCollection_hpp_ #include "libslic3r.h" #include "ExPolygon.hpp" #include "Line.hpp" #include "Polyline.hpp" namespace Slic3r { class ExPolygonCollection; typedef std::vector ExPolygonCollections; class ExPolygonCollection { public: ExPolygons expolygons; ExPolygonCollection() {}; ExPolygonCollection(const ExPolygon &expolygon); ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {}; operator Points() const; operator Polygons() const; operator ExPolygons&(); void scale(double factor); void translate(double x, double y); void rotate(double angle, const Point ¢er); template bool contains(const T &item) const; bool contains_b(const Point &point) const; void simplify(double tolerance); Polygon convex_hull() const; Lines lines() const; Polygons contours() const; void append(const ExPolygons &expolygons); }; extern BoundingBox get_extents(const ExPolygonCollection &expolygon); } #endif Slic3r-version_1.39.1/xs/src/libslic3r/Extruder.cpp000066400000000000000000000070331324354444700221430ustar00rootroot00000000000000#include "Extruder.hpp" namespace Slic3r { Extruder::Extruder(unsigned int id, GCodeConfig *config) : m_id(id), m_config(config) { reset(); // cache values that are going to be called often m_e_per_mm3 = this->extrusion_multiplier(); if (! m_config->use_volumetric_e) m_e_per_mm3 /= this->filament_crossection(); } double Extruder::extrude(double dE) { // in case of relative E distances we always reset to 0 before any output if (m_config->use_relative_e_distances) m_E = 0.; m_E += dE; m_absolute_E += dE; if (dE < 0.) m_retracted -= dE; return dE; } /* This method makes sure the extruder is retracted by the specified amount of filament and returns the amount of filament retracted. If the extruder is already retracted by the same or a greater amount, this method is a no-op. The restart_extra argument sets the extra length to be used for unretraction. If we're actually performing a retraction, any restart_extra value supplied will overwrite the previous one if any. */ double Extruder::retract(double length, double restart_extra) { // in case of relative E distances we always reset to 0 before any output if (m_config->use_relative_e_distances) m_E = 0.; double to_retract = std::max(0., length - m_retracted); if (to_retract > 0.) { m_E -= to_retract; m_absolute_E -= to_retract; m_retracted += to_retract; m_restart_extra = restart_extra; } return to_retract; } double Extruder::unretract() { double dE = m_retracted + m_restart_extra; this->extrude(dE); m_retracted = 0.; m_restart_extra = 0.; return dE; } // Used filament volume in mm^3. double Extruder::extruded_volume() const { return m_config->use_volumetric_e ? m_absolute_E + m_retracted : this->used_filament() * this->filament_crossection(); } // Used filament length in mm. double Extruder::used_filament() const { return m_config->use_volumetric_e ? this->extruded_volume() / this->filament_crossection() : m_absolute_E + m_retracted; } double Extruder::filament_diameter() const { return m_config->filament_diameter.get_at(m_id); } double Extruder::filament_density() const { return m_config->filament_density.get_at(m_id); } double Extruder::filament_cost() const { return m_config->filament_cost.get_at(m_id); } double Extruder::extrusion_multiplier() const { return m_config->extrusion_multiplier.get_at(m_id); } // Return a "retract_before_wipe" percentage as a factor clamped to <0, 1> double Extruder::retract_before_wipe() const { return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_id) * 0.01)); } double Extruder::retract_length() const { return m_config->retract_length.get_at(m_id); } double Extruder::retract_lift() const { return m_config->retract_lift.get_at(m_id); } int Extruder::retract_speed() const { return int(floor(m_config->retract_speed.get_at(m_id)+0.5)); } int Extruder::deretract_speed() const { int speed = int(floor(m_config->deretract_speed.get_at(m_id)+0.5)); return (speed > 0) ? speed : this->retract_speed(); } double Extruder::retract_restart_extra() const { return m_config->retract_restart_extra.get_at(m_id); } double Extruder::retract_length_toolchange() const { return m_config->retract_length_toolchange.get_at(m_id); } double Extruder::retract_restart_extra_toolchange() const { return m_config->retract_restart_extra_toolchange.get_at(m_id); } } Slic3r-version_1.39.1/xs/src/libslic3r/Extruder.hpp000066400000000000000000000054531324354444700221540ustar00rootroot00000000000000#ifndef slic3r_Extruder_hpp_ #define slic3r_Extruder_hpp_ #include "libslic3r.h" #include "Point.hpp" #include "PrintConfig.hpp" namespace Slic3r { class Extruder { public: Extruder(unsigned int id, GCodeConfig *config); virtual ~Extruder() {} void reset() { m_E = 0; m_absolute_E = 0; m_retracted = 0; m_restart_extra = 0; } unsigned int id() const { return m_id; } double extrude(double dE); double retract(double length, double restart_extra); double unretract(); double E() const { return m_E; } void reset_E() { m_E = 0.; } double e_per_mm(double mm3_per_mm) const { return mm3_per_mm * m_e_per_mm3; } double e_per_mm3() const { return m_e_per_mm3; } // Used filament volume in mm^3. double extruded_volume() const; // Used filament length in mm. double used_filament() const; double filament_diameter() const; double filament_crossection() const { return this->filament_diameter() * this->filament_diameter() * 0.25 * PI; } double filament_density() const; double filament_cost() const; double extrusion_multiplier() const; double retract_before_wipe() const; double retract_length() const; double retract_lift() const; int retract_speed() const; int deretract_speed() const; double retract_restart_extra() const; double retract_length_toolchange() const; double retract_restart_extra_toolchange() const; // Constructor for a key object, to be used by the stdlib search functions. static Extruder key(unsigned int id) { return Extruder(id); } private: // Private constructor to create a key for a search in std::set. Extruder(unsigned int id) : m_id(id) {} // Reference to GCodeWriter instance owned by GCodeWriter. GCodeConfig *m_config; // Print-wide global ID of this extruder. unsigned int m_id; // Current state of the extruder axis, may be resetted if use_relative_e_distances. double m_E; // Current state of the extruder tachometer, used to output the extruded_volume() and used_filament() statistics. double m_absolute_E; // Current positive amount of retraction. double m_retracted; // When retracted, this value stores the extra amount of priming on deretraction. double m_restart_extra; double m_e_per_mm3; }; // Sort Extruder objects by the extruder id by default. inline bool operator==(const Extruder &e1, const Extruder &e2) { return e1.id() == e2.id(); } inline bool operator!=(const Extruder &e1, const Extruder &e2) { return e1.id() != e2.id(); } inline bool operator< (const Extruder &e1, const Extruder &e2) { return e1.id() < e2.id(); } inline bool operator> (const Extruder &e1, const Extruder &e2) { return e1.id() > e2.id(); } } #endif Slic3r-version_1.39.1/xs/src/libslic3r/ExtrusionEntity.cpp000066400000000000000000000267601324354444700235460ustar00rootroot00000000000000#include "ExtrusionEntity.hpp" #include "ExtrusionEntityCollection.hpp" #include "ExPolygonCollection.hpp" #include "ClipperUtils.hpp" #include "Extruder.hpp" #include "Flow.hpp" #include #include #include namespace Slic3r { void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { this->_inflate_collection(intersection_pl(this->polyline, collection), retval); } void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { this->_inflate_collection(diff_pl(this->polyline, collection), retval); } void ExtrusionPath::clip_end(double distance) { this->polyline.clip_end(distance); } void ExtrusionPath::simplify(double tolerance) { this->polyline.simplify(tolerance); } double ExtrusionPath::length() const { return this->polyline.length(); } void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const { for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) { ExtrusionPath* path = this->clone(); path->polyline = *it; collection->entities.push_back(path); } } void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { polygons_append(out, offset(this->polyline, float(scale_(this->width/2)) + scaled_epsilon)); } void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const { // Instantiating the Flow class to get the line spacing. // Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler. Flow flow(this->width, this->height, 0.f, is_bridge(this->role())); polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon)); } void ExtrusionMultiPath::reverse() { for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) path->reverse(); std::reverse(this->paths.begin(), this->paths.end()); } double ExtrusionMultiPath::length() const { double len = 0; for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) len += path->polyline.length(); return len; } void ExtrusionMultiPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) path->polygons_covered_by_width(out, scaled_epsilon); } void ExtrusionMultiPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const { for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) path->polygons_covered_by_spacing(out, scaled_epsilon); } double ExtrusionMultiPath::min_mm3_per_mm() const { double min_mm3_per_mm = std::numeric_limits::max(); for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) min_mm3_per_mm = std::min(min_mm3_per_mm, path->mm3_per_mm); return min_mm3_per_mm; } Polyline ExtrusionMultiPath::as_polyline() const { Polyline out; if (! paths.empty()) { size_t len = 0; for (size_t i_path = 0; i_path < paths.size(); ++ i_path) { assert(! paths[i_path].polyline.points.empty()); assert(i_path == 0 || paths[i_path - 1].polyline.points.back() == paths[i_path].polyline.points.front()); len += paths[i_path].polyline.points.size(); } // The connecting points between the segments are equal. len -= paths.size() - 1; assert(len > 0); out.points.reserve(len); out.points.push_back(paths.front().polyline.points.front()); for (size_t i_path = 0; i_path < paths.size(); ++ i_path) out.points.insert(out.points.end(), paths[i_path].polyline.points.begin() + 1, paths[i_path].polyline.points.end()); } return out; } bool ExtrusionLoop::make_clockwise() { bool was_ccw = this->polygon().is_counter_clockwise(); if (was_ccw) this->reverse(); return was_ccw; } bool ExtrusionLoop::make_counter_clockwise() { bool was_cw = this->polygon().is_clockwise(); if (was_cw) this->reverse(); return was_cw; } void ExtrusionLoop::reverse() { for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) path->reverse(); std::reverse(this->paths.begin(), this->paths.end()); } Polygon ExtrusionLoop::polygon() const { Polygon polygon; for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { // for each polyline, append all points except the last one (because it coincides with the first one of the next polyline) polygon.points.insert(polygon.points.end(), path->polyline.points.begin(), path->polyline.points.end()-1); } return polygon; } double ExtrusionLoop::length() const { double len = 0; for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) len += path->polyline.length(); return len; } bool ExtrusionLoop::split_at_vertex(const Point &point) { for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) { int idx = path->polyline.find_point(point); if (idx != -1) { if (this->paths.size() == 1) { // just change the order of points path->polyline.points.insert(path->polyline.points.end(), path->polyline.points.begin() + 1, path->polyline.points.begin() + idx + 1); path->polyline.points.erase(path->polyline.points.begin(), path->polyline.points.begin() + idx); } else { // new paths list starts with the second half of current path ExtrusionPaths new_paths; new_paths.reserve(this->paths.size() + 1); { ExtrusionPath p = *path; p.polyline.points.erase(p.polyline.points.begin(), p.polyline.points.begin() + idx); if (p.polyline.is_valid()) new_paths.push_back(p); } // then we add all paths until the end of current path list new_paths.insert(new_paths.end(), path+1, this->paths.end()); // not including this path // then we add all paths since the beginning of current list up to the previous one new_paths.insert(new_paths.end(), this->paths.begin(), path); // not including this path // finally we add the first half of current path { ExtrusionPath p = *path; p.polyline.points.erase(p.polyline.points.begin() + idx + 1, p.polyline.points.end()); if (p.polyline.is_valid()) new_paths.push_back(p); } // we can now override the old path list with the new one and stop looping std::swap(this->paths, new_paths); } return true; } } return false; } // Splitting an extrusion loop, possibly made of multiple segments, some of the segments may be bridging. void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang) { if (this->paths.empty()) return; // Find the closest path and closest point belonging to that path. Avoid overhangs, if asked for. size_t path_idx = 0; Point p; { double min = std::numeric_limits::max(); Point p_non_overhang; size_t path_idx_non_overhang = 0; double min_non_overhang = std::numeric_limits::max(); for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { Point p_tmp = point.projection_onto(path->polyline); double dist = point.distance_to(p_tmp); if (dist < min) { p = p_tmp; min = dist; path_idx = path - this->paths.begin(); } if (prefer_non_overhang && ! is_bridge(path->role()) && dist < min_non_overhang) { p_non_overhang = p_tmp; min_non_overhang = dist; path_idx_non_overhang = path - this->paths.begin(); } } if (prefer_non_overhang && min_non_overhang != std::numeric_limits::max()) { // Only apply the non-overhang point if there is one. path_idx = path_idx_non_overhang; p = p_non_overhang; } } // now split path_idx in two parts const ExtrusionPath &path = this->paths[path_idx]; ExtrusionPath p1(path.role(), path.mm3_per_mm, path.width, path.height); ExtrusionPath p2(path.role(), path.mm3_per_mm, path.width, path.height); path.polyline.split_at(p, &p1.polyline, &p2.polyline); if (this->paths.size() == 1) { if (! p1.polyline.is_valid()) std::swap(this->paths.front().polyline.points, p2.polyline.points); else if (! p2.polyline.is_valid()) std::swap(this->paths.front().polyline.points, p1.polyline.points); else { p2.polyline.points.insert(p2.polyline.points.end(), p1.polyline.points.begin() + 1, p1.polyline.points.end()); std::swap(this->paths.front().polyline.points, p2.polyline.points); } } else { // install the two paths this->paths.erase(this->paths.begin() + path_idx); if (p2.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p2); if (p1.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p1); } // split at the new vertex this->split_at_vertex(p); } void ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const { *paths = this->paths; while (distance > 0 && !paths->empty()) { ExtrusionPath &last = paths->back(); double len = last.length(); if (len <= distance) { paths->pop_back(); distance -= len; } else { last.polyline.clip_end(distance); break; } } } bool ExtrusionLoop::has_overhang_point(const Point &point) const { for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { int pos = path->polyline.find_point(point); if (pos != -1) { // point belongs to this path // we consider it overhang only if it's not an endpoint return (is_bridge(path->role()) && pos > 0 && pos != (int)(path->polyline.points.size())-1); } } return false; } void ExtrusionLoop::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) path->polygons_covered_by_width(out, scaled_epsilon); } void ExtrusionLoop::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const { for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) path->polygons_covered_by_spacing(out, scaled_epsilon); } double ExtrusionLoop::min_mm3_per_mm() const { double min_mm3_per_mm = std::numeric_limits::max(); for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) min_mm3_per_mm = std::min(min_mm3_per_mm, path->mm3_per_mm); return min_mm3_per_mm; } } Slic3r-version_1.39.1/xs/src/libslic3r/ExtrusionEntity.hpp000066400000000000000000000357151324354444700235530ustar00rootroot00000000000000#ifndef slic3r_ExtrusionEntity_hpp_ #define slic3r_ExtrusionEntity_hpp_ #include "libslic3r.h" #include "Polygon.hpp" #include "Polyline.hpp" namespace Slic3r { class ExPolygonCollection; class ExtrusionEntityCollection; class Extruder; /* Each ExtrusionRole value identifies a distinct set of { extruder, speed } */ enum ExtrusionRole { erNone, erPerimeter, erExternalPerimeter, erOverhangPerimeter, erInternalInfill, erSolidInfill, erTopSolidInfill, erBridgeInfill, erGapFill, erSkirt, erSupportMaterial, erSupportMaterialInterface, // Extrusion role for a collection with multiple extrusion roles. erMixed, }; inline bool is_perimeter(ExtrusionRole role) { return role == erPerimeter || role == erExternalPerimeter || role == erOverhangPerimeter; } inline bool is_infill(ExtrusionRole role) { return role == erBridgeInfill || role == erInternalInfill || role == erSolidInfill || role == erTopSolidInfill; } inline bool is_solid_infill(ExtrusionRole role) { return role == erBridgeInfill || role == erSolidInfill || role == erTopSolidInfill; } inline bool is_bridge(ExtrusionRole role) { return role == erBridgeInfill || role == erOverhangPerimeter; } /* Special flags describing loop */ enum ExtrusionLoopRole { elrDefault, elrContourInternalPerimeter, elrSkirt, }; class ExtrusionEntity { public: virtual ExtrusionRole role() const = 0; virtual bool is_collection() const { return false; } virtual bool is_loop() const { return false; } virtual bool can_reverse() const { return true; } virtual ExtrusionEntity* clone() const = 0; virtual ~ExtrusionEntity() {}; virtual void reverse() = 0; virtual Point first_point() const = 0; virtual Point last_point() const = 0; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill. virtual void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const = 0; Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. virtual double min_mm3_per_mm() const = 0; virtual Polyline as_polyline() const = 0; virtual double length() const = 0; }; typedef std::vector ExtrusionEntitiesPtr; class ExtrusionPath : public ExtrusionEntity { public: Polyline polyline; // Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator. double mm3_per_mm; // Width of the extrusion, used for visualization purposes. float width; // Height of the extrusion, used for visualization purposed. float height; ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {}; ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {}; ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} // ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height) {}; ExtrusionPath& operator=(const ExtrusionPath &rhs) { this->m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = rhs.polyline; return *this; } ExtrusionPath& operator=(ExtrusionPath &&rhs) { this->m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = std::move(rhs.polyline); return *this; } ExtrusionPath* clone() const { return new ExtrusionPath (*this); } void reverse() { this->polyline.reverse(); } Point first_point() const { return this->polyline.points.front(); } Point last_point() const { return this->polyline.points.back(); } // Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection. // Currently not used. void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; // Produce a list of extrusion paths into retval by removing parts of this path by ExPolygonCollection. // Currently not used. void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const; void clip_end(double distance); void simplify(double tolerance); virtual double length() const; virtual ExtrusionRole role() const { return m_role; } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill. void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const; Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const { return this->mm3_per_mm; } Polyline as_polyline() const { return this->polyline; } private: void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; ExtrusionRole m_role; }; typedef std::vector ExtrusionPaths; // Single continuous extrusion path, possibly with varying extrusion thickness, extrusion height or bridging / non bridging. class ExtrusionMultiPath : public ExtrusionEntity { public: ExtrusionPaths paths; ExtrusionMultiPath() {}; ExtrusionMultiPath(const ExtrusionMultiPath &rhs) : paths(rhs.paths) {} ExtrusionMultiPath(ExtrusionMultiPath &&rhs) : paths(std::move(rhs.paths)) {} ExtrusionMultiPath(const ExtrusionPaths &paths) : paths(paths) {}; ExtrusionMultiPath(const ExtrusionPath &path) { this->paths.push_back(path); } ExtrusionMultiPath& operator=(const ExtrusionMultiPath &rhs) { this->paths = rhs.paths; return *this; } ExtrusionMultiPath& operator=(ExtrusionMultiPath &&rhs) { this->paths = std::move(rhs.paths); return *this; } bool is_loop() const { return false; } bool can_reverse() const { return true; } ExtrusionMultiPath* clone() const { return new ExtrusionMultiPath(*this); } void reverse(); Point first_point() const { return this->paths.front().polyline.points.front(); } Point last_point() const { return this->paths.back().polyline.points.back(); } virtual double length() const; virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill. void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const; Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const; Polyline as_polyline() const; }; // Single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height or bridging / non bridging. class ExtrusionLoop : public ExtrusionEntity { public: ExtrusionPaths paths; ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {}; ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {}; ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {}; ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role) { this->paths.push_back(path); }; ExtrusionLoop(const ExtrusionPath &&path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role) { this->paths.emplace_back(std::move(path)); }; bool is_loop() const { return true; } bool can_reverse() const { return false; } ExtrusionLoop* clone() const { return new ExtrusionLoop (*this); } bool make_clockwise(); bool make_counter_clockwise(); void reverse(); Point first_point() const { return this->paths.front().polyline.points.front(); } Point last_point() const { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } Polygon polygon() const; virtual double length() const; bool split_at_vertex(const Point &point); void split_at(const Point &point, bool prefer_non_overhang); void clip_end(double distance, ExtrusionPaths* paths) const; // Test, whether the point is extruded by a bridging flow. // This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead. bool has_overhang_point(const Point &point) const; virtual ExtrusionRole role() const { return this->paths.empty() ? erNone : this->paths.front().role(); } ExtrusionLoopRole loop_role() const { return m_loop_role; } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill. void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const; Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const; Polyline as_polyline() const { return this->polygon().split_at_first_point(); } private: ExtrusionLoopRole m_loop_role; }; inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) { dst.reserve(dst.size() + polylines.size()); for (Polyline &polyline : polylines) if (polyline.is_valid()) { dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height)); dst.back().polyline = polyline; } } inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) { dst.reserve(dst.size() + polylines.size()); for (Polyline &polyline : polylines) if (polyline.is_valid()) { dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height)); dst.back().polyline = std::move(polyline); } polylines.clear(); } inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) { dst.reserve(dst.size() + polylines.size()); for (Polyline &polyline : polylines) if (polyline.is_valid()) { ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height); dst.push_back(extrusion_path); extrusion_path->polyline = polyline; } } inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) { dst.reserve(dst.size() + polylines.size()); for (Polyline &polyline : polylines) if (polyline.is_valid()) { ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height); dst.push_back(extrusion_path); extrusion_path->polyline = std::move(polyline); } polylines.clear(); } inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, ExtrusionRole role, double mm3_per_mm, float width, float height) { dst.reserve(dst.size() + loops.size()); for (Polygon &poly : loops) { if (poly.is_valid()) { ExtrusionPath path(role, mm3_per_mm, width, height); path.polyline.points = std::move(poly.points); path.polyline.points.push_back(path.polyline.points.front()); dst.emplace_back(new ExtrusionLoop(std::move(path))); } } loops.clear(); } } #endif Slic3r-version_1.39.1/xs/src/libslic3r/ExtrusionEntityCollection.cpp000066400000000000000000000166261324354444700255620ustar00rootroot00000000000000#include "ExtrusionEntityCollection.hpp" #include #include #include namespace Slic3r { ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths) : no_sort(false) { this->append(paths); } ExtrusionEntityCollection& ExtrusionEntityCollection::operator= (const ExtrusionEntityCollection &other) { this->entities = other.entities; for (size_t i = 0; i < this->entities.size(); ++i) this->entities[i] = this->entities[i]->clone(); this->orig_indices = other.orig_indices; this->no_sort = other.no_sort; return *this; } void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c) { std::swap(this->entities, c.entities); std::swap(this->orig_indices, c.orig_indices); std::swap(this->no_sort, c.no_sort); } void ExtrusionEntityCollection::clear() { for (size_t i = 0; i < this->entities.size(); ++i) delete this->entities[i]; this->entities.clear(); } ExtrusionEntityCollection::operator ExtrusionPaths() const { ExtrusionPaths paths; for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { if (const ExtrusionPath* path = dynamic_cast(*it)) paths.push_back(*path); } return paths; } ExtrusionEntityCollection* ExtrusionEntityCollection::clone() const { ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(*this); for (size_t i = 0; i < coll->entities.size(); ++i) coll->entities[i] = this->entities[i]->clone(); return coll; } void ExtrusionEntityCollection::reverse() { for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it) { // Don't reverse it if it's a loop, as it doesn't change anything in terms of elements ordering // and caller might rely on winding order if (!(*it)->is_loop()) (*it)->reverse(); } std::reverse(this->entities.begin(), this->entities.end()); } void ExtrusionEntityCollection::replace(size_t i, const ExtrusionEntity &entity) { delete this->entities[i]; this->entities[i] = entity.clone(); } void ExtrusionEntityCollection::remove(size_t i) { delete this->entities[i]; this->entities.erase(this->entities.begin() + i); } ExtrusionEntityCollection ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const { ExtrusionEntityCollection coll; this->chained_path(&coll, no_reverse, role); return coll; } void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector* orig_indices) const { if (this->entities.empty()) return; this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, role, orig_indices); } ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(Point start_near, bool no_reverse, ExtrusionRole role) const { ExtrusionEntityCollection coll; this->chained_path_from(start_near, &coll, no_reverse, role); return coll; } void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector* orig_indices) const { if (this->no_sort) { *retval = *this; return; } retval->entities.reserve(this->entities.size()); retval->orig_indices.reserve(this->entities.size()); // if we're asked to return the original indices, build a map std::map indices_map; ExtrusionEntitiesPtr my_paths; for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { if (role != erMixed) { // The caller wants only paths with a specific extrusion role. auto role2 = (*it)->role(); if (role != role2) { // This extrusion entity does not match the role asked. assert(role2 != erMixed); continue; } } ExtrusionEntity* entity = (*it)->clone(); my_paths.push_back(entity); if (orig_indices != NULL) indices_map[entity] = it - this->entities.begin(); } Points endpoints; for (ExtrusionEntitiesPtr::iterator it = my_paths.begin(); it != my_paths.end(); ++it) { endpoints.push_back((*it)->first_point()); if (no_reverse || !(*it)->can_reverse()) { endpoints.push_back((*it)->first_point()); } else { endpoints.push_back((*it)->last_point()); } } while (!my_paths.empty()) { // find nearest point int start_index = start_near.nearest_point_index(endpoints); int path_index = start_index/2; ExtrusionEntity* entity = my_paths.at(path_index); // never reverse loops, since it's pointless for chained path and callers might depend on orientation if (start_index % 2 && !no_reverse && entity->can_reverse()) { entity->reverse(); } retval->entities.push_back(my_paths.at(path_index)); if (orig_indices != NULL) orig_indices->push_back(indices_map[entity]); my_paths.erase(my_paths.begin() + path_index); endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2); start_near = retval->entities.back()->last_point(); } } void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) (*it)->polygons_covered_by_width(out, scaled_epsilon); } void ExtrusionEntityCollection::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const { for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) (*it)->polygons_covered_by_spacing(out, scaled_epsilon); } /* Recursively count paths and loops contained in this collection */ size_t ExtrusionEntityCollection::items_count() const { size_t count = 0; for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { if ((*it)->is_collection()) { ExtrusionEntityCollection* collection = dynamic_cast(*it); count += collection->items_count(); } else { ++count; } } return count; } /* Returns a single vector of pointers to all non-collection items contained in this one */ void ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const { for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { if ((*it)->is_collection()) { ExtrusionEntityCollection* collection = dynamic_cast(*it); retval->append(collection->flatten().entities); } else { retval->append(**it); } } } ExtrusionEntityCollection ExtrusionEntityCollection::flatten() const { ExtrusionEntityCollection coll; this->flatten(&coll); return coll; } double ExtrusionEntityCollection::min_mm3_per_mm() const { double min_mm3_per_mm = std::numeric_limits::max(); for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) min_mm3_per_mm = std::min(min_mm3_per_mm, (*it)->min_mm3_per_mm()); return min_mm3_per_mm; } } Slic3r-version_1.39.1/xs/src/libslic3r/ExtrusionEntityCollection.hpp000066400000000000000000000120331324354444700255530ustar00rootroot00000000000000#ifndef slic3r_ExtrusionEntityCollection_hpp_ #define slic3r_ExtrusionEntityCollection_hpp_ #include "libslic3r.h" #include "ExtrusionEntity.hpp" namespace Slic3r { class ExtrusionEntityCollection : public ExtrusionEntity { public: ExtrusionEntityCollection* clone() const; ExtrusionEntitiesPtr entities; // we own these entities std::vector orig_indices; // handy for XS bool no_sort; ExtrusionEntityCollection(): no_sort(false) {}; ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : orig_indices(other.orig_indices), no_sort(other.no_sort) { this->append(other.entities); } ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), orig_indices(std::move(other.orig_indices)), no_sort(other.no_sort) {} explicit ExtrusionEntityCollection(const ExtrusionPaths &paths); ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other); ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other) { this->entities = std::move(other.entities); this->orig_indices = std::move(other.orig_indices); this->no_sort = other.no_sort; return *this; } ~ExtrusionEntityCollection() { clear(); } explicit operator ExtrusionPaths() const; bool is_collection() const { return true; }; virtual ExtrusionRole role() const { ExtrusionRole out = erNone; for (const ExtrusionEntity *ee : entities) { ExtrusionRole er = ee->role(); out = (out == erNone || out == er) ? er : erMixed; } return out; } bool can_reverse() const { return !this->no_sort; }; bool empty() const { return this->entities.empty(); }; void clear(); void swap (ExtrusionEntityCollection &c); void append(const ExtrusionEntity &entity) { this->entities.push_back(entity.clone()); } void append(const ExtrusionEntitiesPtr &entities) { this->entities.reserve(this->entities.size() + entities.size()); for (ExtrusionEntitiesPtr::const_iterator ptr = entities.begin(); ptr != entities.end(); ++ptr) this->entities.push_back((*ptr)->clone()); } void append(ExtrusionEntitiesPtr &&src) { if (entities.empty()) entities = std::move(src); else { std::move(std::begin(src), std::end(src), std::back_inserter(entities)); src.clear(); } } void append(const ExtrusionPaths &paths) { this->entities.reserve(this->entities.size() + paths.size()); for (ExtrusionPaths::const_iterator path = paths.begin(); path != paths.end(); ++path) this->entities.push_back(path->clone()); } void replace(size_t i, const ExtrusionEntity &entity); void remove(size_t i); ExtrusionEntityCollection chained_path(bool no_reverse = false, ExtrusionRole role = erMixed) const; void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector* orig_indices = nullptr) const; ExtrusionEntityCollection chained_path_from(Point start_near, bool no_reverse = false, ExtrusionRole role = erMixed) const; void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector* orig_indices = nullptr) const; void reverse(); Point first_point() const { return this->entities.front()->first_point(); } Point last_point() const { return this->entities.back()->last_point(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill. virtual void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const; Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } size_t items_count() const; void flatten(ExtrusionEntityCollection* retval) const; ExtrusionEntityCollection flatten() const; double min_mm3_per_mm() const; // Following methods shall never be called on an ExtrusionEntityCollection. Polyline as_polyline() const { CONFESS("Calling as_polyline() on a ExtrusionEntityCollection"); return Polyline(); }; virtual double length() const { CONFESS("Calling length() on a ExtrusionEntityCollection"); return 0.; } }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/ExtrusionSimulator.cpp000066400000000000000000000775471324354444700242620ustar00rootroot00000000000000// Optimize the extrusion simulator to the bones. //#pragma GCC optimize ("O3") //#undef SLIC3R_DEBUG //#define NDEBUG #include #include #include #include #include #include #include #include "libslic3r.h" #include "ExtrusionSimulator.hpp" #ifndef M_PI #define M_PI 3.1415926535897932384626433832795 #endif namespace Slic3r { // Replacement for a template alias. // Shorthand for the point_xy. template struct V2 { typedef boost::geometry::model::d2::point_xy Type; }; // Replacement for a template alias. // Shorthand for the point with a cartesian coordinate system. template struct V3 { typedef boost::geometry::model::point Type; }; // Replacement for a template alias. // Shorthand for the point with a cartesian coordinate system. template struct V4 { typedef boost::geometry::model::point Type; }; typedef V2::Type V2i; typedef V2::Type V2f; typedef V2::Type V2d; // Used for an RGB color. typedef V3::Type V3uc; // Used for an RGBA color. typedef V4::Type V4uc; typedef boost::geometry::model::box B2i; typedef boost::geometry::model::box B2f; typedef boost::geometry::model::box B2d; typedef boost::multi_array A2uc; typedef boost::multi_array A2i; typedef boost::multi_array A2f; typedef boost::multi_array A2d; template inline void operator+=( boost::geometry::model::d2::point_xy &v1, const boost::geometry::model::d2::point_xy &v2) { boost::geometry::add_point(v1, v2); } template inline void operator-=( boost::geometry::model::d2::point_xy &v1, const boost::geometry::model::d2::point_xy &v2) { boost::geometry::subtract_point(v1, v2); } template inline void operator*=(boost::geometry::model::d2::point_xy &v, const T c) { boost::geometry::multiply_value(v, c); } template inline void operator/=(boost::geometry::model::d2::point_xy &v, const T c) { boost::geometry::divide_value(v, c); } template inline typename boost::geometry::model::d2::point_xy operator+( const boost::geometry::model::d2::point_xy &v1, const boost::geometry::model::d2::point_xy &v2) { boost::geometry::model::d2::point_xy out(v1); out += v2; return out; } template inline boost::geometry::model::d2::point_xy operator-( const boost::geometry::model::d2::point_xy &v1, const boost::geometry::model::d2::point_xy &v2) { boost::geometry::model::d2::point_xy out(v1); out -= v2; return out; } template inline boost::geometry::model::d2::point_xy operator*( const boost::geometry::model::d2::point_xy &v, const T c) { boost::geometry::model::d2::point_xy out(v); out *= c; return out; } template inline typename boost::geometry::model::d2::point_xy operator*( const T c, const boost::geometry::model::d2::point_xy &v) { boost::geometry::model::d2::point_xy out(v); out *= c; return out; } template inline typename boost::geometry::model::d2::point_xy operator/( const boost::geometry::model::d2::point_xy &v, const T c) { boost::geometry::model::d2::point_xy out(v); out /= c; return out; } template inline T dot( const boost::geometry::model::d2::point_xy &v1, const boost::geometry::model::d2::point_xy &v2) { return boost::geometry::dot_product(v1, v2); } template inline T dot(const boost::geometry::model::d2::point_xy &v) { return boost::geometry::dot_product(v, v); } template inline T cross( const boost::geometry::model::d2::point_xy &v1, const boost::geometry::model::d2::point_xy &v2) { return v1.x() * v2.y() - v2.x() * v1.y(); } // Euclidian measure template inline T l2(const boost::geometry::model::d2::point_xy &v) { return std::sqrt(dot(v)); } // Euclidian measure template inline T mag(const boost::geometry::model::d2::point_xy &v) { return l2(v); } template inline T dist2_to_line( const boost::geometry::model::d2::point_xy &p0, const boost::geometry::model::d2::point_xy &p1, const boost::geometry::model::d2::point_xy &px) { boost::geometry::model::d2::point_xy v = p1 - p0; boost::geometry::model::d2::point_xy vx = px - p0; T l = dot(v); T t = dot(v, vx); if (l != T(0) && t > T(0.)) { t /= l; vx = px - ((t > T(1.)) ? p1 : (p0 + t * v)); } return dot(vx); } // Intersect a circle with a line segment. // Returns number of intersection points. template int line_circle_intersection( const boost::geometry::model::d2::point_xy &p0, const boost::geometry::model::d2::point_xy &p1, const boost::geometry::model::d2::point_xy ¢er, const T radius, boost::geometry::model::d2::point_xy intersection[2]) { typedef typename V2::Type V2T; V2T v = p1 - p0; V2T vc = p0 - center; T a = dot(v); T b = T(2.) * dot(vc, v); T c = dot(vc) - radius * radius; T d = b * b - T(4.) * a * c; if (d < T(0)) // The circle misses the ray. return 0; int n = 0; if (d == T(0)) { // The circle touches the ray at a single tangent point. T t = - b / (T(2.) * a); if (t >= T(0.) && t <= T(1.)) intersection[n ++] = p0 + t * v; } else { // The circle intersects the ray in two points. d = sqrt(d); T t = (- b - d) / (T(2.) * a); if (t >= T(0.) && t <= T(1.)) intersection[n ++] = p0 + t * v; t = (- b + d) / (T(2.) * a); if (t >= T(0.) && t <= T(1.)) intersection[n ++] = p0 + t * v; } return n; } // Sutherland–Hodgman clipping of a rectangle against an AABB. // Expects the first 4 points of rect to be filled at the beginning. // The clipping may produce up to 8 points. // Returns the number of resulting points. template int clip_rect_by_AABB( boost::geometry::model::d2::point_xy rect[8], const boost::geometry::model::box > &aabb) { typedef typename V2::Type V2T; V2T result[8]; int nin = 4; int nout = 0; V2T *in = rect; V2T *out = result; // Clip left { const V2T *S = in + nin - 1; T left = aabb.min_corner().x(); for (int i = 0; i < nin; ++i) { const V2T &E = in[i]; if (E.x() == left) { out[nout++] = E; } else if (E.x() > left) { // E is inside the AABB. if (S->x() < left) { // S is outside the AABB. Calculate an intersection point. T t = (left - S->x()) / (E.x() - S->x()); out[nout++] = V2T(left, S->y() + t * (E.y() - S->y())); } out[nout++] = E; } else if (S->x() > left) { // S is inside the AABB, E is outside the AABB. T t = (left - S->x()) / (E.x() - S->x()); out[nout++] = V2T(left, S->y() + t * (E.y() - S->y())); } S = &E; } assert(nout <= 8); } // Clip bottom { std::swap(in, out); nin = nout; nout = 0; const V2T *S = in + nin - 1; T bottom = aabb.min_corner().y(); for (int i = 0; i < nin; ++i) { const V2T &E = in[i]; if (E.y() == bottom) { out[nout++] = E; } else if (E.y() > bottom) { // E is inside the AABB. if (S->y() < bottom) { // S is outside the AABB. Calculate an intersection point. T t = (bottom - S->y()) / (E.y() - S->y()); out[nout++] = V2T(S->x() + t * (E.x() - S->x()), bottom); } out[nout++] = E; } else if (S->y() > bottom) { // S is inside the AABB, E is outside the AABB. T t = (bottom - S->y()) / (E.y() - S->y()); out[nout++] = V2T(S->x() + t * (E.x() - S->x()), bottom); } S = &E; } assert(nout <= 8); } // Clip right { std::swap(in, out); nin = nout; nout = 0; const V2T *S = in + nin - 1; T right = aabb.max_corner().x(); for (int i = 0; i < nin; ++i) { const V2T &E = in[i]; if (E.x() == right) { out[nout++] = E; } else if (E.x() < right) { // E is inside the AABB. if (S->x() > right) { // S is outside the AABB. Calculate an intersection point. T t = (right - S->x()) / (E.x() - S->x()); out[nout++] = V2T(right, S->y() + t * (E.y() - S->y())); } out[nout++] = E; } else if (S->x() < right) { // S is inside the AABB, E is outside the AABB. T t = (right - S->x()) / (E.x() - S->x()); out[nout++] = V2T(right, S->y() + t * (E.y() - S->y())); } S = &E; } assert(nout <= 8); } // Clip top { std::swap(in, out); nin = nout; nout = 0; const V2T *S = in + nin - 1; T top = aabb.max_corner().y(); for (int i = 0; i < nin; ++i) { const V2T &E = in[i]; if (E.y() == top) { out[nout++] = E; } else if (E.y() < top) { // E is inside the AABB. if (S->y() > top) { // S is outside the AABB. Calculate an intersection point. T t = (top - S->y()) / (E.y() - S->y()); out[nout++] = V2T(S->x() + t * (E.x() - S->x()), top); } out[nout++] = E; } else if (S->y() < top) { // S is inside the AABB, E is outside the AABB. T t = (top - S->y()) / (E.y() - S->y()); out[nout++] = V2T(S->x() + t * (E.x() - S->x()), top); } S = &E; } assert(nout <= 8); } assert(nout <= 8); return nout; } // Calculate area of the circle x AABB intersection. // The calculation is approximate in a way, that the circular segment // intersecting the cell is approximated by its chord (a linear segment). template int clip_circle_by_AABB( const boost::geometry::model::d2::point_xy ¢er, const T radius, const boost::geometry::model::box > &aabb, boost::geometry::model::d2::point_xy result[8], bool result_arc[8]) { typedef typename V2::Type V2T; V2T rect[4] = { aabb.min_corner(), V2T(aabb.max_corner().x(), aabb.min_corner().y()), aabb.max_corner(), V2T(aabb.min_corner().x(), aabb.max_corner().y()) }; int bits_corners = 0; T r2 = sqr(radius); for (int i = 0; i < 4; ++ i, bits_corners <<= 1) bits_corners |= dot(rect[i] - center) >= r2; bits_corners >>= 1; if (bits_corners == 0) { // all inside memcpy(result, rect, sizeof(rect)); memset(result_arc, true, 4); return 4; } if (bits_corners == 0x0f) // all outside return 0; // Some corners are outside, some are inside. Trim the rectangle. int n = 0; for (int i = 0; i < 4; ++ i) { bool inside = (bits_corners & 0x08) == 0; bits_corners <<= 1; V2T chordal_points[2]; int n_chordal_points = line_circle_intersection(rect[i], rect[(i + 1)%4], center, radius, chordal_points); if (n_chordal_points == 2) { result_arc[n] = true; result[n ++] = chordal_points[0]; result_arc[n] = true; result[n ++] = chordal_points[1]; } else { if (inside) { result_arc[n] = false; result[n ++] = rect[i]; } if (n_chordal_points == 1) { result_arc[n] = false; result[n ++] = chordal_points[0]; } } } return n; } /* // Calculate area of the circle x AABB intersection. // The calculation is approximate in a way, that the circular segment // intersecting the cell is approximated by its chord (a linear segment). template T circle_AABB_intersection_area( const boost::geometry::model::d2::point_xy ¢er, const T radius, const boost::geometry::model::box > &aabb) { typedef typename V2::Type V2T; typedef typename boost::geometry::model::box B2T; T radius2 = radius * radius; bool intersectionLeft = sqr(aabb.min_corner().x() - center.x()) < radius2; bool intersectionRight = sqr(aabb.max_corner().x() - center.x()) < radius2; bool intersectionBottom = sqr(aabb.min_corner().y() - center.y()) < radius2; bool intersectionTop = sqr(aabb.max_corner().y() - center.y()) < radius2; if (! (intersectionLeft || intersectionRight || intersectionTop || intersectionBottom)) // No intersection between the aabb and the center. return boost::geometry::point_in_box()::apply(center, aabb) ? 1.f : 0.f; V2T rect[4] = { aabb.min_corner(), V2T(aabb.max_corner().x(), aabb.min_corner().y()), aabb.max_corner(), V2T(aabb.min_corner().x(), aabb.max_corner().y()) }; int bits_corners = 0; T r2 = sqr(radius); for (int i = 0; i < 4; ++ i, bits_corners <<= 1) bits_corners |= dot(rect[i] - center) >= r2; bits_corners >>= 1; if (bits_corners == 0) { // all inside memcpy(result, rect, sizeof(rect)); memset(result_arc, true, 4); return 4; } if (bits_corners == 0x0f) // all outside return 0; // Some corners are outside, some are inside. Trim the rectangle. int n = 0; for (int i = 0; i < 4; ++ i) { bool inside = (bits_corners & 0x08) == 0; bits_corners <<= 1; V2T chordal_points[2]; int n_chordal_points = line_circle_intersection(rect[i], rect[(i + 1)%4], center, radius, chordal_points); if (n_chordal_points == 2) { result_arc[n] = true; result[n ++] = chordal_points[0]; result_arc[n] = true; result[n ++] = chordal_points[1]; } else { if (inside) { result_arc[n] = false; result[n ++] = rect[i]; } if (n_chordal_points == 1) { result_arc[n] = false; result[n ++] = chordal_points[0]; } } } return n; } */ template inline T polyArea(const boost::geometry::model::d2::point_xy *poly, int n) { T area = T(0); for (int i = 1; i + 1 < n; ++i) area += cross(poly[i] - poly[0], poly[i + 1] - poly[0]); return T(0.5) * area; } template boost::geometry::model::d2::point_xy polyCentroid(const boost::geometry::model::d2::point_xy *poly, int n) { boost::geometry::model::d2::point_xy centroid(T(0), T(0)); for (int i = 0; i < n; ++i) centroid += poly[i]; return (n == 0) ? centroid : (centroid / float(n)); } void gcode_paint_layer( const std::vector &polyline, float width, float thickness, A2f &acc) { int nc = acc.shape()[1]; int nr = acc.shape()[0]; // printf("gcode_paint_layer %d,%d\n", nc, nr); for (size_t iLine = 1; iLine != polyline.size(); ++iLine) { const V2f &p1 = polyline[iLine - 1]; const V2f &p2 = polyline[iLine]; // printf("p1, p2: %f,%f %f,%f\n", p1.x(), p1.y(), p2.x(), p2.y()); const V2f dir = p2 - p1; V2f vperp(- dir.y(), dir.x()); vperp = vperp * 0.5f * width / l2(vperp); // Rectangle of the extrusion. V2f rect[4] = { p1 + vperp, p1 - vperp, p2 - vperp, p2 + vperp }; // Bounding box of the extrusion. B2f bboxLine(rect[0], rect[0]); boost::geometry::expand(bboxLine, rect[1]); boost::geometry::expand(bboxLine, rect[2]); boost::geometry::expand(bboxLine, rect[3]); B2i bboxLinei( V2i(clamp(0, nc-1, int(floor(bboxLine.min_corner().x()))), clamp(0, nr-1, int(floor(bboxLine.min_corner().y())))), V2i(clamp(0, nc-1, int(ceil (bboxLine.max_corner().x()))), clamp(0, nr-1, int(ceil (bboxLine.max_corner().y()))))); // printf("bboxLinei %d,%d %d,%d\n", bboxLinei.min_corner().x(), bboxLinei.min_corner().y(), bboxLinei.max_corner().x(), bboxLinei.max_corner().y()); #ifdef _DEBUG float area = polyArea(rect, 4); assert(area > 0.f); #endif /* _DEBUG */ for (int j = bboxLinei.min_corner().y(); j + 1 < bboxLinei.max_corner().y(); ++ j) { for (int i = bboxLinei.min_corner().x(); i + 1 < bboxLinei.max_corner().x(); ++i) { V2f rect2[8]; memcpy(rect2, rect, sizeof(rect)); int n = clip_rect_by_AABB(rect2, B2f(V2f(float(i), float(j)), V2f(float(i + 1), float(j + 1)))); float area = polyArea(rect2, n); assert(area >= 0.f && area <= 1.000001f); acc[j][i] += area * thickness; } } } } void gcode_paint_bitmap( const std::vector &polyline, float width, A2uc &bitmap, float scale) { int nc = bitmap.shape()[1]; int nr = bitmap.shape()[0]; float r2 = width * width * 0.25f; // printf("gcode_paint_layer %d,%d\n", nc, nr); for (size_t iLine = 1; iLine != polyline.size(); ++iLine) { const V2f &p1 = polyline[iLine - 1]; const V2f &p2 = polyline[iLine]; // printf("p1, p2: %f,%f %f,%f\n", p1.x(), p1.y(), p2.x(), p2.y()); V2f dir = p2 - p1; dir = dir * 0.5f * width / l2(dir); V2f vperp(- dir.y(), dir.x()); // Rectangle of the extrusion. V2f rect[4] = { (p1 + vperp - dir) * scale, (p1 - vperp - dir) * scale, (p2 - vperp + dir) * scale, (p2 + vperp + dir) * scale }; // Bounding box of the extrusion. B2f bboxLine(rect[0], rect[0]); boost::geometry::expand(bboxLine, rect[1]); boost::geometry::expand(bboxLine, rect[2]); boost::geometry::expand(bboxLine, rect[3]); B2i bboxLinei( V2i(clamp(0, nc-1, int(floor(bboxLine.min_corner().x()))), clamp(0, nr-1, int(floor(bboxLine.min_corner().y())))), V2i(clamp(0, nc-1, int(ceil (bboxLine.max_corner().x()))), clamp(0, nr-1, int(ceil (bboxLine.max_corner().y()))))); // printf("bboxLinei %d,%d %d,%d\n", bboxLinei.min_corner().x(), bboxLinei.min_corner().y(), bboxLinei.max_corner().x(), bboxLinei.max_corner().y()); for (int j = bboxLinei.min_corner().y(); j + 1 < bboxLinei.max_corner().y(); ++ j) { for (int i = bboxLinei.min_corner().x(); i + 1 < bboxLinei.max_corner().x(); ++i) { float d2 = dist2_to_line(p1, p2, V2f(float(i) + 0.5f, float(j) + 0.5f) / scale); if (d2 < r2) bitmap[j][i] = 1; } } } } struct Cell { // Cell index in the grid. V2i idx; // Total volume of the material stored in this cell. float volume; // Area covered inside this cell, <0,1>. float area; // Fraction of the area covered by the print head. <0,1> float fraction_covered; // Height of the covered part in excess to the expected layer height. float excess_height; bool operator<(const Cell &c2) const { return this->excess_height < c2.excess_height; } }; struct ExtrusionPoint { V2f center; float radius; float height; }; typedef std::vector ExtrusionPoints; void gcode_spread_points( A2f &acc, const A2f &mask, const ExtrusionPoints &points, ExtrusionSimulationType simulationType) { int nc = acc.shape()[1]; int nr = acc.shape()[0]; // Maximum radius of the spreading points, to allocate a large enough cell array. float rmax = 0.f; for (ExtrusionPoints::const_iterator it = points.begin(); it != points.end(); ++ it) rmax = std::max(rmax, it->radius); size_t n_rows_max = size_t(ceil(rmax * 2.f + 2.f)); size_t n_cells_max = sqr(n_rows_max); std::vector > spans; std::vector cells(n_cells_max, Cell()); std::vector areas_sum(n_cells_max, 0.f); for (ExtrusionPoints::const_iterator it = points.begin(); it != points.end(); ++ it) { const V2f ¢er = it->center; const float radius = it->radius; const float radius2 = radius * radius; const float height_target = it->height; B2f bbox(center - V2f(radius, radius), center + V2f(radius, radius)); B2i bboxi( V2i(clamp(0, nc-1, int(floor(bbox.min_corner().x()))), clamp(0, nr-1, int(floor(bbox.min_corner().y())))), V2i(clamp(0, nc-1, int(ceil (bbox.max_corner().x()))), clamp(0, nr-1, int(ceil (bbox.max_corner().y()))))); /* // Fill in the spans, at which the circle intersects the rows. int row_first = bboxi.min_corner().y(); int row_last = bboxi.max_corner().y(); for (; row_first <= row_last; ++ row_first) { float y = float(j) - center.y(); float discr = radius2 - sqr(y); if (discr > 0) { // Circle intersects the row j at 2 points. float d = sqrt(discr); spans.push_back(std.pair(center.x() - d, center.x() + d))); break; } } for (int j = row_first + 1; j <= row_last; ++ j) { float y = float(j) - center.y(); float discr = radius2 - sqr(y); if (discr > 0) { // Circle intersects the row j at 2 points. float d = sqrt(discr); spans.push_back(std.pair(center.x() - d, center.x() + d))); } else { row_last = j - 1; break; } } */ float area_total = 0; float volume_total = 0; float volume_excess = 0; float volume_deficit = 0; size_t n_cells = 0; float area_circle_total = 0; #if 0 // The intermediate lines. for (int j = row_first; j < row_last; ++ j) { const std::pair &span1 = spans[j]; const std::pair &span2 = spans[j+1]; float l1 = span1.first; float l2 = span2.first; float r1 = span1.second; float r2 = span2.second; if (l2 < l1) std::swap(l1, l2); if (r1 > r2) std::swap(r1, r2); int il1 = int(floor(l1)); int il2 = int(ceil(l2)); int ir1 = int(floor(r1)); int ir2 = int(floor(r2)); assert(il2 <= ir1); for (int i = il1; i < il2; ++ i) { Cell &cell = cells[n_cells ++]; cell.idx.x(i); cell.idx.y(j); cell.area = area; } for (int i = il2; i < ir1; ++ i) { Cell &cell = cells[n_cells ++]; cell.idx.x(i); cell.idx.y(j); cell.area = 1.f; } for (int i = ir1; i < ir2; ++ i) { Cell &cell = cells[n_cells ++]; cell.idx.x(i); cell.idx.y(j); cell.area = area; } } #else for (int j = bboxi.min_corner().y(); j < bboxi.max_corner().y(); ++ j) { for (int i = bboxi.min_corner().x(); i < bboxi.max_corner().x(); ++i) { B2f bb(V2f(float(i), float(j)), V2f(float(i + 1), float(j + 1))); V2f poly[8]; bool poly_arc[8]; int n = clip_circle_by_AABB(center, radius, bb, poly, poly_arc); float area = polyArea(poly, n); assert(area >= 0.f && area <= 1.000001f); if (area == 0.f) continue; Cell &cell = cells[n_cells ++]; cell.idx.x(i); cell.idx.y(j); cell.volume = acc[j][i]; cell.area = mask[j][i]; assert(cell.area >= 0.f && cell.area <= 1.000001f); area_circle_total += area; if (cell.area < area) cell.area = area; cell.fraction_covered = clamp(0.f, 1.f, (cell.area > 0) ? (area / cell.area) : 0); if (cell.fraction_covered == 0) { -- n_cells; continue; } float cell_height = cell.volume / cell.area; cell.excess_height = cell_height - height_target; if (cell.excess_height > 0.f) volume_excess += cell.excess_height * cell.area * cell.fraction_covered; else volume_deficit -= cell.excess_height * cell.area * cell.fraction_covered; volume_total += cell.volume * cell.fraction_covered; area_total += cell.area * cell.fraction_covered; } } #endif float area_circle_total2 = float(M_PI) * sqr(radius); float area_err = fabs(area_circle_total2 - area_circle_total) / area_circle_total2; // printf("area_circle_total: %f, %f, %f\n", area_circle_total, area_circle_total2, area_err); float volume_full = float(M_PI) * sqr(radius) * height_target; // if (true) { // printf("volume_total: %f, volume_full: %f, fill factor: %f\n", volume_total, volume_full, 100.f - 100.f * volume_total / volume_full); // printf("volume_full: %f, volume_excess+deficit: %f, volume_excess: %f, volume_deficit: %f\n", volume_full, volume_excess+volume_deficit, volume_excess, volume_deficit); if (simulationType == ExtrusionSimulationSpreadFull || volume_total <= volume_full) { // The volume under the circle is spreaded fully. float height_avg = volume_total / area_total; for (size_t i = 0; i < n_cells; ++ i) { const Cell &cell = cells[i]; acc[cell.idx.y()][cell.idx.x()] = (1.f - cell.fraction_covered) * cell.volume + cell.fraction_covered * cell.area * height_avg; } } else if (simulationType == ExtrusionSimulationSpreadExcess) { // The volume under the circle does not fit. // 1) Fill the underfilled cells and remove them from the list. float volume_borrowed_total = 0.; for (size_t i = 0; i < n_cells;) { Cell &cell = cells[i]; if (cell.excess_height <= 0) { // Fill in the part of the cell below the circle. float volume_borrowed = - cell.excess_height * cell.area * cell.fraction_covered; assert(volume_borrowed >= 0.f); acc[cell.idx.y()][cell.idx.x()] = cell.volume + volume_borrowed; volume_borrowed_total += volume_borrowed; cell = cells[-- n_cells]; } else ++ i; } // 2) Sort the remaining cells by their excess height. std::sort(cells.begin(), cells.begin() + n_cells); // 3) Prefix sum the areas per excess height. // The excess height is discrete with the number of excess cells. areas_sum[n_cells-1] = cells[n_cells-1].area * cells[n_cells-1].fraction_covered; for (int i = n_cells - 2; i >= 0; -- i) { const Cell &cell = cells[i]; areas_sum[i] = areas_sum[i + 1] + cell.area * cell.fraction_covered; } // 4) Find the excess height, where the volume_excess is over the volume_borrowed_total. float volume_current = 0.f; float excess_height_prev = 0.f; size_t i_top = n_cells; for (size_t i = 0; i < n_cells; ++ i) { const Cell &cell = cells[i]; volume_current += (cell.excess_height - excess_height_prev) * areas_sum[i]; excess_height_prev = cell.excess_height; if (volume_current > volume_borrowed_total) { i_top = i; break; } } // 5) Remove material from the cells with deficit. // First remove all the excess material from the cells, where the deficit is low. for (size_t i = 0; i < i_top; ++ i) { const Cell &cell = cells[i]; float volume_removed = cell.excess_height * cell.area * cell.fraction_covered; acc[cell.idx.y()][cell.idx.x()] = cell.volume - volume_removed; volume_borrowed_total -= volume_removed; } // Second remove some excess material from the cells, where the deficit is high. if (i_top < n_cells) { float height_diff = volume_borrowed_total / areas_sum[i_top]; for (size_t i = i_top; i < n_cells; ++ i) { const Cell &cell = cells[i]; acc[cell.idx.y()][cell.idx.x()] = cell.volume - height_diff * cell.area * cell.fraction_covered; } } } } } inline std::vector CreatePowerColorGradient24bit() { int i; int iColor = 0; std::vector out(6 * 255 + 1, V3uc(0, 0, 0)); for (i = 0; i < 256; ++i) out[iColor++] = V3uc(0, 0, i); for (i = 1; i < 256; ++i) out[iColor++] = V3uc(0, i, 255); for (i = 1; i < 256; ++i) out[iColor++] = V3uc(0, 255, 256 - i); for (i = 1; i < 256; ++i) out[iColor++] = V3uc(i, 255, 0); for (i = 1; i < 256; ++i) out[iColor++] = V3uc(255, 256 - i, 0); for (i = 1; i < 256; ++i) out[iColor++] = V3uc(255, 0, i); return out; } class ExtrusionSimulatorImpl { public: std::vector image_data; A2f accumulator; A2uc bitmap; unsigned int bitmap_oversampled; ExtrusionPoints extrusion_points; // RGB gradient to color map the fullness of an accumulator bucket into the output image. std::vector > color_gradient; }; ExtrusionSimulator::ExtrusionSimulator() : pimpl(new ExtrusionSimulatorImpl) { pimpl->color_gradient = CreatePowerColorGradient24bit(); pimpl->bitmap_oversampled = 4; } ExtrusionSimulator::~ExtrusionSimulator() { delete pimpl; pimpl = NULL; } void ExtrusionSimulator::set_image_size(const Point &image_size) { // printf("ExtrusionSimulator::set_image_size()\n"); if (this->image_size.x == image_size.x && this->image_size.y == image_size.y) return; // printf("Setting image size: %d, %d\n", image_size.x, image_size.y); this->image_size = image_size; // Allocate the image data in an RGBA format. // printf("Allocating image data, size %d\n", image_size.x * image_size.y * 4); pimpl->image_data.assign(image_size.x * image_size.y * 4, 0); // printf("Allocating image data, allocated\n"); //FIXME fill the image with red vertical lines. for (size_t r = 0; r < image_size.y; ++ r) { for (size_t c = 0; c < image_size.x; c += 2) { // Color red pimpl->image_data[r * image_size.x * 4 + c * 4] = 255; // Opacity full pimpl->image_data[r * image_size.x * 4 + c * 4 + 3] = 255; } } // printf("Allocating image data, set\n"); } void ExtrusionSimulator::set_viewport(const BoundingBox &viewport) { // printf("ExtrusionSimulator::set_viewport(%d, %d, %d, %d)\n", viewport.min.x, viewport.min.y, viewport.max.x, viewport.max.y); if (this->viewport != viewport) { this->viewport = viewport; Point sz = viewport.size(); pimpl->accumulator.resize(boost::extents[sz.y][sz.x]); pimpl->bitmap.resize(boost::extents[sz.y*pimpl->bitmap_oversampled][sz.x*pimpl->bitmap_oversampled]); // printf("Accumulator size: %d, %d\n", sz.y, sz.x); } } void ExtrusionSimulator::set_bounding_box(const BoundingBox &bbox) { this->bbox = bbox; } const void* ExtrusionSimulator::image_ptr() const { return (pimpl->image_data.empty()) ? NULL : (void*)&pimpl->image_data.front(); } void ExtrusionSimulator::reset_accumulator() { // printf("ExtrusionSimulator::reset_accumulator()\n"); Point sz = viewport.size(); // printf("Reset accumulator, Accumulator size: %d, %d\n", sz.y, sz.x); memset(&pimpl->accumulator[0][0], 0, sizeof(float) * sz.x * sz.y); memset(&pimpl->bitmap[0][0], 0, sz.x * sz.y * pimpl->bitmap_oversampled * pimpl->bitmap_oversampled); pimpl->extrusion_points.clear(); // printf("Reset accumulator, done.\n"); } void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const Point &shift, ExtrusionSimulationType simulationType) { // printf("Extruding a path. Nr points: %d, width: %f, height: %f\r\n", path.polyline.points.size(), path.width, path.height); // Convert the path to V2f points, shift and scale them to the viewport. std::vector polyline; polyline.reserve(path.polyline.points.size()); float scalex = float(viewport.size().x) / float(bbox.size().x); float scaley = float(viewport.size().y) / float(bbox.size().y); float w = scale_(path.width) * scalex; float h = scale_(path.height) * scalex; w = scale_(path.mm3_per_mm / path.height) * scalex; // printf("scalex: %f, scaley: %f\n", scalex, scaley); // printf("bbox: %d,%d %d,%d\n", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y); for (Points::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) { // printf("point %d,%d\n", it->x+shift.x, it->y+shift.y); ExtrusionPoint ept; ept.center = V2f(float(it->x+shift.x-bbox.min.x) * scalex, float(it->y+shift.y-bbox.min.y) * scaley); ept.radius = w/2.f; ept.height = 0.5f; polyline.push_back(ept.center); pimpl->extrusion_points.push_back(ept); } // Extrude the polyline into an accumulator. // printf("width scaled: %f, height scaled: %f\n", w, h); gcode_paint_layer(polyline, w, 0.5f, pimpl->accumulator); if (simulationType > ExtrusionSimulationDontSpread) gcode_paint_bitmap(polyline, w, pimpl->bitmap, pimpl->bitmap_oversampled); // double path.mm3_per_mm; // mm^3 of plastic per mm of linear head motion // float path.width; // float path.height; } void ExtrusionSimulator::evaluate_accumulator(ExtrusionSimulationType simulationType) { // printf("ExtrusionSimulator::evaluate_accumulator()\n"); Point sz = viewport.size(); if (simulationType > ExtrusionSimulationDontSpread) { // Average the cells of a bitmap into a lower resolution floating point mask. A2f mask(boost::extents[sz.y][sz.x]); for (int r = 0; r < sz.y; ++r) { for (int c = 0; c < sz.x; ++c) { float p = 0; for (int j = 0; j < pimpl->bitmap_oversampled; ++ j) { for (int i = 0; i < pimpl->bitmap_oversampled; ++ i) { if (pimpl->bitmap[r * pimpl->bitmap_oversampled + j][c * pimpl->bitmap_oversampled + i]) p += 1.f; } } p /= float(pimpl->bitmap_oversampled * pimpl->bitmap_oversampled * 2); mask[r][c] = p; } } // Spread the excess of the material. gcode_spread_points(pimpl->accumulator, mask, pimpl->extrusion_points, simulationType); } // Color map the accumulator. for (int r = 0; r < sz.y; ++r) { unsigned char *ptr = &pimpl->image_data[(image_size.x * (viewport.min.y + r) + viewport.min.x) * 4]; for (int c = 0; c < sz.x; ++c) { #if 1 float p = pimpl->accumulator[r][c]; #else float p = mask[r][c]; #endif int idx = int(floor(p * float(pimpl->color_gradient.size()) + 0.5f)); V3uc clr = pimpl->color_gradient[clamp(0, int(pimpl->color_gradient.size()-1), idx)]; *ptr ++ = clr.get<0>(); *ptr ++ = clr.get<1>(); *ptr ++ = clr.get<2>(); *ptr ++ = (idx == 0) ? 0 : 255; } } } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/ExtrusionSimulator.hpp000066400000000000000000000037711324354444700242530ustar00rootroot00000000000000#ifndef slic3r_ExtrusionSimulator_hpp_ #define slic3r_ExtrusionSimulator_hpp_ #include "libslic3r.h" #include "ExtrusionEntity.hpp" #include "BoundingBox.hpp" namespace Slic3r { enum ExtrusionSimulationType { ExtrusionSimulationSimple, ExtrusionSimulationDontSpread, ExtrisopmSimulationSpreadNotOverfilled, ExtrusionSimulationSpreadFull, ExtrusionSimulationSpreadExcess }; // An opaque class, to keep the boost stuff away from the header. class ExtrusionSimulatorImpl; class ExtrusionSimulator { public: ExtrusionSimulator(); ~ExtrusionSimulator(); // Size of the image, that will be returned by image_ptr(). // The image may be bigger than the viewport as many graphics drivers // expect the size of a texture to be rounded to a power of two. void set_image_size(const Point &image_size); // Which part of the image shall be rendered to? void set_viewport(const BoundingBox &viewport); // Shift and scale of the rendered extrusion paths into the viewport. void set_bounding_box(const BoundingBox &bbox); // Reset the extrusion accumulator to zero for all buckets. void reset_accumulator(); // Paint a thick path into an extrusion buffer. // A simple implementation is provided now, splatting a rectangular extrusion for each linear segment. // In the future, spreading and suqashing of a material will be simulated. void extrude_to_accumulator(const ExtrusionPath &path, const Point &shift, ExtrusionSimulationType simulationType); // Evaluate the content of the accumulator and paint it into the viewport. // After this call the image_ptr() call will return a valid image. void evaluate_accumulator(ExtrusionSimulationType simulationType); // An RGBA image of image_size, to be loaded into a GPU texture. const void* image_ptr() const; private: Point image_size; BoundingBox viewport; BoundingBox bbox; ExtrusionSimulatorImpl *pimpl; }; } #endif /* slic3r_ExtrusionSimulator_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/Fill/000077500000000000000000000000001324354444700205205ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/libslic3r/Fill/Fill.cpp000066400000000000000000000310321324354444700221110ustar00rootroot00000000000000#include #include #include #include "../ClipperUtils.hpp" #include "../Geometry.hpp" #include "../Layer.hpp" #include "../Print.hpp" #include "../PrintConfig.hpp" #include "../Surface.hpp" #include "FillBase.hpp" namespace Slic3r { struct SurfaceGroupAttrib { SurfaceGroupAttrib() : is_solid(false), flow_width(0.f), pattern(-1) {} bool operator==(const SurfaceGroupAttrib &other) const { return is_solid == other.is_solid && flow_width == other.flow_width && pattern == other.pattern; } bool is_solid; float flow_width; // pattern is of type InfillPattern, -1 for an unset pattern. int pattern; }; // Generate infills for Slic3r::Layer::Region. // The Slic3r::Layer::Region at this point of time may contain // surfaces of various types (internal/bridge/top/bottom/solid). // The infills are generated on the groups of surfaces with a compatible type. // Returns an array of Slic3r::ExtrusionPath::Collection objects containing the infills generaed now // and the thin fills generated by generate_perimeters(). void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) { // Slic3r::debugf "Filling layer %d:\n", $layerm->layer->id; double fill_density = layerm.region()->config.fill_density; Flow infill_flow = layerm.flow(frInfill); Flow solid_infill_flow = layerm.flow(frSolidInfill); Flow top_solid_infill_flow = layerm.flow(frTopSolidInfill); Surfaces surfaces; // merge adjacent surfaces // in case of bridge surfaces, the ones with defined angle will be attached to the ones // without any angle (shouldn't this logic be moved to process_external_surfaces()?) { Polygons polygons_bridged; polygons_bridged.reserve(layerm.fill_surfaces.surfaces.size()); for (Surfaces::iterator it = layerm.fill_surfaces.surfaces.begin(); it != layerm.fill_surfaces.surfaces.end(); ++ it) if (it->bridge_angle >= 0) polygons_append(polygons_bridged, *it); // group surfaces by distinct properties (equal surface_type, thickness, thickness_layers, bridge_angle) // group is of type Slic3r::SurfaceCollection //FIXME: Use some smart heuristics to merge similar surfaces to eliminate tiny regions. std::vector groups; layerm.fill_surfaces.group(&groups); // merge compatible groups (we can generate continuous infill for them) { // cache flow widths and patterns used for all solid groups // (we'll use them for comparing compatible groups) std::vector group_attrib(groups.size()); for (size_t i = 0; i < groups.size(); ++ i) { // we can only merge solid non-bridge surfaces, so discard // non-solid surfaces const Surface &surface = *groups[i].front(); if (surface.is_solid() && (!surface.is_bridge() || layerm.layer()->id() == 0)) { group_attrib[i].is_solid = true; group_attrib[i].flow_width = (surface.surface_type == stTop) ? top_solid_infill_flow.width : solid_infill_flow.width; group_attrib[i].pattern = surface.is_external() ? layerm.region()->config.external_fill_pattern.value : ipRectilinear; } } // Loop through solid groups, find compatible groups and append them to this one. for (size_t i = 0; i < groups.size(); ++ i) { if (! group_attrib[i].is_solid) continue; for (size_t j = i + 1; j < groups.size();) { if (group_attrib[i] == group_attrib[j]) { // groups are compatible, merge them groups[i].insert(groups[i].end(), groups[j].begin(), groups[j].end()); groups.erase(groups.begin() + j); group_attrib.erase(group_attrib.begin() + j); } else ++ j; } } } // Give priority to bridges. Process the bridges in the first round, the rest of the surfaces in the 2nd round. for (size_t round = 0; round < 2; ++ round) { for (std::vector::iterator it_group = groups.begin(); it_group != groups.end(); ++ it_group) { const SurfacesPtr &group = *it_group; bool is_bridge = group.front()->bridge_angle >= 0; if (is_bridge != (round == 0)) continue; // Make a union of polygons defining the infiill regions of a group, use a safety offset. Polygons union_p = union_(to_polygons(*it_group), true); // Subtract surfaces having a defined bridge_angle from any other, use a safety offset. if (! polygons_bridged.empty() && ! is_bridge) union_p = diff(union_p, polygons_bridged, true); // subtract any other surface already processed //FIXME Vojtech: Because the bridge surfaces came first, they are subtracted twice! // Using group.front() as a template. surfaces_append(surfaces, diff_ex(union_p, to_polygons(surfaces), true), *group.front()); } } } // we need to detect any narrow surfaces that might collapse // when adding spacing below // such narrow surfaces are often generated in sloping walls // by bridge_over_infill() and combine_infill() as a result of the // subtraction of the combinable area from the layer infill area, // which leaves small areas near the perimeters // we are going to grow such regions by overlapping them with the void (if any) // TODO: detect and investigate whether there could be narrow regions without // any void neighbors { coord_t distance_between_surfaces = std::max( std::max(infill_flow.scaled_spacing(), solid_infill_flow.scaled_spacing()), top_solid_infill_flow.scaled_spacing()); Polygons surfaces_polygons = to_polygons(surfaces); Polygons collapsed = diff( surfaces_polygons, offset2(surfaces_polygons, -distance_between_surfaces/2, +distance_between_surfaces/2), true); Polygons to_subtract; to_subtract.reserve(collapsed.size() + number_polygons(surfaces)); for (Surfaces::const_iterator it_surface = surfaces.begin(); it_surface != surfaces.end(); ++ it_surface) if (it_surface->surface_type == stInternalVoid) polygons_append(to_subtract, *it_surface); polygons_append(to_subtract, collapsed); surfaces_append( surfaces, intersection_ex( offset(collapsed, distance_between_surfaces), to_subtract, true), stInternalSolid); } if (0) { // require "Slic3r/SVG.pm"; // Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg", // expolygons => [ map $_->expolygon, grep !$_->is_solid, @surfaces ], // red_expolygons => [ map $_->expolygon, grep $_->is_solid, @surfaces ], // ); } for (const Surface &surface : surfaces) { if (surface.surface_type == stInternalVoid) continue; InfillPattern fill_pattern = layerm.region()->config.fill_pattern.value; double density = fill_density; FlowRole role = (surface.surface_type == stTop) ? frTopSolidInfill : (surface.is_solid() ? frSolidInfill : frInfill); bool is_bridge = layerm.layer()->id() > 0 && surface.is_bridge(); if (surface.is_solid()) { density = 100.; fill_pattern = (surface.is_external() && ! is_bridge) ? layerm.region()->config.external_fill_pattern.value : ipRectilinear; } else if (density <= 0) continue; // get filler object std::unique_ptr f = std::unique_ptr(Fill::new_from_type(fill_pattern)); f->set_bounding_box(layerm.layer()->object()->bounding_box()); // calculate the actual flow we'll be using for this infill coordf_t h = (surface.thickness == -1) ? layerm.layer()->height : surface.thickness; Flow flow = layerm.region()->flow( role, h, is_bridge || f->use_bridge_flow(), // bridge flow? layerm.layer()->id() == 0, // first layer? -1, // auto width *layerm.layer()->object() ); // calculate flow spacing for infill pattern generation bool using_internal_flow = false; if (! surface.is_solid() && ! is_bridge) { // it's internal infill, so we can calculate a generic flow spacing // for all layers, for avoiding the ugly effect of // misaligned infill on first layer because of different extrusion width and // layer height Flow internal_flow = layerm.region()->flow( frInfill, layerm.layer()->object()->config.layer_height.value, // TODO: handle infill_every_layers? false, // no bridge false, // no first layer -1, // auto width *layerm.layer()->object() ); f->spacing = internal_flow.spacing(); using_internal_flow = true; } else { f->spacing = flow.spacing(); } double link_max_length = 0.; if (! is_bridge) { #if 0 link_max_length = layerm.region()->config.get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing()); // printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length); #else if (density > 80.) // 80% link_max_length = 3. * f->spacing; #endif } f->layer_id = layerm.layer()->id(); f->z = layerm.layer()->print_z; f->angle = float(Geometry::deg2rad(layerm.region()->config.fill_angle.value)); // Maximum length of the perimeter segment linking two infill lines. f->link_max_length = scale_(link_max_length); // Used by the concentric infill pattern to clip the loops to create extrusion paths. f->loop_clipping = scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER; // f->layer_height = h; // apply half spacing using this flow's own spacing and generate infill FillParams params; params.density = 0.01 * density; // params.dont_adjust = true; params.dont_adjust = false; Polylines polylines = f->fill_surface(&surface, params); if (polylines.empty()) continue; // calculate actual flow from spacing (which might have been adjusted by the infill // pattern generator) if (using_internal_flow) { // if we used the internal flow we're not doing a solid infill // so we can safely ignore the slight variation that might have // been applied to $f->flow_spacing } else { flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, h, is_bridge || f->use_bridge_flow()); } // Save into layer. auto *eec = new ExtrusionEntityCollection(); out.entities.push_back(eec); // Only concentric fills are not sorted. eec->no_sort = f->no_sort(); extrusion_entities_append_paths( eec->entities, STDMOVE(polylines), is_bridge ? erBridgeInfill : (surface.is_solid() ? ((surface.surface_type == stTop) ? erTopSolidInfill : erSolidInfill) : erInternalInfill), flow.mm3_per_mm(), flow.width, flow.height); } // add thin fill regions // thin_fills are of C++ Slic3r::ExtrusionEntityCollection, perl type Slic3r::ExtrusionPath::Collection // Unpacks the collection, creates multiple collections per path. // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. // Why the paths are unpacked? for (const ExtrusionEntity *thin_fill : layerm.thin_fills.entities) { ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection()); out.entities.push_back(&collection); collection.entities.push_back(thin_fill->clone()); } } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/Fill/Fill.hpp000066400000000000000000000012331324354444700221160ustar00rootroot00000000000000#ifndef slic3r_Fill_hpp_ #define slic3r_Fill_hpp_ #include #include #include #include "../libslic3r.h" #include "../BoundingBox.hpp" #include "../PrintConfig.hpp" #include "FillBase.hpp" namespace Slic3r { class ExtrusionEntityCollection; class LayerRegion; // An interface class to Perl, aggregating an instance of a Fill and a FillData. class Filler { public: Filler() : fill(NULL) {} ~Filler() { delete fill; fill = NULL; } Fill *fill; FillParams params; }; void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out); } // namespace Slic3r #endif // slic3r_Fill_hpp_ Slic3r-version_1.39.1/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp000066400000000000000000000207411324354444700241510ustar00rootroot00000000000000#include "../ClipperUtils.hpp" #include "../PolylineCollection.hpp" #include "../Surface.hpp" #include "Fill3DHoneycomb.hpp" namespace Slic3r { /* Creates a contiguous sequence of points at a specified height that make up a horizontal slice of the edges of a space filling truncated octahedron tesselation. The octahedrons are oriented so that the square faces are in the horizontal plane with edges parallel to the X and Y axes. Credits: David Eccles (gringer). */ // Generate an array of points that are in the same direction as the // basic printing line (i.e. Y points for columns, X points for rows) // Note: a negative offset only causes a change in the perpendicular // direction static std::vector colinearPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength) { const coordf_t offset2 = std::abs(offset / coordf_t(2.)); std::vector points; points.push_back(baseLocation - offset2); for (size_t i = 0; i < gridLength; ++i) { points.push_back(baseLocation + i + offset2); points.push_back(baseLocation + i + 1 - offset2); } points.push_back(baseLocation + gridLength + offset2); return points; } // Generate an array of points for the dimension that is perpendicular to // the basic printing line (i.e. X points for columns, Y points for rows) static std::vector perpendPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength) { coordf_t offset2 = offset / coordf_t(2.); coord_t side = 2 * (baseLocation & 1) - 1; std::vector points; points.push_back(baseLocation - offset2 * side); for (size_t i = 0; i < gridLength; ++i) { side = 2*((i+baseLocation) & 1) - 1; points.push_back(baseLocation + offset2 * side); points.push_back(baseLocation + offset2 * side); } points.push_back(baseLocation - offset2 * side); return points; } // Trims an array of points to specified rectangular limits. Point // components that are outside these limits are set to the limits. static inline void trim(Pointfs &pts, coordf_t minX, coordf_t minY, coordf_t maxX, coordf_t maxY) { for (Pointfs::iterator it = pts.begin(); it != pts.end(); ++ it) { it->x = clamp(minX, maxX, it->x); it->y = clamp(minY, maxY, it->y); } } static inline Pointfs zip(const std::vector &x, const std::vector &y) { assert(x.size() == y.size()); Pointfs out; out.reserve(x.size()); for (size_t i = 0; i < x.size(); ++ i) out.push_back(Pointf(x[i], y[i])); return out; } // Generate a set of curves (array of array of 2d points) that describe a // horizontal slice of a truncated regular octahedron with edge length 1. // curveType specifies which lines to print, 1 for vertical lines // (columns), 2 for horizontal lines (rows), and 3 for both. static std::vector makeNormalisedGrid(coordf_t z, size_t gridWidth, size_t gridHeight, size_t curveType) { // offset required to create a regular octagram coordf_t octagramGap = coordf_t(0.5); // sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap] coordf_t a = std::sqrt(coordf_t(2.)); // period coordf_t wave = fabs(fmod(z, a) - a/2.)/a*4. - 1.; coordf_t offset = wave * octagramGap; std::vector points; if ((curveType & 1) != 0) { for (size_t x = 0; x <= gridWidth; ++x) { points.push_back(Pointfs()); Pointfs &newPoints = points.back(); newPoints = zip( perpendPoints(offset, x, gridHeight), colinearPoints(offset, 0, gridHeight)); // trim points to grid edges trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); if (x & 1) std::reverse(newPoints.begin(), newPoints.end()); } } if ((curveType & 2) != 0) { for (size_t y = 0; y <= gridHeight; ++y) { points.push_back(Pointfs()); Pointfs &newPoints = points.back(); newPoints = zip( colinearPoints(offset, 0, gridWidth), perpendPoints(offset, y, gridWidth)); // trim points to grid edges trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); if (y & 1) std::reverse(newPoints.begin(), newPoints.end()); } } return points; } // Generate a set of curves (array of array of 2d points) that describe a // horizontal slice of a truncated regular octahedron with a specified // grid square size. static Polylines makeGrid(coord_t z, coord_t gridSize, size_t gridWidth, size_t gridHeight, size_t curveType) { coord_t scaleFactor = gridSize; coordf_t normalisedZ = coordf_t(z) / coordf_t(scaleFactor); std::vector polylines = makeNormalisedGrid(normalisedZ, gridWidth, gridHeight, curveType); Polylines result; result.reserve(polylines.size()); for (std::vector::const_iterator it_polylines = polylines.begin(); it_polylines != polylines.end(); ++ it_polylines) { result.push_back(Polyline()); Polyline &polyline = result.back(); for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it) polyline.points.push_back(Point(coord_t(it->x * scaleFactor), coord_t(it->y * scaleFactor))); } return result; } void Fill3DHoneycomb::_fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, const std::pair &direction, ExPolygon &expolygon, Polylines &polylines_out) { // no rotation is supported for this infill pattern BoundingBox bb = expolygon.contour.bounding_box(); coord_t distance = coord_t(scale_(this->spacing) / params.density); // align bounding box to a multiple of our honeycomb grid module // (a module is 2*$distance since one $distance half-module is // growing while the other $distance half-module is shrinking) bb.merge(_align_to_grid(bb.min, Point(2*distance, 2*distance))); // generate pattern Polylines polylines = makeGrid( scale_(this->z), distance, ceil(bb.size().x / distance) + 1, ceil(bb.size().y / distance) + 1, ((this->layer_id/thickness_layers) % 2) + 1); // move pattern in place for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) it->translate(bb.min.x, bb.min.y); // clip pattern to boundaries polylines = intersection_pl(polylines, (Polygons)expolygon); // connect lines if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections ExPolygon expolygon_off; { ExPolygons expolygons_off = offset_ex(expolygon, SCALED_EPSILON); if (! expolygons_off.empty()) { // When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island. assert(expolygons_off.size() == 1); std::swap(expolygon_off, expolygons_off.front()); } } Polylines chained = PolylineCollection::chained_path_from( std::move(polylines), PolylineCollection::leftmost_point(polylines), false); // reverse allowed bool first = true; for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { if (! first) { // Try to connect the lines. Points &pts_end = polylines_out.back().points; const Point &first_point = it_polyline->points.front(); const Point &last_point = pts_end.back(); // TODO: we should also check that both points are on a fill_boundary to avoid // connecting paths on the boundaries of internal regions if (first_point.distance_to(last_point) <= 1.5 * distance && expolygon_off.contains(Line(last_point, first_point))) { // Append the polyline. pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end()); continue; } } // The lines cannot be connected. polylines_out.emplace_back(std::move(*it_polyline)); first = false; } } } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/Fill/Fill3DHoneycomb.hpp000066400000000000000000000014461324354444700241570ustar00rootroot00000000000000#ifndef slic3r_Fill3DHoneycomb_hpp_ #define slic3r_Fill3DHoneycomb_hpp_ #include #include "../libslic3r.h" #include "FillBase.hpp" namespace Slic3r { class Fill3DHoneycomb : public Fill { public: virtual Fill* clone() const { return new Fill3DHoneycomb(*this); }; virtual ~Fill3DHoneycomb() {} // require bridge flow since most of this pattern hangs in air virtual bool use_bridge_flow() const { return true; } protected: virtual void _fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, const std::pair &direction, ExPolygon &expolygon, Polylines &polylines_out); }; } // namespace Slic3r #endif // slic3r_Fill3DHoneycomb_hpp_ Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillBase.cpp000066400000000000000000000115251324354444700227110ustar00rootroot00000000000000#include #include "../ClipperUtils.hpp" #include "../Surface.hpp" #include "../PrintConfig.hpp" #include "FillBase.hpp" #include "FillConcentric.hpp" #include "FillHoneycomb.hpp" #include "Fill3DHoneycomb.hpp" #include "FillPlanePath.hpp" #include "FillRectilinear.hpp" #include "FillRectilinear2.hpp" #include "FillRectilinear3.hpp" namespace Slic3r { Fill* Fill::new_from_type(const InfillPattern type) { switch (type) { case ipConcentric: return new FillConcentric(); case ipHoneycomb: return new FillHoneycomb(); case ip3DHoneycomb: return new Fill3DHoneycomb(); case ipRectilinear: return new FillRectilinear2(); // case ipRectilinear: return new FillRectilinear(); case ipLine: return new FillLine(); case ipGrid: return new FillGrid2(); case ipTriangles: return new FillTriangles(); case ipStars: return new FillStars(); case ipCubic: return new FillCubic(); // case ipGrid: return new FillGrid(); case ipArchimedeanChords: return new FillArchimedeanChords(); case ipHilbertCurve: return new FillHilbertCurve(); case ipOctagramSpiral: return new FillOctagramSpiral(); default: CONFESS("unknown type"); return nullptr; } } Fill* Fill::new_from_type(const std::string &type) { const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); t_config_enum_values::const_iterator it = enum_keys_map.find(type); return (it == enum_keys_map.end()) ? nullptr : new_from_type(InfillPattern(it->second)); } Polylines Fill::fill_surface(const Surface *surface, const FillParams ¶ms) { // Perform offset. Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing))); // Create the infills for each of the regions. Polylines polylines_out; for (size_t i = 0; i < expp.size(); ++ i) _fill_surface_single( params, surface->thickness_layers, _infill_direction(surface), expp[i], polylines_out); return polylines_out; } // Calculate a new spacing to fill width with possibly integer number of lines, // the first and last line being centered at the interval ends. // This function possibly increases the spacing, never decreases, // and for a narrow width the increase in spacing may become severe, // therefore the adjustment is limited to 20% increase. coord_t Fill::_adjust_solid_spacing(const coord_t width, const coord_t distance) { assert(width >= 0); assert(distance > 0); // floor(width / distance) coord_t number_of_intervals = (width - EPSILON) / distance; coord_t distance_new = (number_of_intervals == 0) ? distance : ((width - EPSILON) / number_of_intervals); const coordf_t factor = coordf_t(distance_new) / coordf_t(distance); assert(factor > 1. - 1e-5); // How much could the extrusion width be increased? By 20%. const coordf_t factor_max = 1.2; if (factor > factor_max) distance_new = coord_t(floor((coordf_t(distance) * factor_max + 0.5))); return distance_new; } // Returns orientation of the infill and the reference point of the infill pattern. // For a normal print, the reference point is the center of a bounding box of the STL. std::pair Fill::_infill_direction(const Surface *surface) const { // set infill angle float out_angle = this->angle; if (out_angle == FLT_MAX) { //FIXME Vojtech: Add a warning? printf("Using undefined infill angle\n"); out_angle = 0.f; } // Bounding box is the bounding box of a perl object Slic3r::Print::Object (c++ object Slic3r::PrintObject) // The bounding box is only undefined in unit tests. Point out_shift = empty(this->bounding_box) ? surface->expolygon.contour.bounding_box().center() : this->bounding_box.center(); #if 0 if (empty(this->bounding_box)) { printf("Fill::_infill_direction: empty bounding box!"); } else { printf("Fill::_infill_direction: reference point %d, %d\n", out_shift.x, out_shift.y); } #endif if (surface->bridge_angle >= 0) { // use bridge angle //FIXME Vojtech: Add a debugf? // Slic3r::debugf "Filling bridge with angle %d\n", rad2deg($surface->bridge_angle); #ifdef SLIC3R_DEBUG printf("Filling bridge with angle %f\n", surface->bridge_angle); #endif /* SLIC3R_DEBUG */ out_angle = surface->bridge_angle; } else if (this->layer_id != size_t(-1)) { // alternate fill direction out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers); } else { // printf("Layer_ID undefined!\n"); } out_angle += float(M_PI/2.); return std::pair(out_angle, out_shift); } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillBase.hpp000066400000000000000000000110321324354444700227070ustar00rootroot00000000000000#ifndef slic3r_FillBase_hpp_ #define slic3r_FillBase_hpp_ #include #include #include #include #include "../libslic3r.h" #include "../BoundingBox.hpp" #include "../PrintConfig.hpp" namespace Slic3r { class Surface; struct FillParams { FillParams() { memset(this, 0, sizeof(FillParams)); // Adjustment does not work. dont_adjust = true; } bool full_infill() const { return density > 0.9999f; } // Fill density, fraction in <0, 1> float density; // Don't connect the fill lines around the inner perimeter. bool dont_connect; // Don't adjust spacing to fill the space evenly. bool dont_adjust; // For Honeycomb. // we were requested to complete each loop; // in this case we don't try to make more continuous paths bool complete; }; class Fill { public: // Index of the layer. size_t layer_id; // Z coordinate of the top print surface, in unscaled coordinates coordf_t z; // in unscaled coordinates coordf_t spacing; // infill / perimeter overlap, in unscaled coordinates coordf_t overlap; // in radians, ccw, 0 = East float angle; // In scaled coordinates. Maximum lenght of a perimeter segment connecting two infill lines. // Used by the FillRectilinear2, FillGrid2, FillTriangles, FillStars and FillCubic. // If left to zero, the links will not be limited. coord_t link_max_length; // In scaled coordinates. Used by the concentric infill pattern to clip the loops to create extrusion paths. coord_t loop_clipping; // In scaled coordinates. Bounding box of the 2D projection of the object. BoundingBox bounding_box; public: virtual ~Fill() {} static Fill* new_from_type(const InfillPattern type); static Fill* new_from_type(const std::string &type); void set_bounding_box(const Slic3r::BoundingBox &bbox) { bounding_box = bbox; } // Use bridge flow for the fill? virtual bool use_bridge_flow() const { return false; } // Do not sort the fill lines to optimize the print head path? virtual bool no_sort() const { return false; } // Perform the fill. virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); protected: Fill() : layer_id(size_t(-1)), z(0.), spacing(0.), // Infill / perimeter overlap. overlap(0.), // Initial angle is undefined. angle(FLT_MAX), link_max_length(0), loop_clipping(0), // The initial bounding box is empty, therefore undefined. bounding_box(Point(0, 0), Point(-1, -1)) {} // The expolygon may be modified by the method to avoid a copy. virtual void _fill_surface_single( const FillParams & /* params */, unsigned int /* thickness_layers */, const std::pair & /* direction */, ExPolygon & /* expolygon */, Polylines & /* polylines_out */) {}; virtual float _layer_angle(size_t idx) const { return (idx & 1) ? float(M_PI/2.) : 0; } virtual std::pair _infill_direction(const Surface *surface) const; public: static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); // Align a coordinate to a grid. The coordinate may be negative, // the aligned value will never be bigger than the original one. static coord_t _align_to_grid(const coord_t coord, const coord_t spacing) { // Current C++ standard defines the result of integer division to be rounded to zero, // for both positive and negative numbers. Here we want to round down for negative // numbers as well. coord_t aligned = (coord < 0) ? ((coord - spacing + 1) / spacing) * spacing : (coord / spacing) * spacing; assert(aligned <= coord); return aligned; } static Point _align_to_grid(Point coord, Point spacing) { return Point(_align_to_grid(coord.x, spacing.x), _align_to_grid(coord.y, spacing.y)); } static coord_t _align_to_grid(coord_t coord, coord_t spacing, coord_t base) { return base + _align_to_grid(coord - base, spacing); } static Point _align_to_grid(Point coord, Point spacing, Point base) { return Point(_align_to_grid(coord.x, spacing.x, base.x), _align_to_grid(coord.y, spacing.y, base.y)); } }; } // namespace Slic3r #endif // slic3r_FillBase_hpp_ Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillConcentric.cpp000066400000000000000000000045541324354444700241320ustar00rootroot00000000000000#include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" #include "../Surface.hpp" #include "FillConcentric.hpp" namespace Slic3r { void FillConcentric::_fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, const std::pair &direction, ExPolygon &expolygon, Polylines &polylines_out) { // no rotation is supported for this infill pattern BoundingBox bounding_box = expolygon.contour.bounding_box(); coord_t min_spacing = scale_(this->spacing); coord_t distance = coord_t(min_spacing / params.density); if (params.density > 0.9999f && !params.dont_adjust) { distance = this->_adjust_solid_spacing(bounding_box.size().x, distance); this->spacing = unscale(distance); } Polygons loops = (Polygons)expolygon; Polygons last = loops; while (! last.empty()) { last = offset2(last, -(distance + min_spacing/2), +min_spacing/2); loops.insert(loops.end(), last.begin(), last.end()); } // generate paths from the outermost to the innermost, to avoid // adhesion problems of the first central tiny loops loops = union_pt_chained(loops, false); // split paths using a nearest neighbor search size_t iPathFirst = polylines_out.size(); Point last_pos(0, 0); for (const Polygon &loop : loops) { polylines_out.push_back(loop.split_at_index(last_pos.nearest_point_index(loop))); last_pos = polylines_out.back().last_point(); } // clip the paths to prevent the extruder from getting exactly on the first point of the loop // Keep valid paths only. size_t j = iPathFirst; for (size_t i = iPathFirst; i < polylines_out.size(); ++ i) { polylines_out[i].clip_end(this->loop_clipping); if (polylines_out[i].is_valid()) { if (j < i) polylines_out[j] = std::move(polylines_out[i]); ++ j; } } if (j < polylines_out.size()) polylines_out.erase(polylines_out.begin() + j, polylines_out.end()); //TODO: return ExtrusionLoop objects to get better chained paths, // otherwise the outermost loop starts at the closest point to (0, 0). // We want the loops to be split inside the G-code generator to get optimum path planning. } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillConcentric.hpp000066400000000000000000000012521324354444700241270ustar00rootroot00000000000000#ifndef slic3r_FillConcentric_hpp_ #define slic3r_FillConcentric_hpp_ #include "FillBase.hpp" namespace Slic3r { class FillConcentric : public Fill { public: virtual ~FillConcentric() {} protected: virtual Fill* clone() const { return new FillConcentric(*this); }; virtual void _fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, const std::pair &direction, ExPolygon &expolygon, Polylines &polylines_out); virtual bool no_sort() const { return true; } }; } // namespace Slic3r #endif // slic3r_FillConcentric_hpp_ Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillHoneycomb.cpp000066400000000000000000000134651324354444700237670ustar00rootroot00000000000000#include "../ClipperUtils.hpp" #include "../PolylineCollection.hpp" #include "../Surface.hpp" #include "FillHoneycomb.hpp" namespace Slic3r { void FillHoneycomb::_fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, const std::pair &direction, ExPolygon &expolygon, Polylines &polylines_out) { // cache hexagons math CacheID cache_id(params.density, this->spacing); Cache::iterator it_m = this->cache.find(cache_id); if (it_m == this->cache.end()) { it_m = this->cache.insert(it_m, std::pair(cache_id, CacheData())); CacheData &m = it_m->second; coord_t min_spacing = scale_(this->spacing); m.distance = min_spacing / params.density; m.hex_side = m.distance / (sqrt(3)/2); m.hex_width = m.distance * 2; // $m->{hex_width} == $m->{hex_side} * sqrt(3); coord_t hex_height = m.hex_side * 2; m.pattern_height = hex_height + m.hex_side; m.y_short = m.distance * sqrt(3)/3; m.x_offset = min_spacing / 2; m.y_offset = m.x_offset * sqrt(3)/3; m.hex_center = Point(m.hex_width/2, m.hex_side); } CacheData &m = it_m->second; Polygons polygons; { // adjust actual bounding box to the nearest multiple of our hex pattern // and align it so that it matches across layers BoundingBox bounding_box = expolygon.contour.bounding_box(); { // rotate bounding box according to infill direction Polygon bb_polygon = bounding_box.polygon(); bb_polygon.rotate(direction.first, m.hex_center); bounding_box = bb_polygon.bounding_box(); // extend bounding box so that our pattern will be aligned with other layers // $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one // The infill is not aligned to the object bounding box, but to a world coordinate system. Supposedly good enough. bounding_box.merge(_align_to_grid(bounding_box.min, Point(m.hex_width, m.pattern_height))); } coord_t x = bounding_box.min.x; while (x <= bounding_box.max.x) { Polygon p; coord_t ax[2] = { x + m.x_offset, x + m.distance - m.x_offset }; for (size_t i = 0; i < 2; ++ i) { std::reverse(p.points.begin(), p.points.end()); // turn first half upside down for (coord_t y = bounding_box.min.y; y <= bounding_box.max.y; y += m.y_short + m.hex_side + m.y_short + m.hex_side) { p.points.push_back(Point(ax[1], y + m.y_offset)); p.points.push_back(Point(ax[0], y + m.y_short - m.y_offset)); p.points.push_back(Point(ax[0], y + m.y_short + m.hex_side + m.y_offset)); p.points.push_back(Point(ax[1], y + m.y_short + m.hex_side + m.y_short - m.y_offset)); p.points.push_back(Point(ax[1], y + m.y_short + m.hex_side + m.y_short + m.hex_side + m.y_offset)); } ax[0] = ax[0] + m.distance; ax[1] = ax[1] + m.distance; std::swap(ax[0], ax[1]); // draw symmetrical pattern x += m.distance; } p.rotate(-direction.first, m.hex_center); polygons.push_back(p); } } if (params.complete || true) { // we were requested to complete each loop; // in this case we don't try to make more continuous paths Polygons polygons_trimmed = intersection((Polygons)expolygon, polygons); for (Polygons::iterator it = polygons_trimmed.begin(); it != polygons_trimmed.end(); ++ it) polylines_out.push_back(it->split_at_first_point()); } else { // consider polygons as polylines without re-appending the initial point: // this cuts the last segment on purpose, so that the jump to the next // path is more straight Polylines paths; { Polylines p; for (Polygons::iterator it = polygons.begin(); it != polygons.end(); ++ it) p.push_back((Polyline)(*it)); paths = intersection_pl(p, to_polygons(expolygon)); } // connect paths if (! paths.empty()) { // prevent calling leftmost_point() on empty collections Polylines chained = PolylineCollection::chained_path_from( std::move(paths), PolylineCollection::leftmost_point(paths), false); assert(paths.empty()); paths.clear(); for (Polylines::iterator it_path = chained.begin(); it_path != chained.end(); ++ it_path) { if (! paths.empty()) { // distance between first point of this path and last point of last path double distance = paths.back().last_point().distance_to(it_path->first_point()); if (distance <= m.hex_width) { paths.back().points.insert(paths.back().points.end(), it_path->points.begin(), it_path->points.end()); continue; } } // Don't connect the paths. paths.push_back(*it_path); } } // clip paths again to prevent connection segments from crossing the expolygon boundaries paths = intersection_pl(paths, to_polygons(offset_ex(expolygon, SCALED_EPSILON))); // Move the polylines to the output, avoid a deep copy. size_t j = polylines_out.size(); polylines_out.resize(j + paths.size(), Polyline()); for (size_t i = 0; i < paths.size(); ++ i) std::swap(polylines_out[j ++], paths[i]); } } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillHoneycomb.hpp000066400000000000000000000027201324354444700237640ustar00rootroot00000000000000#ifndef slic3r_FillHoneycomb_hpp_ #define slic3r_FillHoneycomb_hpp_ #include #include "../libslic3r.h" #include "FillBase.hpp" namespace Slic3r { class FillHoneycomb : public Fill { public: virtual ~FillHoneycomb() {} protected: virtual Fill* clone() const { return new FillHoneycomb(*this); }; virtual void _fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, const std::pair &direction, ExPolygon &expolygon, Polylines &polylines_out); // Caching the struct CacheID { CacheID(float adensity, coordf_t aspacing) : density(adensity), spacing(aspacing) {} float density; coordf_t spacing; bool operator<(const CacheID &other) const { return (density < other.density) || (density == other.density && spacing < other.spacing); } bool operator==(const CacheID &other) const { return density == other.density && spacing == other.spacing; } }; struct CacheData { coord_t distance; coord_t hex_side; coord_t hex_width; coord_t pattern_height; coord_t y_short; coord_t x_offset; coord_t y_offset; Point hex_center; }; typedef std::map Cache; Cache cache; virtual float _layer_angle(size_t idx) const { return float(M_PI/3.) * (idx % 3); } }; } // namespace Slic3r #endif // slic3r_FillHoneycomb_hpp_ Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillPlanePath.cpp000066400000000000000000000150521324354444700237120ustar00rootroot00000000000000#include "../ClipperUtils.hpp" #include "../PolylineCollection.hpp" #include "../Surface.hpp" #include "FillPlanePath.hpp" namespace Slic3r { void FillPlanePath::_fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, const std::pair &direction, ExPolygon &expolygon, Polylines &polylines_out) { expolygon.rotate(- direction.first); coord_t distance_between_lines = scale_(this->spacing) / params.density; // align infill across layers using the object's bounding box // Rotated bounding box of the whole object. BoundingBox bounding_box = this->bounding_box.rotated(- direction.first); Point shift = this->_centered() ? bounding_box.center() : bounding_box.min; expolygon.translate(-shift.x, -shift.y); bounding_box.translate(-shift.x, -shift.y); Pointfs pts = _generate( coord_t(ceil(coordf_t(bounding_box.min.x) / distance_between_lines)), coord_t(ceil(coordf_t(bounding_box.min.y) / distance_between_lines)), coord_t(ceil(coordf_t(bounding_box.max.x) / distance_between_lines)), coord_t(ceil(coordf_t(bounding_box.max.y) / distance_between_lines))); Polylines polylines; if (pts.size() >= 2) { // Convert points to a polyline, upscale. polylines.push_back(Polyline()); Polyline &polyline = polylines.back(); polyline.points.reserve(pts.size()); for (Pointfs::iterator it = pts.begin(); it != pts.end(); ++ it) polyline.points.push_back(Point( coord_t(floor(it->x * distance_between_lines + 0.5)), coord_t(floor(it->y * distance_between_lines + 0.5)))); // intersection(polylines_src, offset((Polygons)expolygon, scale_(0.02)), &polylines); polylines = intersection_pl(polylines, to_polygons(expolygon)); /* if (1) { require "Slic3r/SVG.pm"; print "Writing fill.svg\n"; Slic3r::SVG::output("fill.svg", no_arrows => 1, polygons => \@$expolygon, green_polygons => [ $bounding_box->polygon ], polylines => [ $polyline ], red_polylines => \@paths, ); } */ // paths must be repositioned and rotated back for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) { it->translate(shift.x, shift.y); it->rotate(direction.first); } } // Move the polylines to the output, avoid a deep copy. size_t j = polylines_out.size(); polylines_out.resize(j + polylines.size(), Polyline()); for (size_t i = 0; i < polylines.size(); ++ i) std::swap(polylines_out[j ++], polylines[i]); } // Follow an Archimedean spiral, in polar coordinates: r=a+b\theta Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) { // Radius to achieve. coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5; // Now unwind the spiral. coordf_t a = 1.; coordf_t b = 1./(2.*M_PI); coordf_t theta = 0.; coordf_t r = 1; Pointfs out; //FIXME Vojtech: If used as a solid infill, there is a gap left at the center. out.push_back(Pointf(0, 0)); out.push_back(Pointf(1, 0)); while (r < rmax) { theta += 1. / r; r = a + b * theta; out.push_back(Pointf(r * cos(theta), r * sin(theta))); } return out; } // Adapted from // http://cpansearch.perl.org/src/KRYDE/Math-PlanePath-122/lib/Math/PlanePath/HilbertCurve.pm // // state=0 3--2 plain // | // 0--1 // // state=4 1--2 transpose // | | // 0 3 // // state=8 // // state=12 3 0 rot180 + transpose // | | // 2--1 // static inline Point hilbert_n_to_xy(const size_t n) { static const int next_state[16] = { 4,0,0,12, 0,4,4,8, 12,8,8,4, 8,12,12,0 }; static const int digit_to_x[16] = { 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0 }; static const int digit_to_y[16] = { 0,0,1,1, 0,1,1,0, 1,1,0,0, 1,0,0,1 }; // Number of 2 bit digits. size_t ndigits = 0; { size_t nc = n; while(nc > 0) { nc >>= 2; ++ ndigits; } } int state = (ndigits & 1) ? 4 : 0; int dirstate = (ndigits & 1) ? 0 : 4; coord_t x = 0; coord_t y = 0; for (int i = (int)ndigits - 1; i >= 0; -- i) { int digit = (n >> (i * 2)) & 3; state += digit; if (digit != 3) dirstate = state; // lowest non-3 digit x |= digit_to_x[state] << i; y |= digit_to_y[state] << i; state = next_state[state]; } return Point(x, y); } Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) { // Minimum power of two square to fit the domain. size_t sz = 2; size_t pw = 1; { size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y); while (sz < sz0) { sz = sz << 1; ++ pw; } } size_t sz2 = sz * sz; Pointfs line; line.reserve(sz2); for (size_t i = 0; i < sz2; ++ i) { Point p = hilbert_n_to_xy(i); line.push_back(Pointf(p.x + min_x, p.y + min_y)); } return line; } Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) { // Radius to achieve. coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5; // Now unwind the spiral. coordf_t r = 0; coordf_t r_inc = sqrt(2.); Pointfs out; out.push_back(Pointf(0, 0)); while (r < rmax) { r += r_inc; coordf_t rx = r / sqrt(2.); coordf_t r2 = r + rx; out.push_back(Pointf( r, 0.)); out.push_back(Pointf( r2, rx)); out.push_back(Pointf( rx, rx)); out.push_back(Pointf( rx, r2)); out.push_back(Pointf(0., r)); out.push_back(Pointf(-rx, r2)); out.push_back(Pointf(-rx, rx)); out.push_back(Pointf(-r2, rx)); out.push_back(Pointf(-r, 0.)); out.push_back(Pointf(-r2, -rx)); out.push_back(Pointf(-rx, -rx)); out.push_back(Pointf(-rx, -r2)); out.push_back(Pointf(0., -r)); out.push_back(Pointf( rx, -r2)); out.push_back(Pointf( rx, -rx)); out.push_back(Pointf( r2+r_inc, -rx)); } return out; } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillPlanePath.hpp000066400000000000000000000037171324354444700237240ustar00rootroot00000000000000#ifndef slic3r_FillPlanePath_hpp_ #define slic3r_FillPlanePath_hpp_ #include #include "../libslic3r.h" #include "FillBase.hpp" namespace Slic3r { // The original Perl code used path generators from Math::PlanePath library: // http://user42.tuxfamily.org/math-planepath/ // http://user42.tuxfamily.org/math-planepath/gallery.html class FillPlanePath : public Fill { public: virtual ~FillPlanePath() {} protected: virtual void _fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, const std::pair &direction, ExPolygon &expolygon, Polylines &polylines_out); virtual float _layer_angle(size_t idx) const { return 0.f; } virtual bool _centered() const = 0; virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) = 0; }; class FillArchimedeanChords : public FillPlanePath { public: virtual Fill* clone() const { return new FillArchimedeanChords(*this); }; virtual ~FillArchimedeanChords() {} protected: virtual bool _centered() const { return true; } virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y); }; class FillHilbertCurve : public FillPlanePath { public: virtual Fill* clone() const { return new FillHilbertCurve(*this); }; virtual ~FillHilbertCurve() {} protected: virtual bool _centered() const { return false; } virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y); }; class FillOctagramSpiral : public FillPlanePath { public: virtual Fill* clone() const { return new FillOctagramSpiral(*this); }; virtual ~FillOctagramSpiral() {} protected: virtual bool _centered() const { return true; } virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y); }; } // namespace Slic3r #endif // slic3r_FillPlanePath_hpp_ Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillRectilinear.cpp000066400000000000000000000142261324354444700243010ustar00rootroot00000000000000#include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" #include "../PolylineCollection.hpp" #include "../Surface.hpp" #include "FillRectilinear.hpp" namespace Slic3r { void FillRectilinear::_fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, const std::pair &direction, ExPolygon &expolygon, Polylines &polylines_out) { // rotate polygons so that we can work with vertical lines here expolygon.rotate(- direction.first); this->_min_spacing = scale_(this->spacing); assert(params.density > 0.0001f && params.density <= 1.f); this->_line_spacing = coord_t(coordf_t(this->_min_spacing) / params.density); this->_diagonal_distance = this->_line_spacing * 2; this->_line_oscillation = this->_line_spacing - this->_min_spacing; // only for Line infill BoundingBox bounding_box = expolygon.contour.bounding_box(); // define flow spacing according to requested density if (params.density > 0.9999f && !params.dont_adjust) { this->_line_spacing = this->_adjust_solid_spacing(bounding_box.size().x, this->_line_spacing); this->spacing = unscale(this->_line_spacing); } else { // extend bounding box so that our pattern will be aligned with other layers // Transform the reference point to the rotated coordinate system. bounding_box.merge(_align_to_grid( bounding_box.min, Point(this->_line_spacing, this->_line_spacing), direction.second.rotated(- direction.first))); } // generate the basic pattern coord_t x_max = bounding_box.max.x + SCALED_EPSILON; Lines lines; for (coord_t x = bounding_box.min.x; x <= x_max; x += this->_line_spacing) lines.push_back(this->_line(lines.size(), x, bounding_box.min.y, bounding_box.max.y)); if (this->_horizontal_lines()) { coord_t y_max = bounding_box.max.y + SCALED_EPSILON; for (coord_t y = bounding_box.min.y; y <= y_max; y += this->_line_spacing) lines.push_back(Line(Point(bounding_box.min.x, y), Point(bounding_box.max.x, y))); } // clip paths against a slightly larger expolygon, so that the first and last paths // are kept even if the expolygon has vertical sides // the minimum offset for preventing edge lines from being clipped is SCALED_EPSILON; // however we use a larger offset to support expolygons with slightly skewed sides and // not perfectly straight //FIXME Vojtech: Update the intersecton function to work directly with lines. Polylines polylines_src; polylines_src.reserve(lines.size()); for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it) { polylines_src.push_back(Polyline()); Points &pts = polylines_src.back().points; pts.reserve(2); pts.push_back(it->a); pts.push_back(it->b); } Polylines polylines = intersection_pl(polylines_src, offset(to_polygons(expolygon), scale_(0.02)), false); // FIXME Vojtech: This is only performed for horizontal lines, not for the vertical lines! const float INFILL_OVERLAP_OVER_SPACING = 0.3f; // How much to extend an infill path from expolygon outside? coord_t extra = coord_t(floor(this->_min_spacing * INFILL_OVERLAP_OVER_SPACING + 0.5f)); for (Polylines::iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) { Point *first_point = &it_polyline->points.front(); Point *last_point = &it_polyline->points.back(); if (first_point->y > last_point->y) std::swap(first_point, last_point); first_point->y -= extra; last_point->y += extra; } size_t n_polylines_out_old = polylines_out.size(); // connect lines if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections // offset the expolygon by max(min_spacing/2, extra) ExPolygon expolygon_off; { ExPolygons expolygons_off = offset_ex(expolygon, this->_min_spacing/2); if (! expolygons_off.empty()) { // When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island. assert(expolygons_off.size() == 1); std::swap(expolygon_off, expolygons_off.front()); } } Polylines chained = PolylineCollection::chained_path_from( std::move(polylines), PolylineCollection::leftmost_point(polylines), false); // reverse allowed bool first = true; for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { if (! first) { // Try to connect the lines. Points &pts_end = polylines_out.back().points; const Point &first_point = it_polyline->points.front(); const Point &last_point = pts_end.back(); // Distance in X, Y. const Vector distance = first_point.vector_to(last_point); // TODO: we should also check that both points are on a fill_boundary to avoid // connecting paths on the boundaries of internal regions if (this->_can_connect(std::abs(distance.x), std::abs(distance.y)) && expolygon_off.contains(Line(last_point, first_point))) { // Append the polyline. pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end()); continue; } } // The lines cannot be connected. polylines_out.emplace_back(std::move(*it_polyline)); first = false; } } // paths must be rotated back for (Polylines::iterator it = polylines_out.begin() + n_polylines_out_old; it != polylines_out.end(); ++ it) { // No need to translate, the absolute position is irrelevant. // it->translate(- direction.second.x, - direction.second.y); it->rotate(direction.first); } } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillRectilinear.hpp000066400000000000000000000044631324354444700243100ustar00rootroot00000000000000#ifndef slic3r_FillRectilinear_hpp_ #define slic3r_FillRectilinear_hpp_ #include "../libslic3r.h" #include "FillBase.hpp" namespace Slic3r { class Surface; class FillRectilinear : public Fill { public: virtual Fill* clone() const { return new FillRectilinear(*this); }; virtual ~FillRectilinear() {} protected: virtual void _fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, const std::pair &direction, ExPolygon &expolygon, Polylines &polylines_out); coord_t _min_spacing; coord_t _line_spacing; // distance threshold for allowing the horizontal infill lines to be connected into a continuous path coord_t _diagonal_distance; // only for line infill coord_t _line_oscillation; // Enabled for the grid infill, disabled for the rectilinear and line infill. virtual bool _horizontal_lines() const { return false; } virtual Line _line(int i, coord_t x, coord_t y_min, coord_t y_max) const { return Line(Point(x, y_min), Point(x, y_max)); } virtual bool _can_connect(coord_t dist_X, coord_t dist_Y) { return dist_X <= this->_diagonal_distance && dist_Y <= this->_diagonal_distance; } }; class FillLine : public FillRectilinear { public: virtual ~FillLine() {} protected: virtual Line _line(int i, coord_t x, coord_t y_min, coord_t y_max) const { coord_t osc = (i & 1) ? this->_line_oscillation : 0; return Line(Point(x - osc, y_min), Point(x + osc, y_max)); } virtual bool _can_connect(coord_t dist_X, coord_t dist_Y) { coord_t TOLERANCE = 10 * SCALED_EPSILON; return (dist_X >= (this->_line_spacing - this->_line_oscillation) - TOLERANCE) && (dist_X <= (this->_line_spacing + this->_line_oscillation) + TOLERANCE) && (dist_Y <= this->_diagonal_distance); } }; class FillGrid : public FillRectilinear { public: virtual ~FillGrid() {} protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. virtual float _layer_angle(size_t idx) const { return 0.f; } // Flag for Slic3r::Fill::Rectilinear to fill both directions. virtual bool _horizontal_lines() const { return true; } }; }; // namespace Slic3r #endif // slic3r_FillRectilinear_hpp_ Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillRectilinear2.cpp000066400000000000000000002217441324354444700243700ustar00rootroot00000000000000#include #include #include #include #include #include #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" #include "../Geometry.hpp" #include "../Surface.hpp" #include "FillRectilinear2.hpp" // #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG #ifdef SLIC3R_DEBUG #undef NDEBUG #include "SVG.hpp" #endif #include // We want our version of assert. #include "../libslic3r.h" namespace Slic3r { // Having a segment of a closed polygon, calculate its Euclidian length. // The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop, // therefore the point p1 lies on poly.points[seg1-1], poly.points[seg1] etc. static inline coordf_t segment_length(const Polygon &poly, size_t seg1, const Point &p1, size_t seg2, const Point &p2) { #ifdef SLIC3R_DEBUG // Verify that p1 lies on seg1. This is difficult to verify precisely, // but at least verify, that p1 lies in the bounding box of seg1. for (size_t i = 0; i < 2; ++ i) { size_t seg = (i == 0) ? seg1 : seg2; Point px = (i == 0) ? p1 : p2; Point pa = poly.points[((seg == 0) ? poly.points.size() : seg) - 1]; Point pb = poly.points[seg]; if (pa.x > pb.x) std::swap(pa.x, pb.x); if (pa.y > pb.y) std::swap(pa.y, pb.y); assert(px.x >= pa.x && px.x <= pb.x); assert(px.y >= pa.y && px.y <= pb.y); } #endif /* SLIC3R_DEBUG */ const Point *pPrev = &p1; const Point *pThis = NULL; coordf_t len = 0; if (seg1 <= seg2) { for (size_t i = seg1; i < seg2; ++ i, pPrev = pThis) len += pPrev->distance_to(*(pThis = &poly.points[i])); } else { for (size_t i = seg1; i < poly.points.size(); ++ i, pPrev = pThis) len += pPrev->distance_to(*(pThis = &poly.points[i])); for (size_t i = 0; i < seg2; ++ i, pPrev = pThis) len += pPrev->distance_to(*(pThis = &poly.points[i])); } len += pPrev->distance_to(p2); return len; } // Append a segment of a closed polygon to a polyline. // The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop. // Only insert intermediate points between seg1 and seg2. static inline void polygon_segment_append(Points &out, const Polygon &polygon, size_t seg1, size_t seg2) { if (seg1 == seg2) { // Nothing to append from this segment. } else if (seg1 < seg2) { // Do not append a point pointed to by seg2. out.insert(out.end(), polygon.points.begin() + seg1, polygon.points.begin() + seg2); } else { out.reserve(out.size() + seg2 + polygon.points.size() - seg1); out.insert(out.end(), polygon.points.begin() + seg1, polygon.points.end()); // Do not append a point pointed to by seg2. out.insert(out.end(), polygon.points.begin(), polygon.points.begin() + seg2); } } // Append a segment of a closed polygon to a polyline. // The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop, // but this time the segment is traversed backward. // Only insert intermediate points between seg1 and seg2. static inline void polygon_segment_append_reversed(Points &out, const Polygon &polygon, size_t seg1, size_t seg2) { if (seg1 >= seg2) { out.reserve(seg1 - seg2); for (size_t i = seg1; i > seg2; -- i) out.push_back(polygon.points[i - 1]); } else { // it could be, that seg1 == seg2. In that case, append the complete loop. out.reserve(out.size() + seg2 + polygon.points.size() - seg1); for (size_t i = seg1; i > 0; -- i) out.push_back(polygon.points[i - 1]); for (size_t i = polygon.points.size(); i > seg2; -- i) out.push_back(polygon.points[i - 1]); } } // Intersection point of a vertical line with a polygon segment. class SegmentIntersection { public: SegmentIntersection() : iContour(0), iSegment(0), pos_p(0), pos_q(1), type(UNKNOWN), consumed_vertical_up(false), consumed_perimeter_right(false) {} // Index of a contour in ExPolygonWithOffset, with which this vertical line intersects. size_t iContour; // Index of a segment in iContour, with which this vertical line intersects. size_t iSegment; // y position of the intersection, ratinal number. int64_t pos_p; uint32_t pos_q; coord_t pos() const { // Division rounds both positive and negative down to zero. // Add half of q for an arithmetic rounding effect. int64_t p = pos_p; if (p < 0) p -= int64_t(pos_q>>1); else p += int64_t(pos_q>>1); return coord_t(p / int64_t(pos_q)); } // Kind of intersection. With the original contour, or with the inner offestted contour? // A vertical segment will be at least intersected by OUTER_LOW, OUTER_HIGH, // but it could be intersected with OUTER_LOW, INNER_LOW, INNER_HIGH, OUTER_HIGH, // and there may be more than one pair of INNER_LOW, INNER_HIGH between OUTER_LOW, OUTER_HIGH. enum SegmentIntersectionType { OUTER_LOW = 0, OUTER_HIGH = 1, INNER_LOW = 2, INNER_HIGH = 3, UNKNOWN = -1 }; SegmentIntersectionType type; // Was this segment along the y axis consumed? // Up means up along the vertical segment. bool consumed_vertical_up; // Was a segment of the inner perimeter contour consumed? // Right means right from the vertical segment. bool consumed_perimeter_right; // For the INNER_LOW type, this point may be connected to another INNER_LOW point following a perimeter contour. // For the INNER_HIGH type, this point may be connected to another INNER_HIGH point following a perimeter contour. // If INNER_LOW is connected to INNER_HIGH or vice versa, // one has to make sure the vertical infill line does not overlap with the connecting perimeter line. bool is_inner() const { return type == INNER_LOW || type == INNER_HIGH; } bool is_outer() const { return type == OUTER_LOW || type == OUTER_HIGH; } bool is_low () const { return type == INNER_LOW || type == OUTER_LOW; } bool is_high () const { return type == INNER_HIGH || type == OUTER_HIGH; } // Compare two y intersection points given by rational numbers. // Note that the rational number is given as pos_p/pos_q, where pos_p is int64 and pos_q is uint32. // This function calculates pos_p * other.pos_q < other.pos_p * pos_q as a 48bit number. // We don't use 128bit intrinsic data types as these are usually not supported by 32bit compilers and // we don't need the full 128bit precision anyway. bool operator<(const SegmentIntersection &other) const { assert(pos_q > 0); assert(other.pos_q > 0); if (pos_p == 0 || other.pos_p == 0) { // Because the denominators are positive and one of the nominators is zero, // following simple statement holds. return pos_p < other.pos_p; } else { // None of the nominators is zero. int sign1 = (pos_p > 0) ? 1 : -1; int sign2 = (other.pos_p > 0) ? 1 : -1; int signs = sign1 * sign2; assert(signs == 1 || signs == -1); if (signs < 0) { // The nominators have different signs. return sign1 < 0; } else { // The nominators have the same sign. // Absolute values uint64_t p1, p2; if (sign1 > 0) { p1 = uint64_t(pos_p); p2 = uint64_t(other.pos_p); } else { p1 = uint64_t(- pos_p); p2 = uint64_t(- other.pos_p); }; // Multiply low and high 32bit words of p1 by other_pos.q // 32bit x 32bit => 64bit // l_hi and l_lo overlap by 32 bits. uint64_t l_hi = (p1 >> 32) * uint64_t(other.pos_q); uint64_t l_lo = (p1 & 0xffffffffll) * uint64_t(other.pos_q); l_hi += (l_lo >> 32); uint64_t r_hi = (p2 >> 32) * uint64_t(pos_q); uint64_t r_lo = (p2 & 0xffffffffll) * uint64_t(pos_q); r_hi += (r_lo >> 32); // Compare the high 64 bits. if (l_hi == r_hi) { // Compare the low 32 bits. l_lo &= 0xffffffffll; r_lo &= 0xffffffffll; return (sign1 < 0) ? (l_lo > r_lo) : (l_lo < r_lo); } return (sign1 < 0) ? (l_hi > r_hi) : (l_hi < r_hi); } } } bool operator==(const SegmentIntersection &other) const { assert(pos_q > 0); assert(other.pos_q > 0); if (pos_p == 0 || other.pos_p == 0) { // Because the denominators are positive and one of the nominators is zero, // following simple statement holds. return pos_p == other.pos_p; } // None of the nominators is zero, none of the denominators is zero. bool positive = pos_p > 0; if (positive != (other.pos_p > 0)) return false; // The nominators have the same sign. // Absolute values uint64_t p1 = positive ? uint64_t(pos_p) : uint64_t(- pos_p); uint64_t p2 = positive ? uint64_t(other.pos_p) : uint64_t(- other.pos_p); // Multiply low and high 32bit words of p1 by other_pos.q // 32bit x 32bit => 64bit // l_hi and l_lo overlap by 32 bits. uint64_t l_lo = (p1 & 0xffffffffll) * uint64_t(other.pos_q); uint64_t r_lo = (p2 & 0xffffffffll) * uint64_t(pos_q); if (l_lo != r_lo) return false; uint64_t l_hi = (p1 >> 32) * uint64_t(other.pos_q); uint64_t r_hi = (p2 >> 32) * uint64_t(pos_q); return l_hi + (l_lo >> 32) == r_hi + (r_lo >> 32); } }; // A vertical line with intersection points with polygons. class SegmentedIntersectionLine { public: // Index of this vertical intersection line. size_t idx; // x position of this vertical intersection line. coord_t pos; // List of intersection points with polygons, sorted increasingly by the y axis. std::vector intersections; }; // A container maintaining an expolygon with its inner offsetted polygon. // The purpose of the inner offsetted polygon is to provide segments to connect the infill lines. struct ExPolygonWithOffset { public: ExPolygonWithOffset( const ExPolygon &expolygon, float angle, coord_t aoffset1, coord_t aoffset2) { // Copy and rotate the source polygons. polygons_src = expolygon; polygons_src.contour.rotate(angle); for (Polygons::iterator it = polygons_src.holes.begin(); it != polygons_src.holes.end(); ++ it) it->rotate(angle); double mitterLimit = 3.; // for the infill pattern, don't cut the corners. // default miterLimt = 3 //double mitterLimit = 10.; assert(aoffset1 < 0); assert(aoffset2 < 0); assert(aoffset2 < aoffset1); bool sticks_removed = remove_sticks(polygons_src); // if (sticks_removed) printf("Sticks removed!\n"); polygons_outer = offset(polygons_src, aoffset1, ClipperLib::jtMiter, mitterLimit); polygons_inner = offset(polygons_outer, aoffset2 - aoffset1, ClipperLib::jtMiter, mitterLimit); // Filter out contours with zero area or small area, contours with 2 points only. const double min_area_threshold = 0.01 * aoffset2 * aoffset2; remove_small(polygons_outer, min_area_threshold); remove_small(polygons_inner, min_area_threshold); remove_sticks(polygons_outer); remove_sticks(polygons_inner); n_contours_outer = polygons_outer.size(); n_contours_inner = polygons_inner.size(); n_contours = n_contours_outer + n_contours_inner; polygons_ccw.assign(n_contours, false); for (size_t i = 0; i < n_contours; ++ i) { contour(i).remove_duplicate_points(); assert(! contour(i).has_duplicate_points()); polygons_ccw[i] = Slic3r::Geometry::is_ccw(contour(i)); } } // Any contour with offset1 bool is_contour_outer(size_t idx) const { return idx < n_contours_outer; } // Any contour with offset2 bool is_contour_inner(size_t idx) const { return idx >= n_contours_outer; } const Polygon& contour(size_t idx) const { return is_contour_outer(idx) ? polygons_outer[idx] : polygons_inner[idx - n_contours_outer]; } Polygon& contour(size_t idx) { return is_contour_outer(idx) ? polygons_outer[idx] : polygons_inner[idx - n_contours_outer]; } bool is_contour_ccw(size_t idx) const { return polygons_ccw[idx]; } BoundingBox bounding_box_src() const { return get_extents(polygons_src); } BoundingBox bounding_box_outer() const { return get_extents(polygons_outer); } BoundingBox bounding_box_inner() const { return get_extents(polygons_inner); } #ifdef SLIC3R_DEBUG void export_to_svg(Slic3r::SVG &svg) { svg.draw_outline(polygons_src, "black"); svg.draw_outline(polygons_outer, "green"); svg.draw_outline(polygons_inner, "brown"); } #endif /* SLIC3R_DEBUG */ ExPolygon polygons_src; Polygons polygons_outer; Polygons polygons_inner; size_t n_contours_outer; size_t n_contours_inner; size_t n_contours; protected: // For each polygon of polygons_inner, remember its orientation. std::vector polygons_ccw; }; static inline int distance_of_segmens(const Polygon &poly, size_t seg1, size_t seg2, bool forward) { int d = int(seg2) - int(seg1); if (! forward) d = - d; if (d < 0) d += int(poly.points.size()); return d; } // For a vertical line, an inner contour and an intersection point, // find an intersection point on the previous resp. next vertical line. // The intersection point is connected with the prev resp. next intersection point with iInnerContour. // Return -1 if there is no such point on the previous resp. next vertical line. static inline int intersection_on_prev_next_vertical_line( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, bool dir_is_next) { size_t iVerticalLineOther = iVerticalLine; if (dir_is_next) { if (++ iVerticalLineOther == segs.size()) // No successive vertical line. return -1; } else if (iVerticalLineOther -- == 0) { // No preceding vertical line. return -1; } const SegmentedIntersectionLine &il = segs[iVerticalLine]; const SegmentIntersection &itsct = il.intersections[iIntersection]; const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther]; const Polygon &poly = poly_with_offset.contour(iInnerContour); // const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour); const bool forward = itsct.is_low() == dir_is_next; // Resulting index of an intersection point on il2. int out = -1; // Find an intersection point on iVerticalLineOther, intersecting iInnerContour // at the same orientation as iIntersection, and being closest to iIntersection // in the number of contour segments, when following the direction of the contour. int dmin = std::numeric_limits::max(); for (size_t i = 0; i < il2.intersections.size(); ++ i) { const SegmentIntersection &itsct2 = il2.intersections[i]; if (itsct.iContour == itsct2.iContour && itsct.type == itsct2.type) { /* if (itsct.is_low()) { assert(itsct.type == SegmentIntersection::INNER_LOW); assert(iIntersection > 0); assert(il.intersections[iIntersection-1].type == SegmentIntersection::OUTER_LOW); assert(i > 0); if (il2.intersections[i-1].is_inner()) // Take only the lowest inner intersection point. continue; assert(il2.intersections[i-1].type == SegmentIntersection::OUTER_LOW); } else { assert(itsct.type == SegmentIntersection::INNER_HIGH); assert(iIntersection+1 < il.intersections.size()); assert(il.intersections[iIntersection+1].type == SegmentIntersection::OUTER_HIGH); assert(i+1 < il2.intersections.size()); if (il2.intersections[i+1].is_inner()) // Take only the highest inner intersection point. continue; assert(il2.intersections[i+1].type == SegmentIntersection::OUTER_HIGH); } */ // The intersection points lie on the same contour and have the same orientation. // Find the intersection point with a shortest path in the direction of the contour. int d = distance_of_segmens(poly, itsct.iSegment, itsct2.iSegment, forward); if (d < dmin) { out = i; dmin = d; } } } //FIXME this routine is not asymptotic optimal, it will be slow if there are many intersection points along the line. return out; } static inline int intersection_on_prev_vertical_line( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection) { return intersection_on_prev_next_vertical_line(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, false); } static inline int intersection_on_next_vertical_line( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection) { return intersection_on_prev_next_vertical_line(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, true); } enum IntersectionTypeOtherVLine { // There is no connection point on the other vertical line. INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED = -1, // Connection point on the other vertical segment was found // and it could be followed. INTERSECTION_TYPE_OTHER_VLINE_OK = 0, // The connection segment connects to a middle of a vertical segment. // Cannot follow. INTERSECTION_TYPE_OTHER_VLINE_INNER, // Cannot extend the contor to this intersection point as either the connection segment // or the succeeding vertical segment were already consumed. INTERSECTION_TYPE_OTHER_VLINE_CONSUMED, // Not the first intersection along the contor. This intersection point // has been preceded by an intersection point along the vertical line. INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST, }; // Find an intersection on a previous line, but return -1, if the connecting segment of a perimeter was already extruded. static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical_line( const std::vector &segs, size_t iVerticalLine, size_t iIntersection, size_t iIntersectionOther, bool dir_is_next) { // This routine will propose a connecting line even if the connecting perimeter segment intersects // iVertical line multiple times before reaching iIntersectionOther. if (iIntersectionOther == -1) return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED; assert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0)); const SegmentedIntersectionLine &il_this = segs[iVerticalLine]; const SegmentIntersection &itsct_this = il_this.intersections[iIntersection]; const SegmentedIntersectionLine &il_other = segs[dir_is_next ? (iVerticalLine+1) : (iVerticalLine-1)]; const SegmentIntersection &itsct_other = il_other.intersections[iIntersectionOther]; assert(itsct_other.is_inner()); assert(iIntersectionOther > 0); assert(iIntersectionOther + 1 < il_other.intersections.size()); // Is iIntersectionOther at the boundary of a vertical segment? const SegmentIntersection &itsct_other2 = il_other.intersections[itsct_other.is_low() ? iIntersectionOther - 1 : iIntersectionOther + 1]; if (itsct_other2.is_inner()) // Cannot follow a perimeter segment into the middle of another vertical segment. // Only perimeter segments connecting to the end of a vertical segment are followed. return INTERSECTION_TYPE_OTHER_VLINE_INNER; assert(itsct_other.is_low() == itsct_other2.is_low()); if (dir_is_next ? itsct_this.consumed_perimeter_right : itsct_other.consumed_perimeter_right) // This perimeter segment was already consumed. return INTERSECTION_TYPE_OTHER_VLINE_CONSUMED; if (itsct_other.is_low() ? itsct_other.consumed_vertical_up : il_other.intersections[iIntersectionOther-1].consumed_vertical_up) // This vertical segment was already consumed. return INTERSECTION_TYPE_OTHER_VLINE_CONSUMED; return INTERSECTION_TYPE_OTHER_VLINE_OK; } static inline IntersectionTypeOtherVLine intersection_type_on_prev_vertical_line( const std::vector &segs, size_t iVerticalLine, size_t iIntersection, size_t iIntersectionPrev) { return intersection_type_on_prev_next_vertical_line(segs, iVerticalLine, iIntersection, iIntersectionPrev, false); } static inline IntersectionTypeOtherVLine intersection_type_on_next_vertical_line( const std::vector &segs, size_t iVerticalLine, size_t iIntersection, size_t iIntersectionNext) { return intersection_type_on_prev_next_vertical_line(segs, iVerticalLine, iIntersection, iIntersectionNext, true); } // Measure an Euclidian length of a perimeter segment when going from iIntersection to iIntersection2. static inline coordf_t measure_perimeter_prev_next_segment_length( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, size_t iIntersection2, bool dir_is_next) { size_t iVerticalLineOther = iVerticalLine; if (dir_is_next) { if (++ iVerticalLineOther == segs.size()) // No successive vertical line. return coordf_t(-1); } else if (iVerticalLineOther -- == 0) { // No preceding vertical line. return coordf_t(-1); } const SegmentedIntersectionLine &il = segs[iVerticalLine]; const SegmentIntersection &itsct = il.intersections[iIntersection]; const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther]; const SegmentIntersection &itsct2 = il2.intersections[iIntersection2]; const Polygon &poly = poly_with_offset.contour(iInnerContour); // const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour); assert(itsct.type == itsct2.type); assert(itsct.iContour == itsct2.iContour); assert(itsct.is_inner()); const bool forward = itsct.is_low() == dir_is_next; Point p1(il.pos, itsct.pos()); Point p2(il2.pos, itsct2.pos()); return forward ? segment_length(poly, itsct .iSegment, p1, itsct2.iSegment, p2) : segment_length(poly, itsct2.iSegment, p2, itsct .iSegment, p1); } static inline coordf_t measure_perimeter_prev_segment_length( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, size_t iIntersection2) { return measure_perimeter_prev_next_segment_length(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, iIntersection2, false); } static inline coordf_t measure_perimeter_next_segment_length( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, size_t iIntersection2) { return measure_perimeter_prev_next_segment_length(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, iIntersection2, true); } // Append the points of a perimeter segment when going from iIntersection to iIntersection2. // The first point (the point of iIntersection) will not be inserted, // the last point will be inserted. static inline void emit_perimeter_prev_next_segment( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, size_t iIntersection2, Polyline &out, bool dir_is_next) { size_t iVerticalLineOther = iVerticalLine; if (dir_is_next) { ++ iVerticalLineOther; assert(iVerticalLineOther < segs.size()); } else { assert(iVerticalLineOther > 0); -- iVerticalLineOther; } const SegmentedIntersectionLine &il = segs[iVerticalLine]; const SegmentIntersection &itsct = il.intersections[iIntersection]; const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther]; const SegmentIntersection &itsct2 = il2.intersections[iIntersection2]; const Polygon &poly = poly_with_offset.contour(iInnerContour); // const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour); assert(itsct.type == itsct2.type); assert(itsct.iContour == itsct2.iContour); assert(itsct.is_inner()); const bool forward = itsct.is_low() == dir_is_next; // Do not append the first point. // out.points.push_back(Point(il.pos, itsct.pos)); if (forward) polygon_segment_append(out.points, poly, itsct.iSegment, itsct2.iSegment); else polygon_segment_append_reversed(out.points, poly, itsct.iSegment, itsct2.iSegment); // Append the last point. out.points.push_back(Point(il2.pos, itsct2.pos())); } static inline coordf_t measure_perimeter_segment_on_vertical_line_length( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, size_t iIntersection2, bool forward) { const SegmentedIntersectionLine &il = segs[iVerticalLine]; const SegmentIntersection &itsct = il.intersections[iIntersection]; const SegmentIntersection &itsct2 = il.intersections[iIntersection2]; const Polygon &poly = poly_with_offset.contour(iInnerContour); assert(itsct.is_inner()); assert(itsct2.is_inner()); assert(itsct.type != itsct2.type); assert(itsct.iContour == iInnerContour); assert(itsct.iContour == itsct2.iContour); Point p1(il.pos, itsct.pos()); Point p2(il.pos, itsct2.pos()); return forward ? segment_length(poly, itsct .iSegment, p1, itsct2.iSegment, p2) : segment_length(poly, itsct2.iSegment, p2, itsct .iSegment, p1); } // Append the points of a perimeter segment when going from iIntersection to iIntersection2. // The first point (the point of iIntersection) will not be inserted, // the last point will be inserted. static inline void emit_perimeter_segment_on_vertical_line( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, size_t iIntersection2, Polyline &out, bool forward) { const SegmentedIntersectionLine &il = segs[iVerticalLine]; const SegmentIntersection &itsct = il.intersections[iIntersection]; const SegmentIntersection &itsct2 = il.intersections[iIntersection2]; const Polygon &poly = poly_with_offset.contour(iInnerContour); assert(itsct.is_inner()); assert(itsct2.is_inner()); assert(itsct.type != itsct2.type); assert(itsct.iContour == iInnerContour); assert(itsct.iContour == itsct2.iContour); // Do not append the first point. // out.points.push_back(Point(il.pos, itsct.pos)); if (forward) polygon_segment_append(out.points, poly, itsct.iSegment, itsct2.iSegment); else polygon_segment_append_reversed(out.points, poly, itsct.iSegment, itsct2.iSegment); // Append the last point. out.points.push_back(Point(il.pos, itsct2.pos())); } //TBD: For precise infill, measure the area of a slab spanned by an infill line. /* static inline float measure_outer_contour_slab( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t i_vline, size_t iIntersection) { const SegmentedIntersectionLine &il = segs[i_vline]; const SegmentIntersection &itsct = il.intersections[i_vline]; const SegmentIntersection &itsct2 = il.intersections[iIntersection2]; const Polygon &poly = poly_with_offset.contour((itsct.iContour); assert(itsct.is_outer()); assert(itsct2.is_outer()); assert(itsct.type != itsct2.type); assert(itsct.iContour == itsct2.iContour); if (! itsct.is_outer() || ! itsct2.is_outer() || itsct.type == itsct2.type || itsct.iContour != itsct2.iContour) // Error, return zero area. return 0.f; // Find possible connection points on the previous / next vertical line. int iPrev = intersection_on_prev_vertical_line(poly_with_offset, segs, i_vline, itsct.iContour, i_intersection); int iNext = intersection_on_next_vertical_line(poly_with_offset, segs, i_vline, itsct.iContour, i_intersection); // Find possible connection points on the same vertical line. int iAbove = iBelow = -1; // Does the perimeter intersect the current vertical line above intrsctn? for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) if (seg.intersections[i].iContour == itsct.iContour) { iAbove = i; break; } // Does the perimeter intersect the current vertical line below intrsctn? for (int i = int(i_intersection) - 1; i > 0; -- i) if (seg.intersections[i].iContour == itsct.iContour) { iBelow = i; break; } if (iSegAbove != -1 && seg.intersections[iAbove].type == SegmentIntersection::OUTER_HIGH) { // Invalidate iPrev resp. iNext, if the perimeter crosses the current vertical line earlier than iPrev resp. iNext. // The perimeter contour orientation. const Polygon &poly = poly_with_offset.contour(itsct.iContour); { int d_horiz = (iPrev == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, segs[i_vline-1].intersections[iPrev].iSegment, itsct.iSegment, true); int d_down = (iBelow == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, iSegBelow, itsct.iSegment, true); int d_up = (iAbove == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, iSegAbove, itsct.iSegment, true); if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up)) // The vertical crossing comes eralier than the prev crossing. // Disable the perimeter going back. intrsctn_type_prev = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST; if (d_up > std::min(d_horiz, d_down)) // The horizontal crossing comes earlier than the vertical crossing. vert_seg_dir_valid_mask &= ~DIR_BACKWARD; } { int d_horiz = (iNext == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, itsct.iSegment, segs[i_vline+1].intersections[iNext].iSegment, true); int d_down = (iSegBelow == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, itsct.iSegment, iSegBelow, true); int d_up = (iSegAbove == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, itsct.iSegment, iSegAbove, true); if (d_up > std::min(d_horiz, d_down)) // The horizontal crossing comes earlier than the vertical crossing. vert_seg_dir_valid_mask &= ~DIR_FORWARD; } } } */ enum DirectionMask { DIR_FORWARD = 1, DIR_BACKWARD = 2 }; bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out) { // At the end, only the new polylines will be rotated back. size_t n_polylines_out_initial = polylines_out.size(); // Shrink the input polygon a bit first to not push the infill lines out of the perimeters. // const float INFILL_OVERLAP_OVER_SPACING = 0.3f; const float INFILL_OVERLAP_OVER_SPACING = 0.45f; assert(INFILL_OVERLAP_OVER_SPACING > 0 && INFILL_OVERLAP_OVER_SPACING < 0.5f); // Rotate polygons so that we can work with vertical lines here std::pair rotate_vector = this->_infill_direction(surface); rotate_vector.first += angleBase; assert(params.density > 0.0001f && params.density <= 1.f); coord_t line_spacing = coord_t(scale_(this->spacing) / params.density); // On the polygons of poly_with_offset, the infill lines will be connected. ExPolygonWithOffset poly_with_offset( surface->expolygon, - rotate_vector.first, scale_(this->overlap - (0.5 - INFILL_OVERLAP_OVER_SPACING) * this->spacing), scale_(this->overlap - 0.5 * this->spacing)); if (poly_with_offset.n_contours_inner == 0) { // Not a single infill line fits. //FIXME maybe one shall trigger the gap fill here? return true; } BoundingBox bounding_box = poly_with_offset.bounding_box_src(); // define flow spacing according to requested density if (params.full_infill() && !params.dont_adjust) { line_spacing = this->_adjust_solid_spacing(bounding_box.size().x, line_spacing); this->spacing = unscale(line_spacing); } else { // extend bounding box so that our pattern will be aligned with other layers // Transform the reference point to the rotated coordinate system. Point refpt = rotate_vector.second.rotated(- rotate_vector.first); // _align_to_grid will not work correctly with positive pattern_shift. coord_t pattern_shift_scaled = coord_t(scale_(pattern_shift)) % line_spacing; refpt.x -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); bounding_box.merge(_align_to_grid( bounding_box.min, Point(line_spacing, line_spacing), refpt)); } // Intersect a set of euqally spaced vertical lines wiht expolygon. // n_vlines = ceil(bbox_width / line_spacing) size_t n_vlines = (bounding_box.max.x - bounding_box.min.x + line_spacing - 1) / line_spacing; coord_t x0 = bounding_box.min.x; if (params.full_infill()) x0 += (line_spacing + SCALED_EPSILON) / 2; #ifdef SLIC3R_DEBUG static int iRun = 0; BoundingBox bbox_svg = poly_with_offset.bounding_box_outer(); ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-%d.svg", iRun), bbox_svg); // , scale_(1.)); poly_with_offset.export_to_svg(svg); { ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-initial-%d.svg", iRun), bbox_svg); // , scale_(1.)); poly_with_offset.export_to_svg(svg); } iRun ++; #endif /* SLIC3R_DEBUG */ // For each contour // Allocate storage for the segments. std::vector segs(n_vlines, SegmentedIntersectionLine()); for (size_t i = 0; i < n_vlines; ++ i) { segs[i].idx = i; segs[i].pos = x0 + i * line_spacing; } for (size_t iContour = 0; iContour < poly_with_offset.n_contours; ++ iContour) { const Points &contour = poly_with_offset.contour(iContour).points; if (contour.size() < 2) continue; // For each segment for (size_t iSegment = 0; iSegment < contour.size(); ++ iSegment) { size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1; const Point &p1 = contour[iPrev]; const Point &p2 = contour[iSegment]; // Which of the equally spaced vertical lines is intersected by this segment? coord_t l = p1.x; coord_t r = p2.x; if (l > r) std::swap(l, r); // il, ir are the left / right indices of vertical lines intersecting a segment int il = (l - x0) / line_spacing; while (il * line_spacing + x0 < l) ++ il; il = std::max(int(0), il); int ir = (r - x0 + line_spacing) / line_spacing; while (ir * line_spacing + x0 > r) -- ir; ir = std::min(int(segs.size()) - 1, ir); if (il > ir) // No vertical line intersects this segment. continue; assert(il >= 0 && il < segs.size()); assert(ir >= 0 && ir < segs.size()); for (int i = il; i <= ir; ++ i) { coord_t this_x = segs[i].pos; assert(this_x == i * line_spacing + x0); SegmentIntersection is; is.iContour = iContour; is.iSegment = iSegment; assert(l <= this_x); assert(r >= this_x); // Calculate the intersection position in y axis. x is known. if (p1.x == this_x) { if (p2.x == this_x) { // Ignore strictly vertical segments. continue; } is.pos_p = p1.y; is.pos_q = 1; } else if (p2.x == this_x) { is.pos_p = p2.y; is.pos_q = 1; } else { // First calculate the intersection parameter 't' as a rational number with non negative denominator. if (p2.x > p1.x) { is.pos_p = this_x - p1.x; is.pos_q = p2.x - p1.x; } else { is.pos_p = p1.x - this_x; is.pos_q = p1.x - p2.x; } assert(is.pos_p >= 0 && is.pos_p <= is.pos_q); // Make an intersection point from the 't'. is.pos_p *= int64_t(p2.y - p1.y); is.pos_p += p1.y * int64_t(is.pos_q); } // +-1 to take rounding into account. assert(is.pos() + 1 >= std::min(p1.y, p2.y)); assert(is.pos() <= std::max(p1.y, p2.y) + 1); segs[i].intersections.push_back(is); } } } // Sort the intersections along their segments, specify the intersection types. for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) { SegmentedIntersectionLine &sil = segs[i_seg]; // Sort the intersection points using exact rational arithmetic. std::sort(sil.intersections.begin(), sil.intersections.end()); // Assign the intersection types, remove duplicate or overlapping intersection points. // When a loop vertex touches a vertical line, intersection point is generated for both segments. // If such two segments are oriented equally, then one of them is removed. // Otherwise the vertex is tangential to the vertical line and both segments are removed. // The same rule applies, if the loop is pinched into a single point and this point touches the vertical line: // The loop has a zero vertical size at the vertical line, therefore the intersection point is removed. size_t j = 0; for (size_t i = 0; i < sil.intersections.size(); ++ i) { // What is the orientation of the segment at the intersection point? size_t iContour = sil.intersections[i].iContour; const Points &contour = poly_with_offset.contour(iContour).points; size_t iSegment = sil.intersections[i].iSegment; size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1; coord_t dir = contour[iSegment].x - contour[iPrev].x; bool low = dir > 0; sil.intersections[i].type = poly_with_offset.is_contour_outer(iContour) ? (low ? SegmentIntersection::OUTER_LOW : SegmentIntersection::OUTER_HIGH) : (low ? SegmentIntersection::INNER_LOW : SegmentIntersection::INNER_HIGH); if (j > 0 && sil.intersections[i].iContour == sil.intersections[j-1].iContour) { // Two successive intersection points on a vertical line with the same contour. This may be a special case. if (sil.intersections[i].pos() == sil.intersections[j-1].pos()) { // Two successive segments meet exactly at the vertical line. #ifdef SLIC3R_DEBUG // Verify that the segments of sil.intersections[i] and sil.intersections[j-1] are adjoint. size_t iSegment2 = sil.intersections[j-1].iSegment; size_t iPrev2 = ((iSegment2 == 0) ? contour.size() : iSegment2) - 1; assert(iSegment == iPrev2 || iSegment2 == iPrev); #endif /* SLIC3R_DEBUG */ if (sil.intersections[i].type == sil.intersections[j-1].type) { // Two successive segments of the same direction (both to the right or both to the left) // meet exactly at the vertical line. // Remove the second intersection point. } else { // This is a loop returning to the same point. // It may as well be a vertex of a loop touching this vertical line. // Remove both the lines. -- j; } } else if (sil.intersections[i].type == sil.intersections[j-1].type) { // Two non successive segments of the same direction (both to the right or both to the left) // meet exactly at the vertical line. That means there is a Z shaped path, where the center segment // of the Z shaped path is aligned with this vertical line. // Remove one of the intersection points while maximizing the vertical segment length. if (low) { // Remove the second intersection point, keep the first intersection point. } else { // Remove the first intersection point, keep the second intersection point. sil.intersections[j-1] = sil.intersections[i]; } } else { // Vertical line intersects a contour segment at a general position (not at one of its end points). // or the contour just touches this vertical line with a vertical segment or a sequence of vertical segments. // Keep both intersection points. if (j < i) sil.intersections[j] = sil.intersections[i]; ++ j; } } else { // Vertical line intersects a contour segment at a general position (not at one of its end points). if (j < i) sil.intersections[j] = sil.intersections[i]; ++ j; } } // Shrink the list of intersections, if any of the intersection was removed during the classification. if (j < sil.intersections.size()) sil.intersections.erase(sil.intersections.begin() + j, sil.intersections.end()); } // Verify the segments. If something is wrong, give up. #define ASSERT_OR_RETURN(CONDITION) do { assert(CONDITION); if (! (CONDITION)) return false; } while (0) for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) { SegmentedIntersectionLine &sil = segs[i_seg]; // The intersection points have to be even. ASSERT_OR_RETURN((sil.intersections.size() & 1) == 0); for (size_t i = 0; i < sil.intersections.size();) { // An intersection segment crossing the bigger contour may cross the inner offsetted contour even number of times. ASSERT_OR_RETURN(sil.intersections[i].type == SegmentIntersection::OUTER_LOW); size_t j = i + 1; ASSERT_OR_RETURN(j < sil.intersections.size()); ASSERT_OR_RETURN(sil.intersections[j].type == SegmentIntersection::INNER_LOW || sil.intersections[j].type == SegmentIntersection::OUTER_HIGH); for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++ j) ; ASSERT_OR_RETURN(j < sil.intersections.size()); ASSERT_OR_RETURN((j & 1) == 1); ASSERT_OR_RETURN(sil.intersections[j].type == SegmentIntersection::OUTER_HIGH); ASSERT_OR_RETURN(i + 1 == j || sil.intersections[j - 1].type == SegmentIntersection::INNER_HIGH); i = j + 1; } } #undef ASSERT_OR_RETURN #ifdef SLIC3R_DEBUG // Paint the segments and finalize the SVG file. for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) { SegmentedIntersectionLine &sil = segs[i_seg]; for (size_t i = 0; i < sil.intersections.size();) { size_t j = i + 1; for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++ j) ; if (i + 1 == j) { svg.draw(Line(Point(sil.pos, sil.intersections[i].pos()), Point(sil.pos, sil.intersections[j].pos())), "blue"); } else { svg.draw(Line(Point(sil.pos, sil.intersections[i].pos()), Point(sil.pos, sil.intersections[i+1].pos())), "green"); svg.draw(Line(Point(sil.pos, sil.intersections[i+1].pos()), Point(sil.pos, sil.intersections[j-1].pos())), (j - i + 1 > 4) ? "yellow" : "magenta"); svg.draw(Line(Point(sil.pos, sil.intersections[j-1].pos()), Point(sil.pos, sil.intersections[j].pos())), "green"); } i = j + 1; } } svg.Close(); #endif /* SLIC3R_DEBUG */ // For each outer only chords, measure their maximum distance to the bow of the outer contour. // Mark an outer only chord as consumed, if the distance is low. for (size_t i_vline = 0; i_vline < segs.size(); ++ i_vline) { SegmentedIntersectionLine &seg = segs[i_vline]; for (size_t i_intersection = 0; i_intersection + 1 < seg.intersections.size(); ++ i_intersection) { if (seg.intersections[i_intersection].type == SegmentIntersection::OUTER_LOW && seg.intersections[i_intersection+1].type == SegmentIntersection::OUTER_HIGH) { bool consumed = false; // if (params.full_infill()) { // measure_outer_contour_slab(poly_with_offset, segs, i_vline, i_ntersection); // } else consumed = true; seg.intersections[i_intersection].consumed_vertical_up = consumed; } } } // Now construct a graph. // Find the first point. // Naively one would expect to achieve best results by chaining the paths by the shortest distance, // but that procedure does not create the longest continuous paths. // A simple "sweep left to right" procedure achieves better results. size_t i_vline = 0; size_t i_intersection = size_t(-1); // Follow the line, connect the lines into a graph. // Until no new line could be added to the output path: Point pointLast; Polyline *polyline_current = NULL; if (! polylines_out.empty()) pointLast = polylines_out.back().points.back(); for (;;) { if (i_intersection == size_t(-1)) { // The path has been interrupted. Find a next starting point, closest to the previous extruder position. coordf_t dist2min = std::numeric_limits().max(); for (size_t i_vline2 = 0; i_vline2 < segs.size(); ++ i_vline2) { const SegmentedIntersectionLine &seg = segs[i_vline2]; if (! seg.intersections.empty()) { assert(seg.intersections.size() > 1); // Even number of intersections with the loops. assert((seg.intersections.size() & 1) == 0); assert(seg.intersections.front().type == SegmentIntersection::OUTER_LOW); for (size_t i = 0; i < seg.intersections.size(); ++ i) { const SegmentIntersection &intrsctn = seg.intersections[i]; if (intrsctn.is_outer()) { assert(intrsctn.is_low() || i > 0); bool consumed = intrsctn.is_low() ? intrsctn.consumed_vertical_up : seg.intersections[i-1].consumed_vertical_up; if (! consumed) { coordf_t dist2 = sqr(coordf_t(pointLast.x - seg.pos)) + sqr(coordf_t(pointLast.y - intrsctn.pos())); if (dist2 < dist2min) { dist2min = dist2; i_vline = i_vline2; i_intersection = i; //FIXME We are taking the first left point always. Verify, that the caller chains the paths // by a shortest distance, while reversing the paths if needed. //if (polylines_out.empty()) // Initial state, take the first line, which is the first from the left. goto found; } } } } } } if (i_intersection == size_t(-1)) // We are finished. break; found: // Start a new path. polylines_out.push_back(Polyline()); polyline_current = &polylines_out.back(); // Emit the first point of a path. pointLast = Point(segs[i_vline].pos, segs[i_vline].intersections[i_intersection].pos()); polyline_current->points.push_back(pointLast); } // From the initial point (i_vline, i_intersection), follow a path. SegmentedIntersectionLine &seg = segs[i_vline]; SegmentIntersection *intrsctn = &seg.intersections[i_intersection]; bool going_up = intrsctn->is_low(); bool try_connect = false; if (going_up) { assert(! intrsctn->consumed_vertical_up); assert(i_intersection + 1 < seg.intersections.size()); // Step back to the beginning of the vertical segment to mark it as consumed. if (intrsctn->is_inner()) { assert(i_intersection > 0); -- intrsctn; -- i_intersection; } // Consume the complete vertical segment up to the outer contour. do { intrsctn->consumed_vertical_up = true; ++ intrsctn; ++ i_intersection; assert(i_intersection < seg.intersections.size()); } while (intrsctn->type != SegmentIntersection::OUTER_HIGH); if ((intrsctn - 1)->is_inner()) { // Step back. -- intrsctn; -- i_intersection; assert(intrsctn->type == SegmentIntersection::INNER_HIGH); try_connect = true; } } else { // Going down. assert(intrsctn->is_high()); assert(i_intersection > 0); assert(! (intrsctn - 1)->consumed_vertical_up); // Consume the complete vertical segment up to the outer contour. if (intrsctn->is_inner()) intrsctn->consumed_vertical_up = true; do { assert(i_intersection > 0); -- intrsctn; -- i_intersection; intrsctn->consumed_vertical_up = true; } while (intrsctn->type != SegmentIntersection::OUTER_LOW); if ((intrsctn + 1)->is_inner()) { // Step back. ++ intrsctn; ++ i_intersection; assert(intrsctn->type == SegmentIntersection::INNER_LOW); try_connect = true; } } if (try_connect) { // Decide, whether to finish the segment, or whether to follow the perimeter. // 1) Find possible connection points on the previous / next vertical line. int iPrev = intersection_on_prev_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection); int iNext = intersection_on_next_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection); IntersectionTypeOtherVLine intrsctn_type_prev = intersection_type_on_prev_vertical_line(segs, i_vline, i_intersection, iPrev); IntersectionTypeOtherVLine intrsctn_type_next = intersection_type_on_next_vertical_line(segs, i_vline, i_intersection, iNext); // 2) Find possible connection points on the same vertical line. int iAbove = -1; int iBelow = -1; int iSegAbove = -1; int iSegBelow = -1; { SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; // Does the perimeter intersect the current vertical line above intrsctn? for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) // if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) { if (seg.intersections[i].iContour == intrsctn->iContour) { iAbove = i; iSegAbove = seg.intersections[i].iSegment; break; } // Does the perimeter intersect the current vertical line below intrsctn? for (size_t i = i_intersection - 1; i > 0; -- i) // if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) { if (seg.intersections[i].iContour == intrsctn->iContour) { iBelow = i; iSegBelow = seg.intersections[i].iSegment; break; } } // 3) Sort the intersection points, clear iPrev / iNext / iSegBelow / iSegAbove, // if it is preceded by any other intersection point along the contour. unsigned int vert_seg_dir_valid_mask = (going_up ? (iSegAbove != -1 && seg.intersections[iAbove].type == SegmentIntersection::INNER_LOW) : (iSegBelow != -1 && seg.intersections[iBelow].type == SegmentIntersection::INNER_HIGH)) ? (DIR_FORWARD | DIR_BACKWARD) : 0; { // Invalidate iPrev resp. iNext, if the perimeter crosses the current vertical line earlier than iPrev resp. iNext. // The perimeter contour orientation. const bool forward = intrsctn->is_low(); // == poly_with_offset.is_contour_ccw(intrsctn->iContour); const Polygon &poly = poly_with_offset.contour(intrsctn->iContour); { int d_horiz = (iPrev == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, segs[i_vline-1].intersections[iPrev].iSegment, intrsctn->iSegment, forward); int d_down = (iSegBelow == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, iSegBelow, intrsctn->iSegment, forward); int d_up = (iSegAbove == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, iSegAbove, intrsctn->iSegment, forward); if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up)) // The vertical crossing comes eralier than the prev crossing. // Disable the perimeter going back. intrsctn_type_prev = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST; if (going_up ? (d_up > std::min(d_horiz, d_down)) : (d_down > std::min(d_horiz, d_up))) // The horizontal crossing comes earlier than the vertical crossing. vert_seg_dir_valid_mask &= ~(forward ? DIR_BACKWARD : DIR_FORWARD); } { int d_horiz = (iNext == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, intrsctn->iSegment, segs[i_vline+1].intersections[iNext].iSegment, forward); int d_down = (iSegBelow == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, intrsctn->iSegment, iSegBelow, forward); int d_up = (iSegAbove == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, intrsctn->iSegment, iSegAbove, forward); if (intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up)) // The vertical crossing comes eralier than the prev crossing. // Disable the perimeter going forward. intrsctn_type_next = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST; if (going_up ? (d_up > std::min(d_horiz, d_down)) : (d_down > std::min(d_horiz, d_up))) // The horizontal crossing comes earlier than the vertical crossing. vert_seg_dir_valid_mask &= ~(forward ? DIR_FORWARD : DIR_BACKWARD); } } // 4) Try to connect to a previous or next vertical line, making a zig-zag pattern. if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK || intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK) { coordf_t distPrev = (intrsctn_type_prev != INTERSECTION_TYPE_OTHER_VLINE_OK) ? std::numeric_limits::max() : measure_perimeter_prev_segment_length(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iPrev); coordf_t distNext = (intrsctn_type_next != INTERSECTION_TYPE_OTHER_VLINE_OK) ? std::numeric_limits::max() : measure_perimeter_next_segment_length(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext); // Take the shorter path. //FIXME this may not be always the best strategy to take the shortest connection line now. bool take_next = (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK) ? (distNext < distPrev) : intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK; assert(intrsctn->is_inner()); bool skip = params.dont_connect || (link_max_length > 0 && (take_next ? distNext : distPrev) > link_max_length); if (skip) { // Just skip the connecting contour and start a new path. goto dont_connect; polyline_current->points.push_back(Point(seg.pos, intrsctn->pos())); polylines_out.push_back(Polyline()); polyline_current = &polylines_out.back(); const SegmentedIntersectionLine &il2 = segs[take_next ? (i_vline + 1) : (i_vline - 1)]; polyline_current->points.push_back(Point(il2.pos, il2.intersections[take_next ? iNext : iPrev].pos())); } else { polyline_current->points.push_back(Point(seg.pos, intrsctn->pos())); emit_perimeter_prev_next_segment(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, take_next ? iNext : iPrev, *polyline_current, take_next); } // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed. if (iPrev != -1) segs[i_vline-1].intersections[iPrev].consumed_perimeter_right = true; if (iNext != -1) intrsctn->consumed_perimeter_right = true; //FIXME consume the left / right connecting segments at the other end of this line? Currently it is not critical because a perimeter segment is not followed if the vertical segment at the other side has already been consumed. // Advance to the neighbor line. if (take_next) { ++ i_vline; i_intersection = iNext; } else { -- i_vline; i_intersection = iPrev; } continue; } // 5) Try to connect to a previous or next point on the same vertical line. if (vert_seg_dir_valid_mask) { bool valid = true; // Verify, that there is no intersection with the inner contour up to the end of the contour segment. // Verify, that the successive segment has not been consumed yet. if (going_up) { if (seg.intersections[iAbove].consumed_vertical_up) { valid = false; } else { for (int i = (int)i_intersection + 1; i < iAbove && valid; ++i) if (seg.intersections[i].is_inner()) valid = false; } } else { if (seg.intersections[iBelow-1].consumed_vertical_up) { valid = false; } else { for (int i = iBelow + 1; i < (int)i_intersection && valid; ++i) if (seg.intersections[i].is_inner()) valid = false; } } if (valid) { const Polygon &poly = poly_with_offset.contour(intrsctn->iContour); int iNext = going_up ? iAbove : iBelow; int iSegNext = going_up ? iSegAbove : iSegBelow; bool dir_forward = (vert_seg_dir_valid_mask == (DIR_FORWARD | DIR_BACKWARD)) ? // Take the shorter length between the current and the next intersection point. (distance_of_segmens(poly, intrsctn->iSegment, iSegNext, true) < distance_of_segmens(poly, intrsctn->iSegment, iSegNext, false)) : (vert_seg_dir_valid_mask == DIR_FORWARD); // Skip this perimeter line? bool skip = params.dont_connect; if (! skip && link_max_length > 0) { coordf_t link_length = measure_perimeter_segment_on_vertical_line_length( poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, dir_forward); skip = link_length > link_max_length; } polyline_current->points.push_back(Point(seg.pos, intrsctn->pos())); if (skip) { // Just skip the connecting contour and start a new path. polylines_out.push_back(Polyline()); polyline_current = &polylines_out.back(); polyline_current->points.push_back(Point(seg.pos, seg.intersections[iNext].pos())); } else { // Consume the connecting contour and the next segment. emit_perimeter_segment_on_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, *polyline_current, dir_forward); } // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed. // If there are any outer intersection points skipped (bypassed) by the contour, // mark them as processed. if (going_up) { for (int i = (int)i_intersection; i < iAbove; ++ i) seg.intersections[i].consumed_vertical_up = true; } else { for (int i = iBelow; i < (int)i_intersection; ++ i) seg.intersections[i].consumed_vertical_up = true; } // seg.intersections[going_up ? i_intersection : i_intersection - 1].consumed_vertical_up = true; intrsctn->consumed_perimeter_right = true; i_intersection = iNext; if (going_up) ++ intrsctn; else -- intrsctn; intrsctn->consumed_perimeter_right = true; continue; } } dont_connect: // No way to continue the current polyline. Take the rest of the line up to the outer contour. // This will finish the polyline, starting another polyline at a new point. if (going_up) ++ intrsctn; else -- intrsctn; } // Finish the current vertical line, // reset the current vertical line to pick a new starting point in the next round. assert(intrsctn->is_outer()); assert(intrsctn->is_high() == going_up); pointLast = Point(seg.pos, intrsctn->pos()); polyline_current->points.push_back(pointLast); // Handle duplicate points and zero length segments. polyline_current->remove_duplicate_points(); assert(! polyline_current->has_duplicate_points()); // Handle nearly zero length edges. if (polyline_current->points.size() <= 1 || (polyline_current->points.size() == 2 && std::abs(polyline_current->points.front().x - polyline_current->points.back().x) < SCALED_EPSILON && std::abs(polyline_current->points.front().y - polyline_current->points.back().y) < SCALED_EPSILON)) polylines_out.pop_back(); intrsctn = NULL; i_intersection = -1; polyline_current = NULL; } #ifdef SLIC3R_DEBUG { { ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-final-%03d.svg", iRun), bbox_svg); // , scale_(1.)); poly_with_offset.export_to_svg(svg); for (size_t i = n_polylines_out_initial; i < polylines_out.size(); ++ i) svg.draw(polylines_out[i].lines(), "black"); } // Paint a picture per polyline. This makes it easier to discover the order of the polylines and their overlap. for (size_t i_polyline = n_polylines_out_initial; i_polyline < polylines_out.size(); ++ i_polyline) { ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-final-%03d-%03d.svg", iRun, i_polyline), bbox_svg); // , scale_(1.)); svg.draw(polylines_out[i_polyline].lines(), "black"); } } #endif /* SLIC3R_DEBUG */ // paths must be rotated back for (Polylines::iterator it = polylines_out.begin() + n_polylines_out_initial; it != polylines_out.end(); ++ it) { // No need to translate, the absolute position is irrelevant. // it->translate(- rotate_vector.second.x, - rotate_vector.second.y); assert(! it->has_duplicate_points()); it->rotate(rotate_vector.first); //FIXME rather simplify the paths to avoid very short edges? //assert(! it->has_duplicate_points()); it->remove_duplicate_points(); } #ifdef SLIC3R_DEBUG // Verify, that there are no duplicate points in the sequence. for (Polyline &polyline : polylines_out) assert(! polyline.has_duplicate_points()); #endif /* SLIC3R_DEBUG */ return true; } Polylines FillRectilinear2::fill_surface(const Surface *surface, const FillParams ¶ms) { Polylines polylines_out; if (! fill_surface_by_lines(surface, params, 0.f, 0.f, polylines_out)) { printf("FillRectilinear2::fill_surface() failed to fill a region.\n"); } return polylines_out; } Polylines FillGrid2::fill_surface(const Surface *surface, const FillParams ¶ms) { // Each linear fill covers half of the target coverage. FillParams params2 = params; params2.density *= 0.5f; Polylines polylines_out; if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out) || ! fill_surface_by_lines(surface, params2, float(M_PI / 2.), 0.f, polylines_out)) { printf("FillGrid2::fill_surface() failed to fill a region.\n"); } return polylines_out; } Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams ¶ms) { // Each linear fill covers 1/3 of the target coverage. FillParams params2 = params; params2.density *= 0.333333333f; Polylines polylines_out; if (! fill_surface_by_lines(surface, params2, 0.f, 0., polylines_out) || ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), 0., polylines_out) || ! fill_surface_by_lines(surface, params2, float(2. * M_PI / 3.), 0., polylines_out)) { printf("FillTriangles::fill_surface() failed to fill a region.\n"); } return polylines_out; } Polylines FillStars::fill_surface(const Surface *surface, const FillParams ¶ms) { // Each linear fill covers 1/3 of the target coverage. FillParams params2 = params; params2.density *= 0.333333333f; Polylines polylines_out; if (! fill_surface_by_lines(surface, params2, 0.f, 0., polylines_out) || ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), 0., polylines_out) || ! fill_surface_by_lines(surface, params2, float(2. * M_PI / 3.), 0.5 * this->spacing / params2.density, polylines_out)) { printf("FillStars::fill_surface() failed to fill a region.\n"); } return polylines_out; } Polylines FillCubic::fill_surface(const Surface *surface, const FillParams ¶ms) { // Each linear fill covers 1/3 of the target coverage. FillParams params2 = params; params2.density *= 0.333333333f; Polylines polylines_out; coordf_t dx = sqrt(0.5) * z; if (! fill_surface_by_lines(surface, params2, 0.f, dx, polylines_out) || ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), - dx, polylines_out) || // Rotated by PI*2/3 + PI to achieve reverse sloping wall. ! fill_surface_by_lines(surface, params2, float(M_PI * 2. / 3.), dx, polylines_out)) { printf("FillCubic::fill_surface() failed to fill a region.\n"); } return polylines_out; } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillRectilinear2.hpp000066400000000000000000000044221324354444700243650ustar00rootroot00000000000000#ifndef slic3r_FillRectilinear2_hpp_ #define slic3r_FillRectilinear2_hpp_ #include "../libslic3r.h" #include "FillBase.hpp" namespace Slic3r { class Surface; class FillRectilinear2 : public Fill { public: virtual Fill* clone() const { return new FillRectilinear2(*this); }; virtual ~FillRectilinear2() {} virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); protected: bool fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out); }; class FillGrid2 : public FillRectilinear2 { public: virtual Fill* clone() const { return new FillGrid2(*this); }; virtual ~FillGrid2() {} virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. virtual float _layer_angle(size_t idx) const { return 0.f; } }; class FillTriangles : public FillRectilinear2 { public: virtual Fill* clone() const { return new FillTriangles(*this); }; virtual ~FillTriangles() {} virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. virtual float _layer_angle(size_t idx) const { return 0.f; } }; class FillStars : public FillRectilinear2 { public: virtual Fill* clone() const { return new FillStars(*this); }; virtual ~FillStars() {} virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. virtual float _layer_angle(size_t idx) const { return 0.f; } }; class FillCubic : public FillRectilinear2 { public: virtual Fill* clone() const { return new FillCubic(*this); }; virtual ~FillCubic() {} virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. virtual float _layer_angle(size_t idx) const { return 0.f; } }; }; // namespace Slic3r #endif // slic3r_FillRectilinear2_hpp_ Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillRectilinear3.cpp000066400000000000000000002446731324354444700243770ustar00rootroot00000000000000#include #include #include #include #include #include #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" #include "../Geometry.hpp" #include "../Surface.hpp" #include "../Int128.hpp" #include "FillRectilinear3.hpp" #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG #ifdef SLIC3R_DEBUG #undef NDEBUG #define DEBUG #define _DEBUG #include "SVG.hpp" #endif #include namespace Slic3r { namespace FillRectilinear3_Internal { // A container maintaining the source expolygon with its inner offsetted polygon. // The source expolygon is offsetted twice: // 1) A tiny offset is used to get a contour, to which the open hatching lines will be extended. // 2) A larger offset is used to get a contor, along which the individual hatching lines will be connected. struct ExPolygonWithOffset { public: ExPolygonWithOffset( const ExPolygon &expolygon, float aoffset1, float aoffset2) { // Copy and rotate the source polygons. polygons_src = expolygon; double mitterLimit = 3.; // for the infill pattern, don't cut the corners. // default miterLimt = 3 //double mitterLimit = 10.; assert(aoffset1 < 0); assert(aoffset2 < 0); assert(aoffset2 < aoffset1); // bool sticks_removed = remove_sticks(polygons_src); // if (sticks_removed) printf("Sticks removed!\n"); polygons_outer = offset(polygons_src, aoffset1, ClipperLib::jtMiter, mitterLimit); polygons_inner = offset(polygons_outer, aoffset2 - aoffset1, ClipperLib::jtMiter, mitterLimit); // Filter out contours with zero area or small area, contours with 2 points only. const double min_area_threshold = 0.01 * aoffset2 * aoffset2; remove_small(polygons_outer, min_area_threshold); remove_small(polygons_inner, min_area_threshold); remove_sticks(polygons_outer); remove_sticks(polygons_inner); n_contours_outer = polygons_outer.size(); n_contours_inner = polygons_inner.size(); n_contours = n_contours_outer + n_contours_inner; polygons_ccw.assign(n_contours, false); for (size_t i = 0; i < n_contours; ++ i) { contour(i).remove_duplicate_points(); assert(! contour(i).has_duplicate_points()); polygons_ccw[i] = Slic3r::Geometry::is_ccw(contour(i)); } } // Any contour with offset1 bool is_contour_outer(size_t idx) const { return idx < n_contours_outer; } // Any contour with offset2 bool is_contour_inner(size_t idx) const { return idx >= n_contours_outer; } const Polygon& contour(size_t idx) const { return is_contour_outer(idx) ? polygons_outer[idx] : polygons_inner[idx - n_contours_outer]; } Polygon& contour(size_t idx) { return is_contour_outer(idx) ? polygons_outer[idx] : polygons_inner[idx - n_contours_outer]; } bool is_contour_ccw(size_t idx) const { return polygons_ccw[idx] != 0; } BoundingBox bounding_box_src() const { return get_extents(polygons_src); } BoundingBox bounding_box_outer() const { return get_extents(polygons_outer); } BoundingBox bounding_box_inner() const { return get_extents(polygons_inner); } #ifdef SLIC3R_DEBUG void export_to_svg(Slic3r::SVG &svg) const { svg.draw_outline(polygons_src, "black"); svg.draw_outline(polygons_outer, "green"); svg.draw_outline(polygons_inner, "brown"); } #endif /* SLIC3R_DEBUG */ ExPolygon polygons_src; Polygons polygons_outer; Polygons polygons_inner; size_t n_contours_outer; size_t n_contours_inner; size_t n_contours; protected: // For each polygon of polygons_inner, remember its orientation. std::vector polygons_ccw; }; class SegmentedIntersectionLine; // Intersection point of a vertical line with a polygon segment. class SegmentIntersection { public: SegmentIntersection() : line(nullptr), expoly_with_offset(nullptr), iContour(0), iSegment(0), type(UNKNOWN), consumed_vertical_up(false), consumed_perimeter_right(false) {} // Parent object owning this intersection point. const SegmentedIntersectionLine *line; // Container with the source expolygon and its shrank copies, to be intersected by the line. const ExPolygonWithOffset *expoly_with_offset; // Index of a contour in ExPolygonWithOffset, with which this vertical line intersects. size_t iContour; // Index of a segment in iContour, with which this vertical line intersects. size_t iSegment; // Kind of intersection. With the original contour, or with the inner offestted contour? // A vertical segment will be at least intersected by OUTER_LOW, OUTER_HIGH, // but it could be intersected with OUTER_LOW, INNER_LOW, INNER_HIGH, OUTER_HIGH, // and there may be more than one pair of INNER_LOW, INNER_HIGH between OUTER_LOW, OUTER_HIGH. enum SegmentIntersectionType { OUTER_LOW = 0, OUTER_HIGH = 1, INNER_LOW = 2, INNER_HIGH = 3, UNKNOWN = -1 }; SegmentIntersectionType type; // For the INNER_LOW type, this point may be connected to another INNER_LOW point following a perimeter contour. // For the INNER_HIGH type, this point may be connected to another INNER_HIGH point following a perimeter contour. // If INNER_LOW is connected to INNER_HIGH or vice versa, // one has to make sure the vertical infill line does not overlap with the connecting perimeter line. bool is_inner() const { return type == INNER_LOW || type == INNER_HIGH; } bool is_outer() const { return type == OUTER_LOW || type == OUTER_HIGH; } bool is_low () const { return type == INNER_LOW || type == OUTER_LOW; } bool is_high () const { return type == INNER_HIGH || type == OUTER_HIGH; } // Calculate a position of this intersection point. The position does not need to be necessary exact. Point pos() const; // Returns 0, if this and other segments intersect at the hatching line. // Returns -1, if this intersection is below the other intersection on the hatching line. // Returns +1 otherwise. int ordering_along_line(const SegmentIntersection &other) const; // Compare two y intersection points given by rational numbers. bool operator< (const SegmentIntersection &other) const; // { return this->ordering_along_line(other) == -1; } bool operator==(const SegmentIntersection &other) const { return this->ordering_along_line(other) == 0; } //FIXME legacy code, suporting the old graph traversal algorithm. Please remove. // Was this segment along the y axis consumed? // Up means up along the vertical segment. bool consumed_vertical_up; // Was a segment of the inner perimeter contour consumed? // Right means right from the vertical segment. bool consumed_perimeter_right; }; // A single hathing line intersecting the ExPolygonWithOffset. class SegmentedIntersectionLine { public: // Index of this vertical intersection line. size_t idx; // Position of the line along the X axis of the oriented bounding box. // coord_t x; // Position of this vertical intersection line, rotated to the world coordinate system. Point pos; // Direction of this vertical intersection line, rotated to the world coordinate system. The direction is not normalized to maintain a sufficient accuracy! Vector dir; // List of intersection points with polygons, sorted increasingly by the y axis. // The SegmentIntersection keeps a pointer to this object to access the start and direction of this line. std::vector intersections; }; // Return an intersection point of the parent SegmentedIntersectionLine with the segment of a parent ExPolygonWithOffset. // The intersected segment of the ExPolygonWithOffset is addressed with (iContour, iSegment). // When calling this method, the SegmentedIntersectionLine must not be parallel with the segment. Point SegmentIntersection::pos() const { // Get the two rays to be intersected. const Polygon &poly = this->expoly_with_offset->contour(this->iContour); // 30 bits + 1 signum bit. const Point &seg_start = poly.points[(this->iSegment == 0) ? poly.points.size() - 1 : this->iSegment - 1]; const Point &seg_end = poly.points[this->iSegment]; // Point, vector of the segment. const Pointf p1 = convert_to(seg_start); const Pointf v1 = convert_to(seg_end - seg_start); // Point, vector of this hatching line. const Pointf p2 = convert_to(line->pos); const Pointf v2 = convert_to(line->dir); // Intersect the two rays. double denom = v1.x * v2.y - v2.x * v1.y; Point out; if (denom == 0.) { // Lines are collinear. As the pos() method is not supposed to be called on collinear vectors, // the source vectors are not quite collinear. Return the center of the contour segment. out = seg_start + seg_end; out.x >>= 1; out.y >>= 1; } else { // Find the intersection point. double t = (v2.x * (p1.y - p2.y) - v2.y * (p1.x - p2.x)) / denom; if (t < 0.) out = seg_start; else if (t > 1.) out = seg_end; else { out.x = coord_t(floor(p1.x + t * v1.x + 0.5)); out.y = coord_t(floor(p1.y + t * v1.y + 0.5)); } } return out; } static inline int signum(int64_t v) { return (v > 0) - (v < 0); } // Returns 0, if this and other segments intersect at the hatching line. // Returns -1, if this intersection is below the other intersection on the hatching line. // Returns +1 otherwise. int SegmentIntersection::ordering_along_line(const SegmentIntersection &other) const { assert(this->line == other.line); assert(this->expoly_with_offset == other.expoly_with_offset); if (this->iContour == other.iContour && this->iSegment == other.iSegment) return true; // Segment of this const Polygon &poly_a = this->expoly_with_offset->contour(this->iContour); // 30 bits + 1 signum bit. const Point &seg_start_a = poly_a.points[(this->iSegment == 0) ? poly_a.points.size() - 1 : this->iSegment - 1]; const Point &seg_end_a = poly_a.points[this->iSegment]; // Segment of other const Polygon &poly_b = this->expoly_with_offset->contour(other.iContour); // 30 bits + 1 signum bit. const Point &seg_start_b = poly_b.points[(other.iSegment == 0) ? poly_b.points.size() - 1 : other.iSegment - 1]; const Point &seg_end_b = poly_b.points[other.iSegment]; if (this->iContour == other.iContour) { if ((this->iSegment + 1) % poly_a.points.size() == other.iSegment) { // other.iSegment succeeds this->iSegment assert(seg_end_a == seg_start_b); // Avoid calling the 128bit x 128bit multiplication below if this->line intersects the common point. if (cross(this->line->dir, seg_end_b - this->line->pos) == 0) return 0; } else if ((other.iSegment + 1) % poly_a.points.size() == this->iSegment) { // this->iSegment succeeds other.iSegment assert(seg_start_a == seg_end_b); // Avoid calling the 128bit x 128bit multiplication below if this->line intersects the common point. if (cross(this->line->dir, seg_start_a - this->line->pos) == 0) return 0; } else { // General case. } } // First test, whether both points of one segment are completely in one half-plane of the other line. const Point vec_b = seg_end_b - seg_start_b; int side_start = signum(cross(vec_b, seg_start_a - seg_start_b)); int side_end = signum(cross(vec_b, seg_end_a - seg_start_b)); int side = side_start * side_end; if (side > 0) // This segment is completely inside one half-plane of the other line, therefore the ordering is trivial. return signum(cross(vec_b, this->line->dir)) * side_start; const Point vec_a = seg_end_a - seg_start_a; int side_start2 = signum(cross(vec_a, seg_start_b - seg_start_a)); int side_end2 = signum(cross(vec_a, seg_end_b - seg_start_a)); int side2 = side_start2 * side_end2; //if (side == 0 && side2 == 0) // The segments share one of their end points. if (side2 > 0) // This segment is completely inside one half-plane of the other line, therefore the ordering is trivial. return signum(cross(this->line->dir, vec_a)) * side_start2; // The two segments intersect and they are not sucessive segments of the same contour. // Ordering of the points depends on the position of the segment intersection (left / right from this->line), // therefore a simple test over the input segment end points is not sufficient. // Find the parameters of intersection of the two segmetns with this->line. int64_t denom1 = cross(this->line->dir, vec_a); int64_t denom2 = cross(this->line->dir, vec_b); Point vx_a = seg_start_a - this->line->pos; Point vx_b = seg_start_b - this->line->pos; int64_t t1_times_denom1 = int64_t(vx_a.x) * int64_t(vec_a.y) - int64_t(vx_a.y) * int64_t(vec_a.x); int64_t t2_times_denom2 = int64_t(vx_b.x) * int64_t(vec_b.y) - int64_t(vx_b.y) * int64_t(vec_b.x); assert(denom1 != 0); assert(denom2 != 0); return Int128::compare_rationals_filtered(t1_times_denom1, denom1, t2_times_denom2, denom2); } // Compare two y intersection points given by rational numbers. bool SegmentIntersection::operator<(const SegmentIntersection &other) const { #ifdef _DEBUG Point p1 = this->pos(); Point p2 = other.pos(); int64_t d = dot(this->line->dir, p2 - p1); #endif /* _DEBUG */ int ordering = this->ordering_along_line(other); #ifdef _DEBUG if (ordering == -1) assert(d >= - int64_t(SCALED_EPSILON)); else if (ordering == 1) assert(d <= int64_t(SCALED_EPSILON)); #endif /* _DEBUG */ return ordering == -1; } // When doing a rectilinear / grid / triangle / stars / cubic infill, // the following class holds the hatching lines of each of the hatching directions. class InfillHatchingSingleDirection { public: // Hatching angle, CCW from the X axis. double angle; // Starting point of the 1st hatching line. Point start_point; // Direction vector, its size is not normalized to maintain a sufficient accuracy! Vector direction; // Spacing of the hatching lines, perpendicular to the direction vector. coord_t line_spacing; // Infill segments oriented at angle. std::vector segs; }; // For the rectilinear, grid, triangles, stars and cubic pattern fill one InfillHatchingSingleDirection structure // for each infill direction. The segments stored in InfillHatchingSingleDirection will then form a graph of candidate // paths to be extruded. static bool prepare_infill_hatching_segments( // Input geometry to be hatch, containing two concentric contours for each input contour. const ExPolygonWithOffset &poly_with_offset, // fill density, dont_adjust const FillParams ¶ms, // angle, pattern_shift, spacing FillRectilinear3::FillDirParams &fill_dir_params, // Reference point of the pattern, to which the infill lines will be alligned, and the base angle. const std::pair &rotate_vector, // Resulting straight segments of the infill graph. InfillHatchingSingleDirection &out) { out.angle = rotate_vector.first + fill_dir_params.angle; out.direction = Point(coord_t(scale_(1000)), coord_t(0)); // Hatch along the Y axis of the rotated coordinate system. out.direction.rotate(out.angle + 0.5 * M_PI); out.segs.clear(); assert(params.density > 0.0001f && params.density <= 1.f); coord_t line_spacing = coord_t(scale_(fill_dir_params.spacing) / params.density); // Bounding box around the source contour, aligned with out.angle. BoundingBox bounding_box = get_extents_rotated(poly_with_offset.polygons_src.contour, - out.angle); // Define the flow spacing according to requested density. if (params.full_infill() && ! params.dont_adjust) { // Full infill, adjust the line spacing to fit an integer number of lines. out.line_spacing = Fill::_adjust_solid_spacing(bounding_box.size().x, line_spacing); // Report back the adjusted line spacing. fill_dir_params.spacing = float(unscale(line_spacing)); } else { // Extend bounding box so that our pattern will be aligned with the other layers. // Transform the reference point to the rotated coordinate system. Point refpt = rotate_vector.second.rotated(- out.angle); // _align_to_grid will not work correctly with positive pattern_shift. coord_t pattern_shift_scaled = coord_t(scale_(fill_dir_params.pattern_shift)) % line_spacing; refpt.x -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); bounding_box.merge(Fill::_align_to_grid( bounding_box.min, Point(line_spacing, line_spacing), refpt)); } // Intersect a set of euqally spaced vertical lines wiht expolygon. // n_vlines = ceil(bbox_width / line_spacing) size_t n_vlines = (bounding_box.max.x - bounding_box.min.x + line_spacing - 1) / line_spacing; coord_t x0 = bounding_box.min.x; if (params.full_infill()) x0 += coord_t((line_spacing + SCALED_EPSILON) / 2); out.line_spacing = line_spacing; out.start_point = Point(x0, bounding_box.min.y); out.start_point.rotate(out.angle); #ifdef SLIC3R_DEBUG static int iRun = 0; BoundingBox bbox_svg = poly_with_offset.bounding_box_outer(); ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-%d.svg", iRun), bbox_svg); // , scale_(1.)); poly_with_offset.export_to_svg(svg); { ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-initial-%d.svg", iRun), bbox_svg); // , scale_(1.)); poly_with_offset.export_to_svg(svg); } iRun ++; #endif /* SLIC3R_DEBUG */ // For each contour // Allocate storage for the segments. out.segs.assign(n_vlines, SegmentedIntersectionLine()); double cos_a = cos(out.angle); double sin_a = sin(out.angle); for (size_t i = 0; i < n_vlines; ++ i) { auto &seg = out.segs[i]; seg.idx = i; // seg.x = x0 + coord_t(i) * line_spacing; coord_t x = x0 + coord_t(i) * line_spacing; seg.pos.x = coord_t(floor(cos_a * x - sin_a * bounding_box.min.y + 0.5)); seg.pos.y = coord_t(floor(cos_a * bounding_box.min.y + sin_a * x + 0.5)); seg.dir = out.direction; } for (size_t iContour = 0; iContour < poly_with_offset.n_contours; ++ iContour) { const Points &contour = poly_with_offset.contour(iContour).points; if (contour.size() < 2) continue; // For each segment for (size_t iSegment = 0; iSegment < contour.size(); ++ iSegment) { size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1; const Point *pl = &contour[iPrev]; const Point *pr = &contour[iSegment]; // Orient the segment to the direction vector. const Point v = *pr - *pl; int orientation = Int128::sign_determinant_2x2_filtered(v.x, v.y, out.direction.x, out.direction.y); if (orientation == 0) // Ignore strictly vertical segments. continue; if (orientation < 0) // Always orient the input segment consistently towards the hatching direction. std::swap(pl, pr); // Which of the equally spaced vertical lines is intersected by this segment? coord_t l = (coord_t)floor(cos_a * pl->x + sin_a * pl->y - SCALED_EPSILON); coord_t r = (coord_t)ceil (cos_a * pr->x + sin_a * pr->y + SCALED_EPSILON); assert(l < r - SCALED_EPSILON); // il, ir are the left / right indices of vertical lines intersecting a segment int il = std::max(0, (l - x0 + line_spacing) / line_spacing); int ir = std::min(int(out.segs.size()) - 1, (r - x0) / line_spacing); // The previous tests were done with floating point arithmetics over an epsilon-extended interval. // Now do the same tests with exact arithmetics over the exact interval. while (il <= ir && Int128::orient(out.segs[il].pos, out.segs[il].pos + out.direction, *pl) < 0) ++ il; while (il <= ir && Int128::orient(out.segs[ir].pos, out.segs[ir].pos + out.direction, *pr) > 0) -- ir; // Here it is ensured, that // 1) out.seg is not parallel to (pl, pr) // 2) all lines from il to ir intersect . assert(il >= 0 && ir < int(out.segs.size())); for (int i = il; i <= ir; ++ i) { // assert(out.segs[i].x == i * line_spacing + x0); // assert(l <= out.segs[i].x); // assert(r >= out.segs[i].x); SegmentIntersection is; is.line = &out.segs[i]; is.expoly_with_offset = &poly_with_offset; is.iContour = iContour; is.iSegment = iSegment; // Test whether the calculated intersection point falls into the bounding box of the input segment. // +-1 to take rounding into account. assert(Int128::orient(out.segs[i].pos, out.segs[i].pos + out.direction, *pl) >= 0); assert(Int128::orient(out.segs[i].pos, out.segs[i].pos + out.direction, *pr) <= 0); assert(is.pos().x + 1 >= std::min(pl->x, pr->x)); assert(is.pos().y + 1 >= std::min(pl->y, pr->y)); assert(is.pos().x <= std::max(pl->x, pr->x) + 1); assert(is.pos().y <= std::max(pl->y, pr->y) + 1); out.segs[i].intersections.push_back(is); } } } // Sort the intersections along their segments, specify the intersection types. for (size_t i_seg = 0; i_seg < out.segs.size(); ++ i_seg) { SegmentedIntersectionLine &sil = out.segs[i_seg]; // Sort the intersection points using exact rational arithmetic. std::sort(sil.intersections.begin(), sil.intersections.end()); #ifdef _DEBUG // Verify that the intersections are sorted along the haching direction. for (size_t i = 1; i < sil.intersections.size(); ++ i) { Point p1 = sil.intersections[i - 1].pos(); Point p2 = sil.intersections[i].pos(); int64_t d = dot(sil.dir, p2 - p1); assert(d >= - int64_t(SCALED_EPSILON)); } #endif /* _DEBUG */ // Assign the intersection types, remove duplicate or overlapping intersection points. // When a loop vertex touches a vertical line, intersection point is generated for both segments. // If such two segments are oriented equally, then one of them is removed. // Otherwise the vertex is tangential to the vertical line and both segments are removed. // The same rule applies, if the loop is pinched into a single point and this point touches the vertical line: // The loop has a zero vertical size at the vertical line, therefore the intersection point is removed. size_t j = 0; for (size_t i = 0; i < sil.intersections.size(); ++ i) { // What is the orientation of the segment at the intersection point? size_t iContour = sil.intersections[i].iContour; const Points &contour = poly_with_offset.contour(iContour).points; size_t iSegment = sil.intersections[i].iSegment; size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1; int dir = Int128::cross(contour[iSegment] - contour[iPrev], sil.dir); bool low = dir > 0; sil.intersections[i].type = poly_with_offset.is_contour_outer(iContour) ? (low ? SegmentIntersection::OUTER_LOW : SegmentIntersection::OUTER_HIGH) : (low ? SegmentIntersection::INNER_LOW : SegmentIntersection::INNER_HIGH); if (j > 0 && sil.intersections[i].iContour == sil.intersections[j-1].iContour) { // Two successive intersection points on a vertical line with the same contour. This may be a special case. if (sil.intersections[i] == sil.intersections[j-1]) { // Two successive segments meet exactly at the vertical line. #ifdef SLIC3R_DEBUG // Verify that the segments of sil.intersections[i] and sil.intersections[j-1] are adjoint. size_t iSegment2 = sil.intersections[j-1].iSegment; size_t iPrev2 = ((iSegment2 == 0) ? contour.size() : iSegment2) - 1; assert(iSegment == iPrev2 || iSegment2 == iPrev); #endif /* SLIC3R_DEBUG */ if (sil.intersections[i].type == sil.intersections[j-1].type) { // Two successive segments of the same direction (both to the right or both to the left) // meet exactly at the vertical line. // Remove the second intersection point. } else { // This is a loop returning to the same point. // It may as well be a vertex of a loop touching this vertical line. // Remove both the lines. -- j; } } else if (sil.intersections[i].type == sil.intersections[j-1].type) { // Two non successive segments of the same direction (both to the right or both to the left) // meet exactly at the vertical line. That means there is a Z shaped path, where the center segment // of the Z shaped path is aligned with this vertical line. // Remove one of the intersection points while maximizing the vertical segment length. if (low) { // Remove the second intersection point, keep the first intersection point. } else { // Remove the first intersection point, keep the second intersection point. sil.intersections[j-1] = sil.intersections[i]; } } else { // Vertical line intersects a contour segment at a general position (not at one of its end points). // or the contour just touches this vertical line with a vertical segment or a sequence of vertical segments. // Keep both intersection points. if (j < i) sil.intersections[j] = sil.intersections[i]; ++ j; } } else { // Vertical line intersects a contour segment at a general position (not at one of its end points). if (j < i) sil.intersections[j] = sil.intersections[i]; ++ j; } } // Shrink the list of intersections, if any of the intersection was removed during the classification. if (j < sil.intersections.size()) sil.intersections.erase(sil.intersections.begin() + j, sil.intersections.end()); } // Verify the segments. If something is wrong, give up. #define ASSERT_OR_RETURN(CONDITION) do { assert(CONDITION); if (! (CONDITION)) return false; } while (0) #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4127) #endif for (size_t i_seg = 0; i_seg < out.segs.size(); ++ i_seg) { SegmentedIntersectionLine &sil = out.segs[i_seg]; // The intersection points have to be even. ASSERT_OR_RETURN((sil.intersections.size() & 1) == 0); for (size_t i = 0; i < sil.intersections.size();) { // An intersection segment crossing the bigger contour may cross the inner offsetted contour even number of times. ASSERT_OR_RETURN(sil.intersections[i].type == SegmentIntersection::OUTER_LOW); size_t j = i + 1; ASSERT_OR_RETURN(j < sil.intersections.size()); ASSERT_OR_RETURN(sil.intersections[j].type == SegmentIntersection::INNER_LOW || sil.intersections[j].type == SegmentIntersection::OUTER_HIGH); for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++ j) ; ASSERT_OR_RETURN(j < sil.intersections.size()); ASSERT_OR_RETURN((j & 1) == 1); ASSERT_OR_RETURN(sil.intersections[j].type == SegmentIntersection::OUTER_HIGH); ASSERT_OR_RETURN(i + 1 == j || sil.intersections[j - 1].type == SegmentIntersection::INNER_HIGH); i = j + 1; } } #undef ASSERT_OR_RETURN #ifdef _MSC_VER #pragma warning(push) #endif /* _MSC_VER */ #ifdef SLIC3R_DEBUG // Paint the segments and finalize the SVG file. for (size_t i_seg = 0; i_seg < out.segs.size(); ++ i_seg) { SegmentedIntersectionLine &sil = out.segs[i_seg]; for (size_t i = 0; i < sil.intersections.size();) { size_t j = i + 1; for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++ j) ; if (i + 1 == j) { svg.draw(Line(sil.intersections[i ].pos(), sil.intersections[j ].pos()), "blue"); } else { svg.draw(Line(sil.intersections[i ].pos(), sil.intersections[i+1].pos()), "green"); svg.draw(Line(sil.intersections[i+1].pos(), sil.intersections[j-1].pos()), (j - i + 1 > 4) ? "yellow" : "magenta"); svg.draw(Line(sil.intersections[j-1].pos(), sil.intersections[j ].pos()), "green"); } i = j + 1; } } svg.Close(); #endif /* SLIC3R_DEBUG */ return true; } /****************************************************************** Legacy code, to be replaced by a graph algorithm ******************************************************************/ // Having a segment of a closed polygon, calculate its Euclidian length. // The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop, // therefore the point p1 lies on poly.points[seg1-1], poly.points[seg1] etc. static inline coordf_t segment_length(const Polygon &poly, size_t seg1, const Point &p1, size_t seg2, const Point &p2) { #ifdef SLIC3R_DEBUG // Verify that p1 lies on seg1. This is difficult to verify precisely, // but at least verify, that p1 lies in the bounding box of seg1. for (size_t i = 0; i < 2; ++ i) { size_t seg = (i == 0) ? seg1 : seg2; Point px = (i == 0) ? p1 : p2; Point pa = poly.points[((seg == 0) ? poly.points.size() : seg) - 1]; Point pb = poly.points[seg]; if (pa.x > pb.x) std::swap(pa.x, pb.x); if (pa.y > pb.y) std::swap(pa.y, pb.y); assert(px.x >= pa.x && px.x <= pb.x); assert(px.y >= pa.y && px.y <= pb.y); } #endif /* SLIC3R_DEBUG */ const Point *pPrev = &p1; const Point *pThis = NULL; coordf_t len = 0; if (seg1 <= seg2) { for (size_t i = seg1; i < seg2; ++ i, pPrev = pThis) len += pPrev->distance_to(*(pThis = &poly.points[i])); } else { for (size_t i = seg1; i < poly.points.size(); ++ i, pPrev = pThis) len += pPrev->distance_to(*(pThis = &poly.points[i])); for (size_t i = 0; i < seg2; ++ i, pPrev = pThis) len += pPrev->distance_to(*(pThis = &poly.points[i])); } len += pPrev->distance_to(p2); return len; } // Append a segment of a closed polygon to a polyline. // The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop. // Only insert intermediate points between seg1 and seg2. static inline void polygon_segment_append(Points &out, const Polygon &polygon, size_t seg1, size_t seg2) { if (seg1 == seg2) { // Nothing to append from this segment. } else if (seg1 < seg2) { // Do not append a point pointed to by seg2. out.insert(out.end(), polygon.points.begin() + seg1, polygon.points.begin() + seg2); } else { out.reserve(out.size() + seg2 + polygon.points.size() - seg1); out.insert(out.end(), polygon.points.begin() + seg1, polygon.points.end()); // Do not append a point pointed to by seg2. out.insert(out.end(), polygon.points.begin(), polygon.points.begin() + seg2); } } // Append a segment of a closed polygon to a polyline. // The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop, // but this time the segment is traversed backward. // Only insert intermediate points between seg1 and seg2. static inline void polygon_segment_append_reversed(Points &out, const Polygon &polygon, size_t seg1, size_t seg2) { if (seg1 >= seg2) { out.reserve(seg1 - seg2); for (size_t i = seg1; i > seg2; -- i) out.push_back(polygon.points[i - 1]); } else { // it could be, that seg1 == seg2. In that case, append the complete loop. out.reserve(out.size() + seg2 + polygon.points.size() - seg1); for (size_t i = seg1; i > 0; -- i) out.push_back(polygon.points[i - 1]); for (size_t i = polygon.points.size(); i > seg2; -- i) out.push_back(polygon.points[i - 1]); } } static inline int distance_of_segmens(const Polygon &poly, size_t seg1, size_t seg2, bool forward) { int d = int(seg2) - int(seg1); if (! forward) d = - d; if (d < 0) d += int(poly.points.size()); return d; } // For a vertical line, an inner contour and an intersection point, // find an intersection point on the previous resp. next vertical line. // The intersection point is connected with the prev resp. next intersection point with iInnerContour. // Return -1 if there is no such point on the previous resp. next vertical line. static inline int intersection_on_prev_next_vertical_line( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, bool dir_is_next) { size_t iVerticalLineOther = iVerticalLine; if (dir_is_next) { if (++ iVerticalLineOther == segs.size()) // No successive vertical line. return -1; } else if (iVerticalLineOther -- == 0) { // No preceding vertical line. return -1; } const SegmentedIntersectionLine &il = segs[iVerticalLine]; const SegmentIntersection &itsct = il.intersections[iIntersection]; const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther]; const Polygon &poly = poly_with_offset.contour(iInnerContour); // const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour); const bool forward = itsct.is_low() == dir_is_next; // Resulting index of an intersection point on il2. int out = -1; // Find an intersection point on iVerticalLineOther, intersecting iInnerContour // at the same orientation as iIntersection, and being closest to iIntersection // in the number of contour segments, when following the direction of the contour. int dmin = std::numeric_limits::max(); for (size_t i = 0; i < il2.intersections.size(); ++ i) { const SegmentIntersection &itsct2 = il2.intersections[i]; if (itsct.iContour == itsct2.iContour && itsct.type == itsct2.type) { /* if (itsct.is_low()) { assert(itsct.type == SegmentIntersection::INNER_LOW); assert(iIntersection > 0); assert(il.intersections[iIntersection-1].type == SegmentIntersection::OUTER_LOW); assert(i > 0); if (il2.intersections[i-1].is_inner()) // Take only the lowest inner intersection point. continue; assert(il2.intersections[i-1].type == SegmentIntersection::OUTER_LOW); } else { assert(itsct.type == SegmentIntersection::INNER_HIGH); assert(iIntersection+1 < il.intersections.size()); assert(il.intersections[iIntersection+1].type == SegmentIntersection::OUTER_HIGH); assert(i+1 < il2.intersections.size()); if (il2.intersections[i+1].is_inner()) // Take only the highest inner intersection point. continue; assert(il2.intersections[i+1].type == SegmentIntersection::OUTER_HIGH); } */ // The intersection points lie on the same contour and have the same orientation. // Find the intersection point with a shortest path in the direction of the contour. int d = distance_of_segmens(poly, itsct.iSegment, itsct2.iSegment, forward); if (d < dmin) { out = i; dmin = d; } } } //FIXME this routine is not asymptotic optimal, it will be slow if there are many intersection points along the line. return out; } static inline int intersection_on_prev_vertical_line( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection) { return intersection_on_prev_next_vertical_line(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, false); } static inline int intersection_on_next_vertical_line( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection) { return intersection_on_prev_next_vertical_line(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, true); } enum IntersectionTypeOtherVLine { // There is no connection point on the other vertical line. INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED = -1, // Connection point on the other vertical segment was found // and it could be followed. INTERSECTION_TYPE_OTHER_VLINE_OK = 0, // The connection segment connects to a middle of a vertical segment. // Cannot follow. INTERSECTION_TYPE_OTHER_VLINE_INNER, // Cannot extend the contor to this intersection point as either the connection segment // or the succeeding vertical segment were already consumed. INTERSECTION_TYPE_OTHER_VLINE_CONSUMED, // Not the first intersection along the contor. This intersection point // has been preceded by an intersection point along the vertical line. INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST, }; // Find an intersection on a previous line, but return -1, if the connecting segment of a perimeter was already extruded. static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical_line( const std::vector &segs, size_t iVerticalLine, size_t iIntersection, size_t iIntersectionOther, bool dir_is_next) { // This routine will propose a connecting line even if the connecting perimeter segment intersects // iVertical line multiple times before reaching iIntersectionOther. if (iIntersectionOther == -1) return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED; assert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0)); const SegmentedIntersectionLine &il_this = segs[iVerticalLine]; const SegmentIntersection &itsct_this = il_this.intersections[iIntersection]; const SegmentedIntersectionLine &il_other = segs[dir_is_next ? (iVerticalLine+1) : (iVerticalLine-1)]; const SegmentIntersection &itsct_other = il_other.intersections[iIntersectionOther]; assert(itsct_other.is_inner()); assert(iIntersectionOther > 0); assert(iIntersectionOther + 1 < il_other.intersections.size()); // Is iIntersectionOther at the boundary of a vertical segment? const SegmentIntersection &itsct_other2 = il_other.intersections[itsct_other.is_low() ? iIntersectionOther - 1 : iIntersectionOther + 1]; if (itsct_other2.is_inner()) // Cannot follow a perimeter segment into the middle of another vertical segment. // Only perimeter segments connecting to the end of a vertical segment are followed. return INTERSECTION_TYPE_OTHER_VLINE_INNER; assert(itsct_other.is_low() == itsct_other2.is_low()); if (dir_is_next ? itsct_this.consumed_perimeter_right : itsct_other.consumed_perimeter_right) // This perimeter segment was already consumed. return INTERSECTION_TYPE_OTHER_VLINE_CONSUMED; if (itsct_other.is_low() ? itsct_other.consumed_vertical_up : il_other.intersections[iIntersectionOther-1].consumed_vertical_up) // This vertical segment was already consumed. return INTERSECTION_TYPE_OTHER_VLINE_CONSUMED; return INTERSECTION_TYPE_OTHER_VLINE_OK; } static inline IntersectionTypeOtherVLine intersection_type_on_prev_vertical_line( const std::vector &segs, size_t iVerticalLine, size_t iIntersection, size_t iIntersectionPrev) { return intersection_type_on_prev_next_vertical_line(segs, iVerticalLine, iIntersection, iIntersectionPrev, false); } static inline IntersectionTypeOtherVLine intersection_type_on_next_vertical_line( const std::vector &segs, size_t iVerticalLine, size_t iIntersection, size_t iIntersectionNext) { return intersection_type_on_prev_next_vertical_line(segs, iVerticalLine, iIntersection, iIntersectionNext, true); } // Measure an Euclidian length of a perimeter segment when going from iIntersection to iIntersection2. static inline coordf_t measure_perimeter_prev_next_segment_length( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, size_t iIntersection2, bool dir_is_next) { size_t iVerticalLineOther = iVerticalLine; if (dir_is_next) { if (++ iVerticalLineOther == segs.size()) // No successive vertical line. return coordf_t(-1); } else if (iVerticalLineOther -- == 0) { // No preceding vertical line. return coordf_t(-1); } const SegmentedIntersectionLine &il = segs[iVerticalLine]; const SegmentIntersection &itsct = il.intersections[iIntersection]; const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther]; const SegmentIntersection &itsct2 = il2.intersections[iIntersection2]; const Polygon &poly = poly_with_offset.contour(iInnerContour); // const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour); assert(itsct.type == itsct2.type); assert(itsct.iContour == itsct2.iContour); assert(itsct.is_inner()); const bool forward = itsct.is_low() == dir_is_next; Point p1 = itsct.pos(); Point p2 = itsct2.pos(); return forward ? segment_length(poly, itsct .iSegment, p1, itsct2.iSegment, p2) : segment_length(poly, itsct2.iSegment, p2, itsct .iSegment, p1); } static inline coordf_t measure_perimeter_prev_segment_length( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, size_t iIntersection2) { return measure_perimeter_prev_next_segment_length(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, iIntersection2, false); } static inline coordf_t measure_perimeter_next_segment_length( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, size_t iIntersection2) { return measure_perimeter_prev_next_segment_length(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, iIntersection2, true); } // Append the points of a perimeter segment when going from iIntersection to iIntersection2. // The first point (the point of iIntersection) will not be inserted, // the last point will be inserted. static inline void emit_perimeter_prev_next_segment( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, size_t iIntersection2, Polyline &out, bool dir_is_next) { size_t iVerticalLineOther = iVerticalLine; if (dir_is_next) { ++ iVerticalLineOther; assert(iVerticalLineOther < segs.size()); } else { assert(iVerticalLineOther > 0); -- iVerticalLineOther; } const SegmentedIntersectionLine &il = segs[iVerticalLine]; const SegmentIntersection &itsct = il.intersections[iIntersection]; const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther]; const SegmentIntersection &itsct2 = il2.intersections[iIntersection2]; const Polygon &poly = poly_with_offset.contour(iInnerContour); // const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour); assert(itsct.type == itsct2.type); assert(itsct.iContour == itsct2.iContour); assert(itsct.is_inner()); const bool forward = itsct.is_low() == dir_is_next; // Do not append the first point. // out.points.push_back(Point(il.pos, itsct.pos)); if (forward) polygon_segment_append(out.points, poly, itsct.iSegment, itsct2.iSegment); else polygon_segment_append_reversed(out.points, poly, itsct.iSegment, itsct2.iSegment); // Append the last point. out.points.push_back(itsct2.pos()); } static inline coordf_t measure_perimeter_segment_on_vertical_line_length( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, size_t iIntersection2, bool forward) { const SegmentedIntersectionLine &il = segs[iVerticalLine]; const SegmentIntersection &itsct = il.intersections[iIntersection]; const SegmentIntersection &itsct2 = il.intersections[iIntersection2]; const Polygon &poly = poly_with_offset.contour(iInnerContour); assert(itsct.is_inner()); assert(itsct2.is_inner()); assert(itsct.type != itsct2.type); assert(itsct.iContour == iInnerContour); assert(itsct.iContour == itsct2.iContour); return forward ? segment_length(poly, itsct .iSegment, itsct.pos(), itsct2.iSegment, itsct2.pos()) : segment_length(poly, itsct2.iSegment, itsct2.pos(), itsct .iSegment, itsct.pos()); } // Append the points of a perimeter segment when going from iIntersection to iIntersection2. // The first point (the point of iIntersection) will not be inserted, // the last point will be inserted. static inline void emit_perimeter_segment_on_vertical_line( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t iVerticalLine, size_t iInnerContour, size_t iIntersection, size_t iIntersection2, Polyline &out, bool forward) { const SegmentedIntersectionLine &il = segs[iVerticalLine]; const SegmentIntersection &itsct = il.intersections[iIntersection]; const SegmentIntersection &itsct2 = il.intersections[iIntersection2]; const Polygon &poly = poly_with_offset.contour(iInnerContour); assert(itsct.is_inner()); assert(itsct2.is_inner()); assert(itsct.type != itsct2.type); assert(itsct.iContour == iInnerContour); assert(itsct.iContour == itsct2.iContour); // Do not append the first point. // out.points.push_back(Point(il.pos, itsct.pos)); if (forward) polygon_segment_append(out.points, poly, itsct.iSegment, itsct2.iSegment); else polygon_segment_append_reversed(out.points, poly, itsct.iSegment, itsct2.iSegment); // Append the last point. out.points.push_back(itsct2.pos()); } //TBD: For precise infill, measure the area of a slab spanned by an infill line. /* static inline float measure_outer_contour_slab( const ExPolygonWithOffset &poly_with_offset, const std::vector &segs, size_t i_vline, size_t iIntersection) { const SegmentedIntersectionLine &il = segs[i_vline]; const SegmentIntersection &itsct = il.intersections[i_vline]; const SegmentIntersection &itsct2 = il.intersections[iIntersection2]; const Polygon &poly = poly_with_offset.contour((itsct.iContour); assert(itsct.is_outer()); assert(itsct2.is_outer()); assert(itsct.type != itsct2.type); assert(itsct.iContour == itsct2.iContour); if (! itsct.is_outer() || ! itsct2.is_outer() || itsct.type == itsct2.type || itsct.iContour != itsct2.iContour) // Error, return zero area. return 0.f; // Find possible connection points on the previous / next vertical line. int iPrev = intersection_on_prev_vertical_line(poly_with_offset, segs, i_vline, itsct.iContour, i_intersection); int iNext = intersection_on_next_vertical_line(poly_with_offset, segs, i_vline, itsct.iContour, i_intersection); // Find possible connection points on the same vertical line. int iAbove = iBelow = -1; // Does the perimeter intersect the current vertical line above intrsctn? for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) if (seg.intersections[i].iContour == itsct.iContour) { iAbove = i; break; } // Does the perimeter intersect the current vertical line below intrsctn? for (int i = int(i_intersection) - 1; i > 0; -- i) if (seg.intersections[i].iContour == itsct.iContour) { iBelow = i; break; } if (iSegAbove != -1 && seg.intersections[iAbove].type == SegmentIntersection::OUTER_HIGH) { // Invalidate iPrev resp. iNext, if the perimeter crosses the current vertical line earlier than iPrev resp. iNext. // The perimeter contour orientation. const Polygon &poly = poly_with_offset.contour(itsct.iContour); { int d_horiz = (iPrev == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, segs[i_vline-1].intersections[iPrev].iSegment, itsct.iSegment, true); int d_down = (iBelow == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, iSegBelow, itsct.iSegment, true); int d_up = (iAbove == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, iSegAbove, itsct.iSegment, true); if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up)) // The vertical crossing comes eralier than the prev crossing. // Disable the perimeter going back. intrsctn_type_prev = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST; if (d_up > std::min(d_horiz, d_down)) // The horizontal crossing comes earlier than the vertical crossing. vert_seg_dir_valid_mask &= ~DIR_BACKWARD; } { int d_horiz = (iNext == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, itsct.iSegment, segs[i_vline+1].intersections[iNext].iSegment, true); int d_down = (iSegBelow == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, itsct.iSegment, iSegBelow, true); int d_up = (iSegAbove == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, itsct.iSegment, iSegAbove, true); if (d_up > std::min(d_horiz, d_down)) // The horizontal crossing comes earlier than the vertical crossing. vert_seg_dir_valid_mask &= ~DIR_FORWARD; } } } */ enum DirectionMask { DIR_FORWARD = 1, DIR_BACKWARD = 2 }; // For the rectilinear, grid, triangles, stars and cubic pattern fill one InfillHatchingSingleDirection structure // for each infill direction. The segments stored in InfillHatchingSingleDirection will then form a graph of candidate // paths to be extruded. static bool fill_hatching_segments_legacy( // Input geometry to be hatch, containing two concentric contours for each input contour. const ExPolygonWithOffset &poly_with_offset, // fill density, dont_adjust const FillParams ¶ms, const coord_t link_max_length, // Resulting straight segments of the infill graph. InfillHatchingSingleDirection &hatching, Polylines &polylines_out) { // At the end, only the new polylines will be rotated back. size_t n_polylines_out_initial = polylines_out.size(); std::vector &segs = hatching.segs; // For each outer only chords, measure their maximum distance to the bow of the outer contour. // Mark an outer only chord as consumed, if the distance is low. for (size_t i_vline = 0; i_vline < segs.size(); ++ i_vline) { SegmentedIntersectionLine &seg = segs[i_vline]; for (size_t i_intersection = 0; i_intersection + 1 < seg.intersections.size(); ++ i_intersection) { if (seg.intersections[i_intersection].type == SegmentIntersection::OUTER_LOW && seg.intersections[i_intersection+1].type == SegmentIntersection::OUTER_HIGH) { bool consumed = false; // if (params.full_infill()) { // measure_outer_contour_slab(poly_with_offset, segs, i_vline, i_ntersection); // } else consumed = true; seg.intersections[i_intersection].consumed_vertical_up = consumed; } } } // Now construct a graph. // Find the first point. // Naively one would expect to achieve best results by chaining the paths by the shortest distance, // but that procedure does not create the longest continuous paths. // A simple "sweep left to right" procedure achieves better results. size_t i_vline = 0; size_t i_intersection = size_t(-1); // Follow the line, connect the lines into a graph. // Until no new line could be added to the output path: Point pointLast; Polyline *polyline_current = NULL; if (! polylines_out.empty()) pointLast = polylines_out.back().points.back(); for (;;) { if (i_intersection == size_t(-1)) { // The path has been interrupted. Find a next starting point, closest to the previous extruder position. coordf_t dist2min = std::numeric_limits().max(); for (size_t i_vline2 = 0; i_vline2 < segs.size(); ++ i_vline2) { const SegmentedIntersectionLine &seg = segs[i_vline2]; if (! seg.intersections.empty()) { assert(seg.intersections.size() > 1); // Even number of intersections with the loops. assert((seg.intersections.size() & 1) == 0); assert(seg.intersections.front().type == SegmentIntersection::OUTER_LOW); for (size_t i = 0; i < seg.intersections.size(); ++ i) { const SegmentIntersection &intrsctn = seg.intersections[i]; if (intrsctn.is_outer()) { assert(intrsctn.is_low() || i > 0); bool consumed = intrsctn.is_low() ? intrsctn.consumed_vertical_up : seg.intersections[i-1].consumed_vertical_up; if (! consumed) { coordf_t dist2 = pointLast.distance_to(intrsctn.pos()); if (dist2 < dist2min) { dist2min = dist2; i_vline = i_vline2; i_intersection = i; //FIXME We are taking the first left point always. Verify, that the caller chains the paths // by a shortest distance, while reversing the paths if needed. //if (polylines_out.empty()) // Initial state, take the first line, which is the first from the left. goto found; } } } } } } if (i_intersection == size_t(-1)) // We are finished. break; found: // Start a new path. polylines_out.push_back(Polyline()); polyline_current = &polylines_out.back(); // Emit the first point of a path. pointLast = segs[i_vline].intersections[i_intersection].pos(); polyline_current->points.push_back(pointLast); } // From the initial point (i_vline, i_intersection), follow a path. SegmentedIntersectionLine &seg = segs[i_vline]; SegmentIntersection *intrsctn = &seg.intersections[i_intersection]; bool going_up = intrsctn->is_low(); bool try_connect = false; if (going_up) { assert(! intrsctn->consumed_vertical_up); assert(i_intersection + 1 < seg.intersections.size()); // Step back to the beginning of the vertical segment to mark it as consumed. if (intrsctn->is_inner()) { assert(i_intersection > 0); -- intrsctn; -- i_intersection; } // Consume the complete vertical segment up to the outer contour. do { intrsctn->consumed_vertical_up = true; ++ intrsctn; ++ i_intersection; assert(i_intersection < seg.intersections.size()); } while (intrsctn->type != SegmentIntersection::OUTER_HIGH); if ((intrsctn - 1)->is_inner()) { // Step back. -- intrsctn; -- i_intersection; assert(intrsctn->type == SegmentIntersection::INNER_HIGH); try_connect = true; } } else { // Going down. assert(intrsctn->is_high()); assert(i_intersection > 0); assert(! (intrsctn - 1)->consumed_vertical_up); // Consume the complete vertical segment up to the outer contour. if (intrsctn->is_inner()) intrsctn->consumed_vertical_up = true; do { assert(i_intersection > 0); -- intrsctn; -- i_intersection; intrsctn->consumed_vertical_up = true; } while (intrsctn->type != SegmentIntersection::OUTER_LOW); if ((intrsctn + 1)->is_inner()) { // Step back. ++ intrsctn; ++ i_intersection; assert(intrsctn->type == SegmentIntersection::INNER_LOW); try_connect = true; } } if (try_connect) { // Decide, whether to finish the segment, or whether to follow the perimeter. // 1) Find possible connection points on the previous / next vertical line. int iPrev = intersection_on_prev_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection); int iNext = intersection_on_next_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection); IntersectionTypeOtherVLine intrsctn_type_prev = intersection_type_on_prev_vertical_line(segs, i_vline, i_intersection, iPrev); IntersectionTypeOtherVLine intrsctn_type_next = intersection_type_on_next_vertical_line(segs, i_vline, i_intersection, iNext); // 2) Find possible connection points on the same vertical line. int iAbove = -1; int iBelow = -1; int iSegAbove = -1; int iSegBelow = -1; { SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; // Does the perimeter intersect the current vertical line above intrsctn? for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) // if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) { if (seg.intersections[i].iContour == intrsctn->iContour) { iAbove = i; iSegAbove = seg.intersections[i].iSegment; break; } // Does the perimeter intersect the current vertical line below intrsctn? for (size_t i = i_intersection - 1; i > 0; -- i) // if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) { if (seg.intersections[i].iContour == intrsctn->iContour) { iBelow = i; iSegBelow = seg.intersections[i].iSegment; break; } } // 3) Sort the intersection points, clear iPrev / iNext / iSegBelow / iSegAbove, // if it is preceded by any other intersection point along the contour. unsigned int vert_seg_dir_valid_mask = (going_up ? (iSegAbove != -1 && seg.intersections[iAbove].type == SegmentIntersection::INNER_LOW) : (iSegBelow != -1 && seg.intersections[iBelow].type == SegmentIntersection::INNER_HIGH)) ? (DIR_FORWARD | DIR_BACKWARD) : 0; { // Invalidate iPrev resp. iNext, if the perimeter crosses the current vertical line earlier than iPrev resp. iNext. // The perimeter contour orientation. const bool forward = intrsctn->is_low(); // == poly_with_offset.is_contour_ccw(intrsctn->iContour); const Polygon &poly = poly_with_offset.contour(intrsctn->iContour); { int d_horiz = (iPrev == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, segs[i_vline-1].intersections[iPrev].iSegment, intrsctn->iSegment, forward); int d_down = (iSegBelow == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, iSegBelow, intrsctn->iSegment, forward); int d_up = (iSegAbove == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, iSegAbove, intrsctn->iSegment, forward); if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up)) // The vertical crossing comes eralier than the prev crossing. // Disable the perimeter going back. intrsctn_type_prev = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST; if (going_up ? (d_up > std::min(d_horiz, d_down)) : (d_down > std::min(d_horiz, d_up))) // The horizontal crossing comes earlier than the vertical crossing. vert_seg_dir_valid_mask &= ~(forward ? DIR_BACKWARD : DIR_FORWARD); } { int d_horiz = (iNext == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, intrsctn->iSegment, segs[i_vline+1].intersections[iNext].iSegment, forward); int d_down = (iSegBelow == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, intrsctn->iSegment, iSegBelow, forward); int d_up = (iSegAbove == -1) ? std::numeric_limits::max() : distance_of_segmens(poly, intrsctn->iSegment, iSegAbove, forward); if (intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up)) // The vertical crossing comes eralier than the prev crossing. // Disable the perimeter going forward. intrsctn_type_next = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST; if (going_up ? (d_up > std::min(d_horiz, d_down)) : (d_down > std::min(d_horiz, d_up))) // The horizontal crossing comes earlier than the vertical crossing. vert_seg_dir_valid_mask &= ~(forward ? DIR_FORWARD : DIR_BACKWARD); } } // 4) Try to connect to a previous or next vertical line, making a zig-zag pattern. if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK || intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK) { coordf_t distPrev = (intrsctn_type_prev != INTERSECTION_TYPE_OTHER_VLINE_OK) ? std::numeric_limits::max() : measure_perimeter_prev_segment_length(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iPrev); coordf_t distNext = (intrsctn_type_next != INTERSECTION_TYPE_OTHER_VLINE_OK) ? std::numeric_limits::max() : measure_perimeter_next_segment_length(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext); // Take the shorter path. //FIXME this may not be always the best strategy to take the shortest connection line now. bool take_next = (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK) ? (distNext < distPrev) : intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK; assert(intrsctn->is_inner()); bool skip = params.dont_connect || (link_max_length > 0 && (take_next ? distNext : distPrev) > link_max_length); if (skip) { // Just skip the connecting contour and start a new path. goto dont_connect; polyline_current->points.push_back(intrsctn->pos()); polylines_out.push_back(Polyline()); polyline_current = &polylines_out.back(); const SegmentedIntersectionLine &il2 = segs[take_next ? (i_vline + 1) : (i_vline - 1)]; polyline_current->points.push_back(il2.intersections[take_next ? iNext : iPrev].pos()); } else { polyline_current->points.push_back(intrsctn->pos()); emit_perimeter_prev_next_segment(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, take_next ? iNext : iPrev, *polyline_current, take_next); } // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed. if (iPrev != -1) segs[i_vline-1].intersections[iPrev].consumed_perimeter_right = true; if (iNext != -1) intrsctn->consumed_perimeter_right = true; //FIXME consume the left / right connecting segments at the other end of this line? Currently it is not critical because a perimeter segment is not followed if the vertical segment at the other side has already been consumed. // Advance to the neighbor line. if (take_next) { ++ i_vline; i_intersection = iNext; } else { -- i_vline; i_intersection = iPrev; } continue; } // 5) Try to connect to a previous or next point on the same vertical line. if (vert_seg_dir_valid_mask) { bool valid = true; // Verify, that there is no intersection with the inner contour up to the end of the contour segment. // Verify, that the successive segment has not been consumed yet. if (going_up) { if (seg.intersections[iAbove].consumed_vertical_up) { valid = false; } else { for (int i = (int)i_intersection + 1; i < iAbove && valid; ++i) if (seg.intersections[i].is_inner()) valid = false; } } else { if (seg.intersections[iBelow-1].consumed_vertical_up) { valid = false; } else { for (int i = iBelow + 1; i < (int)i_intersection && valid; ++i) if (seg.intersections[i].is_inner()) valid = false; } } if (valid) { const Polygon &poly = poly_with_offset.contour(intrsctn->iContour); int iNext = going_up ? iAbove : iBelow; int iSegNext = going_up ? iSegAbove : iSegBelow; bool dir_forward = (vert_seg_dir_valid_mask == (DIR_FORWARD | DIR_BACKWARD)) ? // Take the shorter length between the current and the next intersection point. (distance_of_segmens(poly, intrsctn->iSegment, iSegNext, true) < distance_of_segmens(poly, intrsctn->iSegment, iSegNext, false)) : (vert_seg_dir_valid_mask == DIR_FORWARD); // Skip this perimeter line? bool skip = params.dont_connect; if (! skip && link_max_length > 0) { coordf_t link_length = measure_perimeter_segment_on_vertical_line_length( poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, dir_forward); skip = link_length > link_max_length; } polyline_current->points.push_back(intrsctn->pos()); if (skip) { // Just skip the connecting contour and start a new path. polylines_out.push_back(Polyline()); polyline_current = &polylines_out.back(); polyline_current->points.push_back(seg.intersections[iNext].pos()); } else { // Consume the connecting contour and the next segment. emit_perimeter_segment_on_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, *polyline_current, dir_forward); } // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed. // If there are any outer intersection points skipped (bypassed) by the contour, // mark them as processed. if (going_up) { for (int i = (int)i_intersection; i < iAbove; ++ i) seg.intersections[i].consumed_vertical_up = true; } else { for (int i = iBelow; i < (int)i_intersection; ++ i) seg.intersections[i].consumed_vertical_up = true; } // seg.intersections[going_up ? i_intersection : i_intersection - 1].consumed_vertical_up = true; intrsctn->consumed_perimeter_right = true; i_intersection = iNext; if (going_up) ++ intrsctn; else -- intrsctn; intrsctn->consumed_perimeter_right = true; continue; } } dont_connect: // No way to continue the current polyline. Take the rest of the line up to the outer contour. // This will finish the polyline, starting another polyline at a new point. if (going_up) ++ intrsctn; else -- intrsctn; } // Finish the current vertical line, // reset the current vertical line to pick a new starting point in the next round. assert(intrsctn->is_outer()); assert(intrsctn->is_high() == going_up); pointLast = intrsctn->pos(); polyline_current->points.push_back(pointLast); // Handle duplicate points and zero length segments. polyline_current->remove_duplicate_points(); assert(! polyline_current->has_duplicate_points()); // Handle nearly zero length edges. if (polyline_current->points.size() <= 1 || (polyline_current->points.size() == 2 && std::abs(polyline_current->points.front().x - polyline_current->points.back().x) < SCALED_EPSILON && std::abs(polyline_current->points.front().y - polyline_current->points.back().y) < SCALED_EPSILON)) polylines_out.pop_back(); intrsctn = NULL; i_intersection = -1; polyline_current = NULL; } #ifdef SLIC3R_DEBUG { static int iRun = 0; BoundingBox bbox_svg = poly_with_offset.bounding_box_outer(); { ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-final-%03d.svg", iRun), bbox_svg); // , scale_(1.)); poly_with_offset.export_to_svg(svg); for (size_t i = n_polylines_out_initial; i < polylines_out.size(); ++ i) svg.draw(polylines_out[i].lines(), "black"); } // Paint a picture per polyline. This makes it easier to discover the order of the polylines and their overlap. for (size_t i_polyline = n_polylines_out_initial; i_polyline < polylines_out.size(); ++ i_polyline) { ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-final-%03d-%03d.svg", iRun, i_polyline), bbox_svg); // , scale_(1.)); svg.draw(polylines_out[i_polyline].lines(), "black"); } } #endif /* SLIC3R_DEBUG */ // paths must be rotated back for (Polylines::iterator it = polylines_out.begin() + n_polylines_out_initial; it != polylines_out.end(); ++ it) { // No need to translate, the absolute position is irrelevant. // it->translate(- rotate_vector.second.x, - rotate_vector.second.y); assert(! it->has_duplicate_points()); //it->rotate(rotate_vector.first); //FIXME rather simplify the paths to avoid very short edges? //assert(! it->has_duplicate_points()); it->remove_duplicate_points(); } #ifdef SLIC3R_DEBUG // Verify, that there are no duplicate points in the sequence. for (Polyline &polyline : polylines_out) assert(! polyline.has_duplicate_points()); #endif /* SLIC3R_DEBUG */ return true; } }; // namespace FillRectilinear3_Internal bool FillRectilinear3::fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, std::vector &fill_dir_params, Polylines &polylines_out) { assert(params.density > 0.0001f && params.density <= 1.f); const float INFILL_OVERLAP_OVER_SPACING = 0.45f; assert(INFILL_OVERLAP_OVER_SPACING > 0 && INFILL_OVERLAP_OVER_SPACING < 0.5f); // On the polygons of poly_with_offset, the infill lines will be connected. FillRectilinear3_Internal::ExPolygonWithOffset poly_with_offset( surface->expolygon, float(scale_(- (0.5 - INFILL_OVERLAP_OVER_SPACING) * this->spacing)), float(scale_(- 0.5 * this->spacing))); if (poly_with_offset.n_contours_inner == 0) { // Not a single infill line fits. //FIXME maybe one shall trigger the gap fill here? return true; } // Rotate polygons so that we can work with vertical lines here std::pair rotate_vector = this->_infill_direction(surface); std::vector hatching(fill_dir_params.size(), FillRectilinear3_Internal::InfillHatchingSingleDirection()); for (size_t i = 0; i < hatching.size(); ++ i) if (! FillRectilinear3_Internal::prepare_infill_hatching_segments(poly_with_offset, params, fill_dir_params[i], rotate_vector, hatching[i])) return false; for (size_t i = 0; i < hatching.size(); ++ i) if (! FillRectilinear3_Internal::fill_hatching_segments_legacy( poly_with_offset, params, this->link_max_length, hatching[i], polylines_out)) return false; return true; } Polylines FillRectilinear3::fill_surface(const Surface *surface, const FillParams ¶ms) { Polylines polylines_out; std::vector fill_dir_params; fill_dir_params.emplace_back(FillDirParams(this->spacing, 0.f)); if (! fill_surface_by_lines(surface, params, fill_dir_params, polylines_out)) printf("FillRectilinear3::fill_surface() failed to fill a region.\n"); if (params.full_infill() && ! params.dont_adjust) // Return back the adjusted spacing. this->spacing = fill_dir_params.front().spacing; return polylines_out; } Polylines FillGrid3::fill_surface(const Surface *surface, const FillParams ¶ms) { // Each linear fill covers half of the target coverage. FillParams params2 = params; params2.density *= 0.5f; Polylines polylines_out; std::vector fill_dir_params; fill_dir_params.emplace_back(FillDirParams(this->spacing, 0.f)); fill_dir_params.emplace_back(FillDirParams(this->spacing, float(M_PI / 2.))); if (! fill_surface_by_lines(surface, params2, fill_dir_params, polylines_out)) printf("FillGrid3::fill_surface() failed to fill a region.\n"); return polylines_out; } Polylines FillTriangles3::fill_surface(const Surface *surface, const FillParams ¶ms) { // Each linear fill covers 1/3 of the target coverage. FillParams params2 = params; params2.density *= 0.333333333f; Polylines polylines_out; std::vector fill_dir_params; fill_dir_params.emplace_back(FillDirParams(this->spacing, 0.)); fill_dir_params.emplace_back(FillDirParams(this->spacing, M_PI / 3.)); fill_dir_params.emplace_back(FillDirParams(this->spacing, 2. * M_PI / 3.)); if (! fill_surface_by_lines(surface, params2, fill_dir_params, polylines_out)) printf("FillTriangles3::fill_surface() failed to fill a region.\n"); return polylines_out; } Polylines FillStars3::fill_surface(const Surface *surface, const FillParams ¶ms) { // Each linear fill covers 1/3 of the target coverage. FillParams params2 = params; params2.density *= 0.333333333f; Polylines polylines_out; std::vector fill_dir_params; fill_dir_params.emplace_back(FillDirParams(this->spacing, 0.)); fill_dir_params.emplace_back(FillDirParams(this->spacing, M_PI / 3.)); fill_dir_params.emplace_back(FillDirParams(this->spacing, 2. * M_PI / 3., 0.5 * this->spacing / params2.density)); if (! fill_surface_by_lines(surface, params2, fill_dir_params, polylines_out)) printf("FillStars3::fill_surface() failed to fill a region.\n"); return polylines_out; } Polylines FillCubic3::fill_surface(const Surface *surface, const FillParams ¶ms) { // Each linear fill covers 1/3 of the target coverage. FillParams params2 = params; params2.density *= 0.333333333f; Polylines polylines_out; std::vector fill_dir_params; coordf_t dx = sqrt(0.5) * z; fill_dir_params.emplace_back(FillDirParams(this->spacing, 0., dx)); fill_dir_params.emplace_back(FillDirParams(this->spacing, M_PI / 3., -dx)); fill_dir_params.emplace_back(FillDirParams(this->spacing, 2. * M_PI / 3., dx)); if (! fill_surface_by_lines(surface, params2, fill_dir_params, polylines_out)) printf("FillCubic3::fill_surface() failed to fill a region.\n"); return polylines_out; } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/Fill/FillRectilinear3.hpp000066400000000000000000000051371324354444700243720ustar00rootroot00000000000000#ifndef slic3r_FillRectilinear3_hpp_ #define slic3r_FillRectilinear3_hpp_ #include "../libslic3r.h" #include "FillBase.hpp" namespace Slic3r { class Surface; class FillRectilinear3 : public Fill { public: virtual Fill* clone() const { return new FillRectilinear3(*this); }; virtual ~FillRectilinear3() {} virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); struct FillDirParams { FillDirParams(coordf_t spacing, double angle, coordf_t pattern_shift = 0.f) : spacing(spacing), angle(angle), pattern_shift(pattern_shift) {} coordf_t spacing; double angle; coordf_t pattern_shift; }; protected: bool fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, std::vector &fill_dir_params, Polylines &polylines_out); }; class FillGrid3 : public FillRectilinear3 { public: virtual Fill* clone() const { return new FillGrid3(*this); }; virtual ~FillGrid3() {} virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. virtual float _layer_angle(size_t /* idx */) const { return 0.f; } }; class FillTriangles3 : public FillRectilinear3 { public: virtual Fill* clone() const { return new FillTriangles3(*this); }; virtual ~FillTriangles3() {} virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. virtual float _layer_angle(size_t /* idx */) const { return 0.f; } }; class FillStars3 : public FillRectilinear3 { public: virtual Fill* clone() const { return new FillStars3(*this); }; virtual ~FillStars3() {} virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. virtual float _layer_angle(size_t /* idx */) const { return 0.f; } }; class FillCubic3 : public FillRectilinear3 { public: virtual Fill* clone() const { return new FillCubic3(*this); }; virtual ~FillCubic3() {} virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. virtual float _layer_angle(size_t /* idx */) const { return 0.f; } }; }; // namespace Slic3r #endif // slic3r_FillRectilinear3_hpp_ Slic3r-version_1.39.1/xs/src/libslic3r/Flow.cpp000066400000000000000000000150751324354444700212550ustar00rootroot00000000000000#include "Flow.hpp" #include "Print.hpp" #include #include namespace Slic3r { // This static method returns a sane extrusion width default. static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, float height) { switch (role) { case frSupportMaterial: case frSupportMaterialInterface: case frTopSolidInfill: return nozzle_diameter; default: case frExternalPerimeter: case frPerimeter: case frSolidInfill: case frInfill: return 1.125f * nozzle_diameter; } } // This constructor builds a Flow object from an extrusion width config setting // and other context properties. Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio) { // we need layer height unless it's a bridge if (height <= 0 && bridge_flow_ratio == 0) CONFESS("Invalid flow height supplied to new_from_config_width()"); float w; if (bridge_flow_ratio > 0) { // If bridge flow was requested, calculate the bridge width. height = w = (bridge_flow_ratio == 1.) ? // optimization to avoid sqrt() nozzle_diameter : sqrt(bridge_flow_ratio) * nozzle_diameter; } else if (! width.percent && width.value == 0.) { // If user left option to 0, calculate a sane default width. w = auto_extrusion_width(role, nozzle_diameter, height); } else { // If user set a manual value, use it. w = float(width.get_abs_value(height)); } return Flow(w, height, nozzle_diameter, bridge_flow_ratio > 0); } // This constructor builds a Flow object from a given centerline spacing. Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge) { // we need layer height unless it's a bridge if (height <= 0 && !bridge) CONFESS("Invalid flow height supplied to new_from_spacing()"); // Calculate width from spacing. // For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions. // For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads. float width = float(bridge ? (spacing - BRIDGE_EXTRA_SPACING) : #ifdef HAS_PERIMETER_LINE_OVERLAP (spacing + PERIMETER_LINE_OVERLAP_FACTOR * height * (1. - 0.25 * PI)); #else (spacing + height * (1. - 0.25 * PI))); #endif return Flow(width, bridge ? width : height, nozzle_diameter, bridge); } // This method returns the centerline spacing between two adjacent extrusions // having the same extrusion width (and other properties). float Flow::spacing() const { #ifdef HAS_PERIMETER_LINE_OVERLAP if (this->bridge) return this->width + BRIDGE_EXTRA_SPACING; // rectangle with semicircles at the ends float min_flow_spacing = this->width - this->height * (1. - 0.25 * PI); return this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing); #else return float(this->bridge ? (this->width + BRIDGE_EXTRA_SPACING) : (this->width - this->height * (1. - 0.25 * PI))); #endif } // This method returns the centerline spacing between an extrusion using this // flow and another one using another flow. // this->spacing(other) shall return the same value as other.spacing(*this) float Flow::spacing(const Flow &other) const { assert(this->height == other.height); assert(this->bridge == other.bridge); return float(this->bridge ? 0.5 * this->width + 0.5 * other.width + BRIDGE_EXTRA_SPACING : 0.5 * this->spacing() + 0.5 * other.spacing()); } // This method returns extrusion volume per head move unit. double Flow::mm3_per_mm() const { double res = this->bridge ? // Area of a circle with dmr of this->width. (this->width * this->width) * 0.25 * PI : // Rectangle with semicircles at the ends. ~ h (w - 0.215 h) this->height * (this->width - this->height * (1. - 0.25 * PI)); assert(res > 0.); return res; } Flow support_material_flow(const PrintObject *object, float layer_height) { return Flow::new_from_config_width( frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (object->config.support_material_extrusion_width.value > 0) ? object->config.support_material_extrusion_width : object->config.extrusion_width, // if object->config.support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), (layer_height > 0.f) ? layer_height : float(object->config.layer_height.value), false); } Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) { const auto &width = (object->print()->config.first_layer_extrusion_width.value > 0) ? object->print()->config.first_layer_extrusion_width : object->config.support_material_extrusion_width; return Flow::new_from_config_width( frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (width.value > 0) ? width : object->config.extrusion_width, float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), (layer_height > 0.f) ? layer_height : float(object->config.first_layer_height.get_abs_value(object->config.layer_height.value)), false); } Flow support_material_interface_flow(const PrintObject *object, float layer_height) { return Flow::new_from_config_width( frSupportMaterialInterface, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (object->config.support_material_extrusion_width > 0) ? object->config.support_material_extrusion_width : object->config.extrusion_width, // if object->config.support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_interface_extruder-1)), (layer_height > 0.f) ? layer_height : float(object->config.layer_height.value), false); } } Slic3r-version_1.39.1/xs/src/libslic3r/Flow.hpp000066400000000000000000000043141324354444700212540ustar00rootroot00000000000000#ifndef slic3r_Flow_hpp_ #define slic3r_Flow_hpp_ #include "libslic3r.h" #include "Config.hpp" #include "ExtrusionEntity.hpp" namespace Slic3r { class PrintObject; // Extra spacing of bridge threads, in mm. #define BRIDGE_EXTRA_SPACING 0.05 // Overlap factor of perimeter lines. Currently no overlap. #ifdef HAS_PERIMETER_LINE_OVERLAP #define PERIMETER_LINE_OVERLAP_FACTOR 1.0 #endif enum FlowRole { frExternalPerimeter, frPerimeter, frInfill, frSolidInfill, frTopSolidInfill, frSupportMaterial, frSupportMaterialInterface, }; class Flow { public: // Non bridging flow: Maximum width of an extrusion with semicircles at the ends. // Bridging flow: Bridge thread diameter. float width; // Non bridging flow: Layer height. // Bridging flow: Bridge thread diameter = layer height. float height; // Nozzle diameter. float nozzle_diameter; // Is it a bridge? bool bridge; Flow(float _w, float _h, float _nd, bool _bridge = false) : width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {}; float spacing() const; float spacing(const Flow &other) const; double mm3_per_mm() const; coord_t scaled_width() const { return coord_t(scale_(this->width)); }; coord_t scaled_spacing() const { return coord_t(scale_(this->spacing())); }; coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); }; static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio); // Create a flow from the spacing of extrusion lines. // This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale // to fit a region with integer number of lines. static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); }; extern Flow support_material_flow(const PrintObject *object, float layer_height = 0.f); extern Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height = 0.f); extern Flow support_material_interface_flow(const PrintObject *object, float layer_height = 0.f); } #endif Slic3r-version_1.39.1/xs/src/libslic3r/Format/000077500000000000000000000000001324354444700210625ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/libslic3r/Format/AMF.cpp000066400000000000000000000605301324354444700221750ustar00rootroot00000000000000#include #include #include #include #include #include "../libslic3r.h" #include "../Model.hpp" #include "AMF.hpp" #if 0 // Enable debugging and assert in this file. #define DEBUG #define _DEBUG #undef NDEBUG #endif #include namespace Slic3r { struct AMFParserContext { AMFParserContext(XML_Parser parser, Model *model) : m_parser(parser), m_model(*model), m_object(nullptr), m_volume(nullptr), m_material(nullptr), m_instance(nullptr) { m_path.reserve(12); } void stop() { XML_StopParser(m_parser, 0); } void startElement(const char *name, const char **atts); void endElement(const char *name); void endDocument(); void characters(const XML_Char *s, int len); static void XMLCALL startElement(void *userData, const char *name, const char **atts) { AMFParserContext *ctx = (AMFParserContext*)userData; ctx->startElement(name, atts); } static void XMLCALL endElement(void *userData, const char *name) { AMFParserContext *ctx = (AMFParserContext*)userData; ctx->endElement(name); } /* s is not 0 terminated. */ static void XMLCALL characters(void *userData, const XML_Char *s, int len) { AMFParserContext *ctx = (AMFParserContext*)userData; ctx->characters(s, len); } static const char* get_attribute(const char **atts, const char *id) { if (atts == nullptr) return nullptr; while (*atts != nullptr) { if (strcmp(*(atts ++), id) == 0) return *atts; ++ atts; } return nullptr; } enum AMFNodeType { NODE_TYPE_INVALID = 0, NODE_TYPE_UNKNOWN, NODE_TYPE_AMF, // amf // amf/metadata NODE_TYPE_MATERIAL, // amf/material // amf/material/metadata NODE_TYPE_OBJECT, // amf/object // amf/object/metadata NODE_TYPE_MESH, // amf/object/mesh NODE_TYPE_VERTICES, // amf/object/mesh/vertices NODE_TYPE_VERTEX, // amf/object/mesh/vertices/vertex NODE_TYPE_COORDINATES, // amf/object/mesh/vertices/vertex/coordinates NODE_TYPE_COORDINATE_X, // amf/object/mesh/vertices/vertex/coordinates/x NODE_TYPE_COORDINATE_Y, // amf/object/mesh/vertices/vertex/coordinates/y NODE_TYPE_COORDINATE_Z, // amf/object/mesh/vertices/vertex/coordinates/z NODE_TYPE_VOLUME, // amf/object/mesh/volume // amf/object/mesh/volume/metadata NODE_TYPE_TRIANGLE, // amf/object/mesh/volume/triangle NODE_TYPE_VERTEX1, // amf/object/mesh/volume/triangle/v1 NODE_TYPE_VERTEX2, // amf/object/mesh/volume/triangle/v2 NODE_TYPE_VERTEX3, // amf/object/mesh/volume/triangle/v3 NODE_TYPE_CONSTELLATION, // amf/constellation NODE_TYPE_INSTANCE, // amf/constellation/instance NODE_TYPE_DELTAX, // amf/constellation/instance/deltax NODE_TYPE_DELTAY, // amf/constellation/instance/deltay NODE_TYPE_RZ, // amf/constellation/instance/rz NODE_TYPE_SCALE, // amf/constellation/instance/scale NODE_TYPE_METADATA, // anywhere under amf/*/metadata }; struct Instance { Instance() : deltax_set(false), deltay_set(false), rz_set(false), scale_set(false) {} // Shift in the X axis. float deltax; bool deltax_set; // Shift in the Y axis. float deltay; bool deltay_set; // Rotation around the Z axis. float rz; bool rz_set; // Scaling factor float scale; bool scale_set; }; struct Object { Object() : idx(-1) {} int idx; std::vector instances; }; // Current Expat XML parser instance. XML_Parser m_parser; // Model to receive objects extracted from an AMF file. Model &m_model; // Current parsing path in the XML file. std::vector m_path; // Current object allocated for an amf/object XML subtree. ModelObject *m_object; // Map from obect name to object idx & instances. std::map m_object_instances_map; // Vertices parsed for the current m_object. std::vector m_object_vertices; // Current volume allocated for an amf/object/mesh/volume subtree. ModelVolume *m_volume; // Faces collected for the current m_volume. std::vector m_volume_facets; // Current material allocated for an amf/metadata subtree. ModelMaterial *m_material; // Current instance allocated for an amf/constellation/instance subtree. Instance *m_instance; // Generic string buffer for vertices, face indices, metadata etc. std::string m_value[3]; private: AMFParserContext& operator=(AMFParserContext&); }; void AMFParserContext::startElement(const char *name, const char **atts) { AMFNodeType node_type_new = NODE_TYPE_UNKNOWN; switch (m_path.size()) { case 0: // An AMF file must start with an tag. node_type_new = NODE_TYPE_AMF; if (strcmp(name, "amf") != 0) this->stop(); break; case 1: if (strcmp(name, "metadata") == 0) { const char *type = get_attribute(atts, "type"); if (type != nullptr) { m_value[0] = type; node_type_new = NODE_TYPE_METADATA; } } else if (strcmp(name, "material") == 0) { const char *material_id = get_attribute(atts, "id"); m_material = m_model.add_material((material_id == nullptr) ? "_" : material_id); node_type_new = NODE_TYPE_MATERIAL; } else if (strcmp(name, "object") == 0) { const char *object_id = get_attribute(atts, "id"); if (object_id == nullptr) this->stop(); else { assert(m_object_vertices.empty()); m_object = m_model.add_object(); m_object_instances_map[object_id].idx = int(m_model.objects.size())-1; node_type_new = NODE_TYPE_OBJECT; } } else if (strcmp(name, "constellation") == 0) { node_type_new = NODE_TYPE_CONSTELLATION; } break; case 2: if (strcmp(name, "metadata") == 0) { if (m_path[1] == NODE_TYPE_MATERIAL || m_path[1] == NODE_TYPE_OBJECT) { m_value[0] = get_attribute(atts, "type"); node_type_new = NODE_TYPE_METADATA; } } else if (strcmp(name, "mesh") == 0) { if (m_path[1] == NODE_TYPE_OBJECT) node_type_new = NODE_TYPE_MESH; } else if (strcmp(name, "instance") == 0) { if (m_path[1] == NODE_TYPE_CONSTELLATION) { const char *object_id = get_attribute(atts, "objectid"); if (object_id == nullptr) this->stop(); else { m_object_instances_map[object_id].instances.push_back(AMFParserContext::Instance()); m_instance = &m_object_instances_map[object_id].instances.back(); node_type_new = NODE_TYPE_INSTANCE; } } else this->stop(); } break; case 3: if (m_path[2] == NODE_TYPE_MESH) { assert(m_object); if (strcmp(name, "vertices") == 0) node_type_new = NODE_TYPE_VERTICES; else if (strcmp(name, "volume") == 0) { assert(! m_volume); m_volume = m_object->add_volume(TriangleMesh()); node_type_new = NODE_TYPE_VOLUME; } } else if (m_path[2] == NODE_TYPE_INSTANCE) { assert(m_instance); if (strcmp(name, "deltax") == 0) node_type_new = NODE_TYPE_DELTAX; else if (strcmp(name, "deltay") == 0) node_type_new = NODE_TYPE_DELTAY; else if (strcmp(name, "rz") == 0) node_type_new = NODE_TYPE_RZ; else if (strcmp(name, "scale") == 0) node_type_new = NODE_TYPE_SCALE; } break; case 4: if (m_path[3] == NODE_TYPE_VERTICES) { if (strcmp(name, "vertex") == 0) node_type_new = NODE_TYPE_VERTEX; } else if (m_path[3] == NODE_TYPE_VOLUME) { if (strcmp(name, "metadata") == 0) { const char *type = get_attribute(atts, "type"); if (type == nullptr) this->stop(); else { m_value[0] = type; node_type_new = NODE_TYPE_METADATA; } } else if (strcmp(name, "triangle") == 0) node_type_new = NODE_TYPE_TRIANGLE; } break; case 5: if (strcmp(name, "coordinates") == 0) { if (m_path[4] == NODE_TYPE_VERTEX) { node_type_new = NODE_TYPE_COORDINATES; } else this->stop(); } else if (name[0] == 'v' && name[1] >= '1' && name[1] <= '3' && name[2] == 0) { if (m_path[4] == NODE_TYPE_TRIANGLE) { node_type_new = AMFNodeType(NODE_TYPE_VERTEX1 + name[1] - '1'); } else this->stop(); } break; case 6: if ((name[0] == 'x' || name[0] == 'y' || name[0] == 'z') && name[1] == 0) { if (m_path[5] == NODE_TYPE_COORDINATES) node_type_new = AMFNodeType(NODE_TYPE_COORDINATE_X + name[0] - 'x'); else this->stop(); } break; default: break; } m_path.push_back(node_type_new); } void AMFParserContext::characters(const XML_Char *s, int len) { if (m_path.back() == NODE_TYPE_METADATA) { m_value[1].append(s, len); } else { switch (m_path.size()) { case 4: if (m_path.back() == NODE_TYPE_DELTAX || m_path.back() == NODE_TYPE_DELTAY || m_path.back() == NODE_TYPE_RZ || m_path.back() == NODE_TYPE_SCALE) m_value[0].append(s, len); break; case 6: switch (m_path.back()) { case NODE_TYPE_VERTEX1: m_value[0].append(s, len); break; case NODE_TYPE_VERTEX2: m_value[1].append(s, len); break; case NODE_TYPE_VERTEX3: m_value[2].append(s, len); break; default: break; } case 7: switch (m_path.back()) { case NODE_TYPE_COORDINATE_X: m_value[0].append(s, len); break; case NODE_TYPE_COORDINATE_Y: m_value[1].append(s, len); break; case NODE_TYPE_COORDINATE_Z: m_value[2].append(s, len); break; default: break; } default: break; } } } void AMFParserContext::endElement(const char * /* name */) { switch (m_path.back()) { // Constellation transformation: case NODE_TYPE_DELTAX: assert(m_instance); m_instance->deltax = float(atof(m_value[0].c_str())); m_instance->deltax_set = true; m_value[0].clear(); break; case NODE_TYPE_DELTAY: assert(m_instance); m_instance->deltay = float(atof(m_value[0].c_str())); m_instance->deltay_set = true; m_value[0].clear(); break; case NODE_TYPE_RZ: assert(m_instance); m_instance->rz = float(atof(m_value[0].c_str())); m_instance->rz_set = true; m_value[0].clear(); break; case NODE_TYPE_SCALE: assert(m_instance); m_instance->scale = float(atof(m_value[0].c_str())); m_instance->scale_set = true; m_value[0].clear(); break; // Object vertices: case NODE_TYPE_VERTEX: assert(m_object); // Parse the vertex data m_object_vertices.emplace_back(atof(m_value[0].c_str())); m_object_vertices.emplace_back(atof(m_value[1].c_str())); m_object_vertices.emplace_back(atof(m_value[2].c_str())); m_value[0].clear(); m_value[1].clear(); m_value[2].clear(); break; // Faces of the current volume: case NODE_TYPE_TRIANGLE: assert(m_object && m_volume); m_volume_facets.push_back(atoi(m_value[0].c_str())); m_volume_facets.push_back(atoi(m_value[1].c_str())); m_volume_facets.push_back(atoi(m_value[2].c_str())); m_value[0].clear(); m_value[1].clear(); m_value[2].clear(); break; // Closing the current volume. Create an STL from m_volume_facets pointing to m_object_vertices. case NODE_TYPE_VOLUME: { assert(m_object && m_volume); stl_file &stl = m_volume->mesh.stl; stl.stats.type = inmemory; stl.stats.number_of_facets = int(m_volume_facets.size() / 3); stl.stats.original_num_facets = stl.stats.number_of_facets; stl_allocate(&stl); for (size_t i = 0; i < m_volume_facets.size();) { stl_facet &facet = stl.facet_start[i/3]; for (unsigned int v = 0; v < 3; ++ v) memcpy(&facet.vertex[v].x, &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float)); } stl_get_size(&stl); m_volume->mesh.repair(); m_volume_facets.clear(); m_volume = nullptr; break; } case NODE_TYPE_OBJECT: assert(m_object); m_object_vertices.clear(); m_object = nullptr; break; case NODE_TYPE_MATERIAL: assert(m_material); m_material = nullptr; break; case NODE_TYPE_INSTANCE: assert(m_instance); m_instance = nullptr; break; case NODE_TYPE_METADATA: if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) { const char *opt_key = m_value[0].c_str() + 7; if (print_config_def.options.find(opt_key) != print_config_def.options.end()) { DynamicPrintConfig *config = nullptr; if (m_path.size() == 3) { if (m_path[1] == NODE_TYPE_MATERIAL && m_material) config = &m_material->config; else if (m_path[1] == NODE_TYPE_OBJECT && m_object) config = &m_object->config; } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) config = &m_volume->config; if (config) config->set_deserialize(opt_key, m_value[1]); } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) { // Parse object's layer height profile, a semicolon separated list of floats. char *p = const_cast(m_value[1].c_str()); for (;;) { char *end = strchr(p, ';'); if (end != nullptr) *end = 0; m_object->layer_height_profile.push_back(float(atof(p))); if (end == nullptr) break; p = end + 1; } m_object->layer_height_profile_valid = true; } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume && strcmp(opt_key, "modifier") == 0) { // Is this volume a modifier volume? m_volume->modifier = atoi(m_value[1].c_str()) == 1; } } else if (m_path.size() == 3) { if (m_path[1] == NODE_TYPE_MATERIAL) { if (m_material) m_material->attributes[m_value[0]] = m_value[1]; } else if (m_path[1] == NODE_TYPE_OBJECT) { if (m_object && m_value[0] == "name") m_object->name = std::move(m_value[1]); } } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) { if (m_volume && m_value[0] == "name") m_volume->name = std::move(m_value[1]); } m_value[0].clear(); m_value[1].clear(); break; default: break; } m_path.pop_back(); } void AMFParserContext::endDocument() { for (const auto &object : m_object_instances_map) { if (object.second.idx == -1) { printf("Undefined object %s referenced in constellation\n", object.first.c_str()); continue; } for (const Instance &instance : object.second.instances) if (instance.deltax_set && instance.deltay_set) { ModelInstance *mi = m_model.objects[object.second.idx]->add_instance(); mi->offset.x = instance.deltax; mi->offset.y = instance.deltay; mi->rotation = instance.rz_set ? instance.rz : 0.f; mi->scaling_factor = instance.scale_set ? instance.scale : 1.f; } } } // Load an AMF file into a provided model. bool load_amf(const char *path, Model *model) { XML_Parser parser = XML_ParserCreate(nullptr); // encoding if (! parser) { printf("Couldn't allocate memory for parser\n"); return false; } FILE *pFile = boost::nowide::fopen(path, "rt"); if (pFile == nullptr) { printf("Cannot open file %s\n", path); return false; } AMFParserContext ctx(parser, model); XML_SetUserData(parser, (void*)&ctx); XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement); XML_SetCharacterDataHandler(parser, AMFParserContext::characters); char buff[8192]; bool result = false; for (;;) { int len = (int)fread(buff, 1, 8192, pFile); if (ferror(pFile)) { printf("AMF parser: Read error\n"); break; } int done = feof(pFile); if (XML_Parse(parser, buff, len, done) == XML_STATUS_ERROR) { printf("AMF parser: Parse error at line %ul:\n%s\n", XML_GetCurrentLineNumber(parser), XML_ErrorString(XML_GetErrorCode(parser))); break; } if (done) { result = true; break; } } XML_ParserFree(parser); ::fclose(pFile); if (result) ctx.endDocument(); return result; } bool store_amf(const char *path, Model *model) { FILE *file = boost::nowide::fopen(path, "wb"); if (file == nullptr) return false; fprintf(file, "\n"); fprintf(file, "\n"); fprintf(file, "Slic3r %s\n", SLIC3R_VERSION); for (const auto &material : model->materials) { if (material.first.empty()) continue; // note that material-id must never be 0 since it's reserved by the AMF spec fprintf(file, " \n", material.first.c_str()); for (const auto &attr : material.second->attributes) fprintf(file, " %s\n", attr.first.c_str(), attr.second.c_str()); for (const std::string &key : material.second->config.keys()) fprintf(file, " %s\n", key.c_str(), material.second->config.serialize(key).c_str()); fprintf(file, " \n"); } std::string instances; for (size_t object_id = 0; object_id < model->objects.size(); ++ object_id) { ModelObject *object = model->objects[object_id]; fprintf(file, " \n", object_id); for (const std::string &key : object->config.keys()) fprintf(file, " %s\n", key.c_str(), object->config.serialize(key).c_str()); if (! object->name.empty()) fprintf(file, " %s\n", object->name.c_str()); std::vector layer_height_profile = object->layer_height_profile_valid ? object->layer_height_profile : std::vector(); if (layer_height_profile.size() >= 4 && (layer_height_profile.size() % 2) == 0) { // Store the layer height profile as a single semicolon separated list. fprintf(file, " "); fprintf(file, "%f", layer_height_profile.front()); for (size_t i = 1; i < layer_height_profile.size(); ++ i) fprintf(file, ";%f", layer_height_profile[i]); fprintf(file, "\n \n"); } //FIXME Store the layer height ranges (ModelObject::layer_height_ranges) fprintf(file, " \n"); fprintf(file, " \n"); std::vector vertices_offsets; int num_vertices = 0; for (ModelVolume *volume : object->volumes) { vertices_offsets.push_back(num_vertices); if (! volume->mesh.repaired) CONFESS("store_amf() requires repair()"); auto &stl = volume->mesh.stl; if (stl.v_shared == nullptr) stl_generate_shared_vertices(&stl); for (size_t i = 0; i < stl.stats.shared_vertices; ++ i) { fprintf(file, " \n"); fprintf(file, " \n"); fprintf(file, " %f\n", stl.v_shared[i].x); fprintf(file, " %f\n", stl.v_shared[i].y); fprintf(file, " %f\n", stl.v_shared[i].z); fprintf(file, " \n"); fprintf(file, " \n"); } num_vertices += stl.stats.shared_vertices; } fprintf(file, " \n"); for (size_t i_volume = 0; i_volume < object->volumes.size(); ++ i_volume) { ModelVolume *volume = object->volumes[i_volume]; int vertices_offset = vertices_offsets[i_volume]; if (volume->material_id().empty()) fprintf(file, " \n"); else fprintf(file, " \n", volume->material_id().c_str()); for (const std::string &key : volume->config.keys()) fprintf(file, " %s\n", key.c_str(), volume->config.serialize(key).c_str()); if (! volume->name.empty()) fprintf(file, " %s\n", volume->name.c_str()); if (volume->modifier) fprintf(file, " 1\n"); for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++ i) { fprintf(file, " \n"); for (int j = 0; j < 3; ++ j) fprintf(file, " %d\n", j+1, volume->mesh.stl.v_indices[i].vertex[j] + vertices_offset, j+1); fprintf(file, " \n"); } fprintf(file, " \n"); } fprintf(file, " \n"); fprintf(file, " \n"); if (! object->instances.empty()) { for (ModelInstance *instance : object->instances) { char buf[512]; sprintf(buf, " \n" " %lf\n" " %lf\n" " %lf\n" " %lf\n" " \n", object_id, instance->offset.x, instance->offset.y, instance->rotation, instance->scaling_factor); //FIXME missing instance->scaling_factor instances.append(buf); } } } if (! instances.empty()) { fprintf(file, " \n"); fwrite(instances.data(), instances.size(), 1, file); fprintf(file, " \n"); } fprintf(file, "\n"); fclose(file); return true; } }; // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/Format/AMF.hpp000066400000000000000000000004661324354444700222040ustar00rootroot00000000000000#ifndef slic3r_Format_AMF_hpp_ #define slic3r_Format_AMF_hpp_ namespace Slic3r { class Model; // Load an AMF file into a provided model. extern bool load_amf(const char *path, Model *model); extern bool store_amf(const char *path, Model *model); }; // namespace Slic3r #endif /* slic3r_Format_AMF_hpp_ */Slic3r-version_1.39.1/xs/src/libslic3r/Format/OBJ.cpp000066400000000000000000000104051324354444700222000ustar00rootroot00000000000000#include "../libslic3r.h" #include "../Model.hpp" #include "../TriangleMesh.hpp" #include "OBJ.hpp" #include "objparser.hpp" #include #ifdef _WIN32 #define DIR_SEPARATOR '\\' #else #define DIR_SEPARATOR '/' #endif namespace Slic3r { bool load_obj(const char *path, Model *model, const char *object_name_in) { // Parse the OBJ file. ObjParser::ObjData data; if (! ObjParser::objparse(path, data)) { // die "Failed to parse $file\n" if !-e $path; return false; } // Count the faces and verify, that all faces are triangular. size_t num_faces = 0; size_t num_quads = 0; for (size_t i = 0; i < data.vertices.size(); ) { size_t j = i; for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ; if (i == j) continue; size_t face_vertices = j - i; if (face_vertices != 3 && face_vertices != 4) { // Non-triangular and non-quad faces are not supported as of now. return false; } if (face_vertices == 4) ++ num_quads; ++ num_faces; i = j + 1; } // Convert ObjData into STL. TriangleMesh mesh; stl_file &stl = mesh.stl; stl.stats.type = inmemory; stl.stats.number_of_facets = int(num_faces + num_quads); stl.stats.original_num_facets = int(num_faces + num_quads); // stl_allocate clears all the allocated data to zero, all normals are set to zeros as well. stl_allocate(&stl); size_t i_face = 0; for (size_t i = 0; i < data.vertices.size(); ++ i) { if (data.vertices[i].coordIdx == -1) continue; stl_facet &facet = stl.facet_start[i_face ++]; size_t num_normals = 0; stl_normal normal = { 0.f }; for (unsigned int v = 0; v < 3; ++ v) { const ObjParser::ObjVertex &vertex = data.vertices[i++]; memcpy(&facet.vertex[v].x, &data.coordinates[vertex.coordIdx*4], 3 * sizeof(float)); if (vertex.normalIdx != -1) { normal.x += data.normals[vertex.normalIdx*3]; normal.y += data.normals[vertex.normalIdx*3+1]; normal.z += data.normals[vertex.normalIdx*3+2]; ++ num_normals; } } if (data.vertices[i].coordIdx != -1) { // This is a quad. Produce the other triangle. stl_facet &facet2 = stl.facet_start[i_face++]; facet2.vertex[0] = facet.vertex[0]; facet2.vertex[1] = facet.vertex[2]; const ObjParser::ObjVertex &vertex = data.vertices[i++]; memcpy(&facet2.vertex[2].x, &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float)); if (vertex.normalIdx != -1) { normal.x += data.normals[vertex.normalIdx*3]; normal.y += data.normals[vertex.normalIdx*3+1]; normal.z += data.normals[vertex.normalIdx*3+2]; ++ num_normals; } if (num_normals == 4) { // Normalize an average normal of a quad. float len = sqrt(facet.normal.x*facet.normal.x + facet.normal.y*facet.normal.y + facet.normal.z*facet.normal.z); if (len > EPSILON) { normal.x /= len; normal.y /= len; normal.z /= len; facet.normal = normal; facet2.normal = normal; } } } else if (num_normals == 3) { // Normalize an average normal of a triangle. float len = sqrt(facet.normal.x*facet.normal.x + facet.normal.y*facet.normal.y + facet.normal.z*facet.normal.z); if (len > EPSILON) { normal.x /= len; normal.y /= len; normal.z /= len; facet.normal = normal; } } } stl_get_size(&stl); mesh.repair(); if (mesh.facets_count() == 0) { // die "This STL file couldn't be read because it's empty.\n" return false; } std::string object_name; if (object_name_in == nullptr) { const char *last_slash = strrchr(path, DIR_SEPARATOR); object_name.assign((last_slash == nullptr) ? path : last_slash + 1); } else object_name.assign(object_name_in); model->add_object(object_name.c_str(), path, std::move(mesh)); return true; } }; // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/Format/OBJ.hpp000066400000000000000000000004661324354444700222130ustar00rootroot00000000000000#ifndef slic3r_Format_OBJ_hpp_ #define slic3r_Format_OBJ_hpp_ namespace Slic3r { class TriangleMesh; class Model; // Load an OBJ file into a provided model. extern bool load_obj(const char *path, Model *model, const char *object_name = nullptr); }; // namespace Slic3r #endif /* slic3r_Format_OBJ_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/Format/PRUS.cpp000066400000000000000000000412061324354444700223620ustar00rootroot00000000000000#ifdef SLIC3R_PRUS #include #include #include #include #include #include #include "../libslic3r.h" #include "../Model.hpp" #include "PRUS.hpp" #if 0 // Enable debugging and assert in this file. #define DEBUG #define _DEBUG #undef NDEBUG #endif #include namespace Slic3r { struct StlHeader { char comment[80]; uint32_t nTriangles; }; static_assert(sizeof(StlHeader) == 84, "StlHeader size not correct"); // Buffered line reader for the wxInputStream. class LineReader { public: LineReader(wxInputStream &input_stream, const char *initial_data, int initial_len) : m_input_stream(input_stream), m_pos(0), m_len(initial_len) { assert(initial_len >= 0 && initial_len < m_bufsize); memcpy(m_buffer, initial_data, initial_len); } const char* next_line() { for (;;) { // Skip empty lines. while (m_pos < m_len && (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n')) ++ m_pos; if (m_pos == m_len) { // Empty buffer, fill it from the input stream. m_pos = 0; m_input_stream.Read(m_buffer, m_bufsize - 1); m_len = m_input_stream.LastRead(); assert(m_len >= 0 && m_len < m_bufsize); if (m_len == 0) // End of file. return nullptr; // Skip empty lines etc. continue; } // The buffer is nonempty and it does not start with end of lines. Find the first end of line. int end = m_pos + 1; while (end < m_len && m_buffer[end] != '\r' && m_buffer[end] != '\n') ++ end; if (end == m_len && ! m_input_stream.Eof() && m_len < m_bufsize) { // Move the buffer content to the buffer start and fill the rest of the buffer. assert(m_pos > 0); memmove(m_buffer, m_buffer + m_pos, m_len - m_pos); m_len -= m_pos; assert(m_len >= 0 && m_len < m_bufsize); m_pos = 0; m_input_stream.Read(m_buffer + m_len, m_bufsize - 1 - m_len); int new_data = m_input_stream.LastRead(); if (new_data > 0) { m_len += new_data; assert(m_len >= 0 && m_len < m_bufsize); continue; } } char *ptr_out = m_buffer + m_pos; m_pos = end + 1; m_buffer[end] = 0; if (m_pos >= m_len) { m_pos = 0; m_len = 0; } return ptr_out; } } int next_line_scanf(const char *format, ...) { const char *line = next_line(); if (line == nullptr) return -1; int result; va_list arglist; va_start(arglist, format); result = vsscanf(line, format, arglist); va_end(arglist); return result; } private: wxInputStream &m_input_stream; static const int m_bufsize = 4096; char m_buffer[m_bufsize]; int m_pos = 0; int m_len = 0; }; // Load a PrusaControl project file into a provided model. bool load_prus(const char *path, Model *model) { // To receive the content of the zipped 'scene.xml' file. std::vector scene_xml_data; wxFFileInputStream in( #ifdef WIN32 // On Windows, convert to a 16bit unicode string. boost::nowide::widen(path).c_str() #else path #endif ); wxZipInputStream zip(in); std::unique_ptr entry; size_t num_models = 0; std::map group_to_model_object; while (entry.reset(zip.GetNextEntry()), entry.get() != NULL) { wxString name = entry->GetName(); if (name == "scene.xml") { if (! scene_xml_data.empty()) { // scene.xml has been found more than once in the archive. return false; } size_t size_last = 0; size_t size_incr = 4096; scene_xml_data.resize(size_incr); while (! zip.Read(scene_xml_data.data() + size_last, size_incr).Eof()) { size_last += zip.LastRead(); if (scene_xml_data.size() < size_last + size_incr) scene_xml_data.resize(size_last + size_incr); } size_last += zip.LastRead(); if (scene_xml_data.size() == size_last) scene_xml_data.resize(size_last + 1); else if (scene_xml_data.size() > size_last + 1) scene_xml_data.erase(scene_xml_data.begin() + size_last + 1, scene_xml_data.end()); scene_xml_data[size_last] = 0; } else if (name.EndsWith(".stl") || name.EndsWith(".STL")) { // Find the model entry in the XML data. const wxScopedCharBuffer name_utf8 = name.ToUTF8(); char model_name_tag[1024]; sprintf(model_name_tag, "", name_utf8.data()); const char *model_xml = strstr(scene_xml_data.data(), model_name_tag); const char *zero_tag = ""; const char *zero_xml = strstr(scene_xml_data.data(), zero_tag); float trafo[3][4] = { 0 }; double instance_rotation = 0.; double instance_scaling_factor = 1.f; Pointf instance_offset(0., 0.); bool trafo_set = false; unsigned int group_id = (unsigned int)-1; unsigned int extruder_id = (unsigned int)-1; ModelObject *model_object = nullptr; if (model_xml != nullptr) { model_xml += strlen(model_name_tag); const char *position_tag = ""; const char *position_xml = strstr(model_xml, position_tag); const char *rotation_tag = ""; const char *rotation_xml = strstr(model_xml, rotation_tag); const char *scale_tag = ""; const char *scale_xml = strstr(model_xml, scale_tag); float position[3], rotation[3], scale[3], zero[3]; if (position_xml != nullptr && rotation_xml != nullptr && scale_xml != nullptr && zero_xml != nullptr && sscanf(position_xml+strlen(position_tag), "[%f, %f, %f]", position, position+1, position+2) == 3 && sscanf(rotation_xml+strlen(rotation_tag), "[%f, %f, %f]", rotation, rotation+1, rotation+2) == 3 && sscanf(scale_xml+strlen(scale_tag), "[%f, %f, %f]", scale, scale+1, scale+2) == 3 && sscanf(zero_xml+strlen(zero_tag), "[%f, %f, %f]", zero, zero+1, zero+2) == 3) { if (scale[0] == scale[1] && scale[1] == scale[2]) { instance_scaling_factor = scale[0]; scale[0] = scale[1] = scale[2] = 1.; } if (rotation[0] == 0. && rotation[1] == 0.) { instance_rotation = - rotation[2]; rotation[2] = 0.; } Eigen::Matrix3f mat_rot, mat_scale, mat_trafo; mat_rot = Eigen::AngleAxisf(-rotation[2], Eigen::Vector3f::UnitZ()) * Eigen::AngleAxisf(-rotation[1], Eigen::Vector3f::UnitY()) * Eigen::AngleAxisf(-rotation[0], Eigen::Vector3f::UnitX()); mat_scale = Eigen::Scaling(scale[0], scale[1], scale[2]); mat_trafo = mat_rot * mat_scale; for (size_t r = 0; r < 3; ++ r) { for (size_t c = 0; c < 3; ++ c) trafo[r][c] += mat_trafo(r, c); } instance_offset.x = position[0] - zero[0]; instance_offset.y = position[1] - zero[1]; trafo[2][3] = position[2] / instance_scaling_factor; trafo_set = true; } const char *group_tag = ""; const char *group_xml = strstr(model_xml, group_tag); const char *extruder_tag = ""; const char *extruder_xml = strstr(model_xml, extruder_tag); if (group_xml != nullptr) { int group = atoi(group_xml + strlen(group_tag)); if (group > 0) { group_id = group; auto it = group_to_model_object.find(group_id); if (it != group_to_model_object.end()) model_object = it->second; } } if (extruder_xml != nullptr) { int e = atoi(extruder_xml + strlen(extruder_tag)); if (e > 0) extruder_id = e; } } if (trafo_set) { // Extract the STL. StlHeader header; TriangleMesh mesh; bool mesh_valid = false; bool stl_ascii = false; if (!zip.Read((void*)&header, sizeof(StlHeader)).Eof()) { if (strncmp(header.comment, "solid ", 6) == 0) stl_ascii = true; else { // Header has been extracted. Now read the faces. stl_file &stl = mesh.stl; stl.error = 0; stl.stats.type = inmemory; stl.stats.number_of_facets = header.nTriangles; stl.stats.original_num_facets = header.nTriangles; stl_allocate(&stl); if (header.nTriangles > 0 && zip.ReadAll((void*)stl.facet_start, 50 * header.nTriangles)) { if (sizeof(stl_facet) > SIZEOF_STL_FACET) { // The stl.facet_start is not packed tightly. Unpack the array of stl_facets. unsigned char *data = (unsigned char*)stl.facet_start; for (size_t i = header.nTriangles - 1; i > 0; -- i) memmove(data + i * sizeof(stl_facet), data + i * SIZEOF_STL_FACET, SIZEOF_STL_FACET); } // All the faces have been read. stl_get_size(&stl); mesh.repair(); // Transform the model. stl_transform(&stl, &trafo[0][0]); if (std::abs(stl.stats.min.z) < EPSILON) stl.stats.min.z = 0.; // Add a mesh to a model. if (mesh.facets_count() > 0) mesh_valid = true; } } } else stl_ascii = true; if (stl_ascii) { // Try to parse ASCII STL. char normal_buf[3][32]; stl_facet facet; std::vector facets; LineReader line_reader(zip, (char*)&header, zip.LastRead()); std::string solid_name; facet.extra[0] = facet.extra[1] = 0; for (;;) { const char *line = line_reader.next_line(); if (line == nullptr) // End of file. break; if (strncmp(line, "solid", 5) == 0) { // Opening the "solid" block. if (! solid_name.empty()) { // Error, solid block is already open. facets.clear(); break; } solid_name = line + 5; if (solid_name.empty()) solid_name = "unknown"; continue; } if (strncmp(line, "endsolid", 8) == 0) { // Closing the "solid" block. if (solid_name.empty()) { // Error, no solid block is open. facets.clear(); break; } solid_name.clear(); continue; } // Line has to start with the word solid. int res_normal = sscanf(line, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); assert(res_normal == 3); int res_outer_loop = line_reader.next_line_scanf(" outer loop"); assert(res_outer_loop == 0); int res_vertex1 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z); assert(res_vertex1 == 3); int res_vertex2 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z); assert(res_vertex2 == 3); int res_vertex3 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z); assert(res_vertex3 == 3); int res_endloop = line_reader.next_line_scanf(" endloop"); assert(res_endloop == 0); int res_endfacet = line_reader.next_line_scanf(" endfacet"); if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { // perror("Something is syntactically very wrong with this ASCII STL!"); facets.clear(); break; } // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. if (sscanf(normal_buf[0], "%f", &facet.normal.x) != 1 || sscanf(normal_buf[1], "%f", &facet.normal.y) != 1 || sscanf(normal_buf[2], "%f", &facet.normal.z) != 1) { // Normal was mangled. Maybe denormals or "not a number" were stored? // Just reset the normal and silently ignore it. memset(&facet.normal, 0, sizeof(facet.normal)); } facets.emplace_back(facet); } if (! facets.empty() && solid_name.empty()) { stl_file &stl = mesh.stl; stl.stats.type = inmemory; stl.stats.number_of_facets = facets.size(); stl.stats.original_num_facets = facets.size(); stl_allocate(&stl); memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50); stl_get_size(&stl); mesh.repair(); // Transform the model. stl_transform(&stl, &trafo[0][0]); // Add a mesh to a model. if (mesh.facets_count() > 0) mesh_valid = true; } } if (mesh_valid) { // Add this mesh to the model. ModelVolume *volume = nullptr; if (model_object == nullptr) { // This is a first mesh of a group. Create a new object & volume. model_object = model->add_object(name_utf8.data(), path, std::move(mesh)); volume = model_object->volumes.front(); ModelInstance *instance = model_object->add_instance(); instance->rotation = instance_rotation; instance->scaling_factor = instance_scaling_factor; instance->offset = instance_offset; ++ num_models; if (group_id != (size_t)-1) group_to_model_object[group_id] = model_object; } else { // This is not the 1st mesh of a group. Add it to the ModelObject. volume = model_object->add_volume(std::move(mesh)); volume->name = name_utf8.data(); } // Set the extruder to the volume. if (extruder_id != (unsigned int)-1) { char str_extruder[64]; sprintf(str_extruder, "%ud", extruder_id); volume->config.set_deserialize("extruder", str_extruder); } } } } } return num_models > 0; } }; // namespace Slic3r #endif /* SLIC3R_PRUS */ Slic3r-version_1.39.1/xs/src/libslic3r/Format/PRUS.hpp000066400000000000000000000005401324354444700223630ustar00rootroot00000000000000#if defined(SLIC3R_PRUS) && ! defined(slic3r_Format_PRUS_hpp_) #define slic3r_Format_PRUS_hpp_ namespace Slic3r { class TriangleMesh; class Model; // Load a PrusaControl project file into a provided model. extern bool load_prus(const char *path, Model *model); }; // namespace Slic3r #endif /* SLIC3R_PRUS && ! defined(slic3r_Format_PRUS_hpp_) */ Slic3r-version_1.39.1/xs/src/libslic3r/Format/STL.cpp000066400000000000000000000025361324354444700222360ustar00rootroot00000000000000#include "../libslic3r.h" #include "../Model.hpp" #include "../TriangleMesh.hpp" #include "STL.hpp" #include #ifdef _WIN32 #define DIR_SEPARATOR '\\' #else #define DIR_SEPARATOR '/' #endif namespace Slic3r { bool load_stl(const char *path, Model *model, const char *object_name_in) { TriangleMesh mesh; mesh.ReadSTLFile(path); if (mesh.stl.error) { // die "Failed to open $file\n" if !-e $path; return false; } mesh.repair(); if (mesh.facets_count() == 0) { // die "This STL file couldn't be read because it's empty.\n" return false; } std::string object_name; if (object_name_in == nullptr) { const char *last_slash = strrchr(path, DIR_SEPARATOR); object_name.assign((last_slash == nullptr) ? path : last_slash + 1); } else object_name.assign(object_name_in); model->add_object(object_name.c_str(), path, std::move(mesh)); return true; } bool store_stl(const char *path, TriangleMesh *mesh, bool binary) { if (binary) mesh->write_binary(path); else mesh->write_ascii(path); //FIXME returning false even if write failed. return true; } bool store_stl(const char *path, ModelObject *model_object, bool binary) { TriangleMesh mesh = model_object->mesh(); return store_stl(path, &mesh, binary); } }; // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/Format/STL.hpp000066400000000000000000000007301324354444700222350ustar00rootroot00000000000000#ifndef slic3r_Format_STL_hpp_ #define slic3r_Format_STL_hpp_ namespace Slic3r { class TriangleMesh; class ModelObject; // Load an STL file into a provided model. extern bool load_stl(const char *path, Model *model, const char *object_name = nullptr); extern bool store_stl(const char *path, TriangleMesh *mesh, bool binary); extern bool store_stl(const char *path, ModelObject *model_object, bool binary); }; // namespace Slic3r #endif /* slic3r_Format_STL_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/Format/objparser.cpp000066400000000000000000000323101324354444700235540ustar00rootroot00000000000000#include #include #include #include "objparser.hpp" namespace ObjParser { static bool obj_parseline(const char *line, ObjData &data) { #define EATWS() while (*line == ' ' || *line == '\t') ++ line if (*line == 0) return true; // Ignore whitespaces at the beginning of the line. //FIXME is this a good idea? EATWS(); char c1 = *line ++; switch (c1) { case '#': // Comment, ignore the rest of the line. break; case 'v': { // Parse vertex geometry (position, normal, texture coordinates) char c2 = *line ++; switch (c2) { case 't': { // vt - vertex texture parameter // u v [w], w == 0 (or w == 1) char c2 = *line ++; if (c2 != ' ' && c2 != '\t') return false; EATWS(); char *endptr = 0; double u = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; line = endptr; EATWS(); double v = 0; if (*line != 0) { v = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); } double w = 0; if (*line != 0) { w = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); } if (*line != 0) return false; data.textureCoordinates.push_back((float)u); data.textureCoordinates.push_back((float)v); data.textureCoordinates.push_back((float)w); break; } case 'n': { // vn - vertex normal // x y z char c2 = *line ++; if (c2 != ' ' && c2 != '\t') return false; EATWS(); char *endptr = 0; double x = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; line = endptr; EATWS(); double y = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; line = endptr; EATWS(); double z = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); if (*line != 0) return false; data.normals.push_back((float)x); data.normals.push_back((float)y); data.normals.push_back((float)z); break; } case 'p': { // vp - vertex parameter char c2 = *line ++; if (c2 != ' ' && c2 != '\t') return false; EATWS(); char *endptr = 0; double u = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); double v = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); double w = 0; if (*line != 0) { w = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); } if (*line != 0) return false; data.parameters.push_back((float)u); data.parameters.push_back((float)v); data.parameters.push_back((float)w); break; } default: { // v - vertex geometry if (c2 != ' ' && c2 != '\t') return false; EATWS(); char *endptr = 0; double x = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; line = endptr; EATWS(); double y = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t')) return false; line = endptr; EATWS(); double z = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); double w = 1.0; if (*line != 0) { w = strtod(line, &endptr); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); } if (*line != 0) return false; data.coordinates.push_back((float)x); data.coordinates.push_back((float)y); data.coordinates.push_back((float)z); data.coordinates.push_back((float)w); break; } } break; } case 'f': { // face EATWS(); if (*line == 0) return false; // number of vertices of this face int n = 0; // current vertex to be parsed ObjVertex vertex; char *endptr = 0; while (*line != 0) { // Parse a single vertex reference. vertex.coordIdx = 0; vertex.normalIdx = 0; vertex.textureCoordIdx = 0; vertex.coordIdx = strtol(line, &endptr, 10); // Coordinate has to be defined if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != '/' && *endptr != 0)) return false; line = endptr; if (*line == '/') { ++ line; // Texture coordinate index may be missing after a 1st slash, but then the normal index has to be present. if (*line != '/') { // Parse the texture coordinate index. vertex.textureCoordIdx = strtol(line, &endptr, 10); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != '/' && *endptr != 0)) return false; line = endptr; } if (*line == '/') { // Parse normal index. ++ line; vertex.normalIdx = strtol(line, &endptr, 10); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; } } if (vertex.coordIdx < 0) vertex.coordIdx += data.coordinates.size() / 4; else -- vertex.coordIdx; if (vertex.normalIdx < 0) vertex.normalIdx += data.normals.size() / 3; else -- vertex.normalIdx; if (vertex.textureCoordIdx < 0) vertex.textureCoordIdx += data.textureCoordinates.size() / 3; else -- vertex.textureCoordIdx; data.vertices.push_back(vertex); EATWS(); } vertex.coordIdx = -1; vertex.normalIdx = -1; vertex.textureCoordIdx = -1; data.vertices.push_back(vertex); break; } case 'm': { if (*(line ++) != 't' || *(line ++) != 'l' || *(line ++) != 'l' || *(line ++) != 'i' || *(line ++) != 'b') return false; // mtllib [external .mtl file name] // printf("mtllib %s\r\n", line); EATWS(); data.mtllibs.push_back(std::string(line)); break; } case 'u': { if (*(line ++) != 's' || *(line ++) != 'e' || *(line ++) != 'm' || *(line ++) != 't' || *(line ++) != 'l') return false; // usemtl [material name] // printf("usemtl %s\r\n", line); EATWS(); ObjUseMtl usemtl; usemtl.vertexIdxFirst = data.vertices.size(); usemtl.name = line; data.usemtls.push_back(usemtl); break; } case 'o': { // o [object name] EATWS(); const char *name = line; while (*line != ' ' && *line != '\t' && *line != 0) ++ line; // copy name to line. EATWS(); if (*line != 0) return false; ObjObject object; object.vertexIdxFirst = data.vertices.size(); object.name = line; data.objects.push_back(object); break; } case 'g': { // g [group name] // printf("group %s\r\n", line); ObjGroup group; group.vertexIdxFirst = data.vertices.size(); group.name = line; data.groups.push_back(group); break; } case 's': { // s 1 / off char c2 = *line ++; if (c2 != ' ' && c2 != '\t') return false; EATWS(); char *endptr = 0; long g = strtol(line, &endptr, 10); if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0)) return false; line = endptr; EATWS(); if (*line != 0) return false; ObjSmoothingGroup group; group.vertexIdxFirst = data.vertices.size(); group.smoothingGroupID = g; data.smoothingGroups.push_back(group); break; } default: printf("ObjParser: Unknown command: %c\r\n", c1); break; } return true; } bool objparse(const char *path, ObjData &data) { FILE *pFile = boost::nowide::fopen(path, "rt"); if (pFile == 0) return false; try { char buf[65536 * 2]; size_t len = 0; size_t lenPrev = 0; while ((len = ::fread(buf + lenPrev, 1, 65536, pFile)) != 0) { len += lenPrev; size_t lastLine = 0; for (size_t i = 0; i < len; ++ i) if (buf[i] == '\r' || buf[i] == '\n') { buf[i] = 0; char *c = buf + lastLine; while (*c == ' ' || *c == '\t') ++ c; obj_parseline(c, data); lastLine = i + 1; } lenPrev = len - lastLine; memmove(buf, buf + lastLine, lenPrev); } } catch (std::bad_alloc &ex) { printf("Out of memory\r\n"); } ::fclose(pFile); // printf("vertices: %d\r\n", data.vertices.size() / 4); // printf("coords: %d\r\n", data.coordinates.size()); return true; } template bool savevector(FILE *pFile, const std::vector &v) { size_t cnt = v.size(); ::fwrite(&cnt, 1, sizeof(cnt), pFile); //FIXME sizeof(T) works for data types leaving no gaps in the allocated vector because of alignment of the T type. if (! v.empty()) ::fwrite(&v.front(), 1, sizeof(T) * cnt, pFile); return true; } bool savevector(FILE *pFile, const std::vector &v) { size_t cnt = v.size(); ::fwrite(&cnt, 1, sizeof(cnt), pFile); for (size_t i = 0; i < cnt; ++ i) { size_t len = v[i].size(); ::fwrite(&len, 1, sizeof(cnt), pFile); ::fwrite(v[i].c_str(), 1, len, pFile); } return true; } template bool savevectornameidx(FILE *pFile, const std::vector &v) { size_t cnt = v.size(); ::fwrite(&cnt, 1, sizeof(cnt), pFile); for (size_t i = 0; i < cnt; ++ i) { ::fwrite(&v[i].vertexIdxFirst, 1, sizeof(int), pFile); size_t len = v[i].name.size(); ::fwrite(&len, 1, sizeof(cnt), pFile); ::fwrite(v[i].name.c_str(), 1, len, pFile); } return true; } template bool loadvector(FILE *pFile, std::vector &v) { v.clear(); size_t cnt = 0; if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1) return false; //FIXME sizeof(T) works for data types leaving no gaps in the allocated vector because of alignment of the T type. if (cnt != 0) { v.assign(cnt, T()); if (::fread(&v.front(), sizeof(T), cnt, pFile) != cnt) return false; } return true; } bool loadvector(FILE *pFile, std::vector &v) { v.clear(); size_t cnt = 0; if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1) return false; v.reserve(cnt); for (size_t i = 0; i < cnt; ++ i) { size_t len = 0; if (::fread(&len, sizeof(len), 1, pFile) != 1) return false; std::string s(" ", len); if (::fread(const_cast(s.c_str()), 1, len, pFile) != len) return false; v.push_back(std::move(s)); } return true; } template bool loadvectornameidx(FILE *pFile, std::vector &v) { v.clear(); size_t cnt = 0; if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1) return false; v.assign(cnt, T()); for (size_t i = 0; i < cnt; ++ i) { if (::fread(&v[i].vertexIdxFirst, sizeof(int), 1, pFile) != 1) return false; size_t len = 0; if (::fread(&len, sizeof(len), 1, pFile) != 1) return false; v[i].name.assign(" ", len); if (::fread(const_cast(v[i].name.c_str()), 1, len, pFile) != len) return false; } return true; } bool objbinsave(const char *path, const ObjData &data) { FILE *pFile = boost::nowide::fopen(path, "wb"); if (pFile == 0) return false; size_t version = 1; ::fwrite(&version, 1, sizeof(version), pFile); bool result = savevector(pFile, data.coordinates) && savevector(pFile, data.textureCoordinates) && savevector(pFile, data.normals) && savevector(pFile, data.parameters) && savevector(pFile, data.mtllibs) && savevectornameidx(pFile, data.usemtls) && savevectornameidx(pFile, data.objects) && savevectornameidx(pFile, data.groups) && savevector(pFile, data.smoothingGroups) && savevector(pFile, data.vertices); ::fclose(pFile); return result; } bool objbinload(const char *path, ObjData &data) { FILE *pFile = boost::nowide::fopen(path, "rb"); if (pFile == 0) return false; data.version = 0; if (::fread(&data.version, sizeof(data.version), 1, pFile) != 1) return false; if (data.version != 1) return false; bool result = loadvector(pFile, data.coordinates) && loadvector(pFile, data.textureCoordinates) && loadvector(pFile, data.normals) && loadvector(pFile, data.parameters) && loadvector(pFile, data.mtllibs) && loadvectornameidx(pFile, data.usemtls) && loadvectornameidx(pFile, data.objects) && loadvectornameidx(pFile, data.groups) && loadvector(pFile, data.smoothingGroups) && loadvector(pFile, data.vertices); ::fclose(pFile); return result; } template bool vectorequal(const std::vector &v1, const std::vector &v2) { if (v1.size() != v2.size()) return false; for (size_t i = 0; i < v1.size(); ++ i) if (! (v1[i] == v2[i])) return false; return true; } bool vectorequal(const std::vector &v1, const std::vector &v2) { if (v1.size() != v2.size()) return false; for (size_t i = 0; i < v1.size(); ++ i) if (v1[i].compare(v2[i]) != 0) return false; return true; } extern bool objequal(const ObjData &data1, const ObjData &data2) { //FIXME ignore version number // version; return vectorequal(data1.coordinates, data2.coordinates) && vectorequal(data1.textureCoordinates, data2.textureCoordinates) && vectorequal(data1.normals, data2.normals) && vectorequal(data1.parameters, data2.parameters) && vectorequal(data1.mtllibs, data2.mtllibs) && vectorequal(data1.usemtls, data2.usemtls) && vectorequal(data1.objects, data2.objects) && vectorequal(data1.groups, data2.groups) && vectorequal(data1.vertices, data2.vertices); } } // namespace ObjParser Slic3r-version_1.39.1/xs/src/libslic3r/Format/objparser.hpp000066400000000000000000000043341324354444700235660ustar00rootroot00000000000000#ifndef slic3r_Format_objparser_hpp_ #define slic3r_Format_objparser_hpp_ #include #include namespace ObjParser { struct ObjVertex { int coordIdx; int textureCoordIdx; int normalIdx; }; inline bool operator==(const ObjVertex &v1, const ObjVertex &v2) { return v1.coordIdx == v2.coordIdx && v1.textureCoordIdx == v2.textureCoordIdx && v1.normalIdx == v2.normalIdx; } struct ObjUseMtl { int vertexIdxFirst; std::string name; }; inline bool operator==(const ObjUseMtl &v1, const ObjUseMtl &v2) { return v1.vertexIdxFirst == v2.vertexIdxFirst && v1.name.compare(v2.name) == 0; } struct ObjObject { int vertexIdxFirst; std::string name; }; inline bool operator==(const ObjObject &v1, const ObjObject &v2) { return v1.vertexIdxFirst == v2.vertexIdxFirst && v1.name.compare(v2.name) == 0; } struct ObjGroup { int vertexIdxFirst; std::string name; }; inline bool operator==(const ObjGroup &v1, const ObjGroup &v2) { return v1.vertexIdxFirst == v2.vertexIdxFirst && v1.name.compare(v2.name) == 0; } struct ObjSmoothingGroup { int vertexIdxFirst; int smoothingGroupID; }; inline bool operator==(const ObjSmoothingGroup &v1, const ObjSmoothingGroup &v2) { return v1.vertexIdxFirst == v2.vertexIdxFirst && v1.smoothingGroupID == v2.smoothingGroupID; } struct ObjData { // Version of the data structure for load / store in the private binary format. int version; // x, y, z, w std::vector coordinates; // u, v, w std::vector textureCoordinates; // x, y, z std::vector normals; // u, v, w std::vector parameters; std::vector mtllibs; std::vector usemtls; std::vector objects; std::vector groups; std::vector smoothingGroups; // List of faces, delimited by an ObjVertex with all members set to -1. std::vector vertices; }; extern bool objparse(const char *path, ObjData &data); extern bool objbinsave(const char *path, const ObjData &data); extern bool objbinload(const char *path, ObjData &data); extern bool objequal(const ObjData &data1, const ObjData &data2); } // namespace ObjParser #endif /* slic3r_Format_objparser_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/GCode.cpp000066400000000000000000003423451324354444700213320ustar00rootroot00000000000000#include "GCode.hpp" #include "ExtrusionEntity.hpp" #include "EdgeGrid.hpp" #include "Geometry.hpp" #include "GCode/PrintExtents.hpp" #include "GCode/WipeTowerPrusaMM.hpp" #include "Utils.hpp" #include #include #include #include #include #include #include #include #include #include "SVG.hpp" #include #if 0 // Enable debugging and asserts, even in the release build. #define DEBUG #define _DEBUG #undef NDEBUG #endif #include namespace Slic3r { // Only add a newline in case the current G-code does not end with a newline. static inline void check_add_eol(std::string &gcode) { if (! gcode.empty() && gcode.back() != '\n') gcode += '\n'; } // Plan a travel move while minimizing the number of perimeter crossings. // point is in unscaled coordinates, in the coordinate system of the current active object // (set by gcodegen.set_origin()). Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point) { // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). // Otherwise perform the path planning in the coordinate system of the active object. bool use_external = this->use_external_mp || this->use_external_mp_once; Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin().x, gcodegen.origin().y) : Point(0, 0); Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())-> shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); if (use_external) result.translate(scaled_origin.negative()); return result; } std::string OozePrevention::pre_toolchange(GCode &gcodegen) { std::string gcode; // move to the nearest standby point if (!this->standby_points.empty()) { // get current position in print coordinates Pointf3 writer_pos = gcodegen.writer().get_position(); Point pos = Point::new_scale(writer_pos.x, writer_pos.y); // find standby point Point standby_point; pos.nearest_point(this->standby_points, &standby_point); /* We don't call gcodegen.travel_to() because we don't need retraction (it was already triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates of the destination point must not be transformed by origin nor current extruder offset. */ gcode += gcodegen.writer().travel_to_xy(Pointf::new_unscale(standby_point), "move to standby position"); } if (gcodegen.config().standby_temperature_delta.value != 0) { // we assume that heating is always slower than cooling, so no need to block gcode += gcodegen.writer().set_temperature (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false); } return gcode; } std::string OozePrevention::post_toolchange(GCode &gcodegen) { return (gcodegen.config().standby_temperature_delta.value != 0) ? gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true) : std::string(); } int OozePrevention::_get_temp(GCode &gcodegen) { return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0) ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id()) : gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id()); } std::string Wipe::wipe(GCode &gcodegen, bool toolchange) { std::string gcode; /* Reduce feedrate a bit; travel speed is often too high to move on existing material. Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */ double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8; // get the retraction length double length = toolchange ? gcodegen.writer().extruder()->retract_length_toolchange() : gcodegen.writer().extruder()->retract_length(); // Shorten the retraction length by the amount already retracted before wipe. length *= (1. - gcodegen.writer().extruder()->retract_before_wipe()); if (length > 0) { /* Calculate how long we need to travel in order to consume the required amount of retraction. In other words, how far do we move in XY at wipe_speed for the time needed to consume retract_length at retract_speed? */ double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed); /* Take the stored wipe path and replace first point with the current actual position (they might be different, for example, in case of loop clipping). */ Polyline wipe_path; wipe_path.append(gcodegen.last_pos()); wipe_path.append( this->path.points.begin() + 1, this->path.points.end() ); wipe_path.clip_end(wipe_path.length() - wipe_dist); // subdivide the retraction in segments for (const Line &line : wipe_path.lines()) { double segment_length = line.length(); /* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one due to rounding (TODO: test and/or better math for this) */ double dE = length * (segment_length / wipe_dist) * 0.95; //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle. // Is it here for the cooling markers? Or should it be outside of the cycle? gcode += gcodegen.writer().set_speed(wipe_speed*60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : ""); gcode += gcodegen.writer().extrude_to_xy( gcodegen.point_to_gcode(line.b), -dE, "wipe and retract" ); } // prevent wiping again on same path this->reset_path(); } return gcode; } static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const WipeTower::xy &wipe_tower_pt) { return Point(scale_(wipe_tower_pt.x - gcodegen.origin().x), scale_(wipe_tower_pt.y - gcodegen.origin().y)); } std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const { std::string gcode; // Disable linear advance for the wipe tower operations. gcode += "M900 K0\n"; // Move over the wipe tower. // Retract for a tool change, using the toolchange retract value and setting the priming extra length. gcode += gcodegen.retract(true); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; gcode += gcodegen.travel_to( wipe_tower_point_to_object_point(gcodegen, tcr.start_pos), erMixed, "Travel to a Wipe Tower"); gcode += gcodegen.unretract(); // Let the tool change be executed by the wipe tower class. // Inform the G-code writer about the changes done behind its back. gcode += tcr.gcode; // Let the m_writer know the current extruder_id, but ignore the generated G-code. if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) gcodegen.writer().toolchange(new_extruder_id); // Always append the filament start G-code even if the extruder did not switch, // because the wipe tower resets the linear advance and we want it to be re-enabled. const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id); if (! start_filament_gcode.empty()) { // Process the start_filament_gcode for the active filament only. gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); gcode += gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id); check_add_eol(gcode); } // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y)); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos)); // Prepare a future wipe. gcodegen.m_wipe.path.points.clear(); if (new_extruder_id >= 0) { // Start the wipe at the current position. gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos)); // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, WipeTower::xy((std::abs(m_left - tcr.end_pos.x) < std::abs(m_right - tcr.end_pos.x)) ? m_right : m_left, tcr.end_pos.y))); } // Let the planner know we are traveling between objects. gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; return gcode; } std::string WipeTowerIntegration::prime(GCode &gcodegen) { assert(m_layer_idx == 0); std::string gcode; if (&m_priming != nullptr && ! m_priming.extrusions.empty()) { // Disable linear advance for the wipe tower operations. gcode += "M900 K0\n"; // Let the tool change be executed by the wipe tower class. // Inform the G-code writer about the changes done behind its back. gcode += m_priming.gcode; // Let the m_writer know the current extruder_id, but ignore the generated G-code. unsigned int current_extruder_id = m_priming.extrusions.back().tool; gcodegen.writer().toolchange(current_extruder_id); gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(Pointf(m_priming.end_pos.x, m_priming.end_pos.y)); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); // Prepare a future wipe. gcodegen.m_wipe.path.points.clear(); // Start the wipe at the current position. gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, WipeTower::xy((std::abs(m_left - m_priming.end_pos.x) < std::abs(m_right - m_priming.end_pos.x)) ? m_right : m_left, m_priming.end_pos.y))); } return gcode; } std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) { std::string gcode; assert(m_layer_idx >= 0 && m_layer_idx <= m_tool_changes.size()); if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { if (m_layer_idx < m_tool_changes.size()) { assert(m_tool_change_idx < m_tool_changes[m_layer_idx].size()); gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id); } m_brim_done = true; } return gcode; } // Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower. std::string WipeTowerIntegration::finalize(GCode &gcodegen) { std::string gcode; if (std::abs(gcodegen.writer().get_position().z - m_final_purge.print_z) > EPSILON) gcode += gcodegen.change_layer(m_final_purge.print_z); gcode += append_tcr(gcodegen, m_final_purge, -1); return gcode; } #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id()) // Collect pairs of object_layer + support_layer sorted by print_z. // object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. std::vector GCode::collect_layers_to_print(const PrintObject &object) { std::vector layers_to_print; layers_to_print.reserve(object.layers.size() + object.support_layers.size()); // Pair the object layers with the support layers by z. size_t idx_object_layer = 0; size_t idx_support_layer = 0; while (idx_object_layer < object.layers.size() || idx_support_layer < object.support_layers.size()) { LayerToPrint layer_to_print; layer_to_print.object_layer = (idx_object_layer < object.layers.size()) ? object.layers[idx_object_layer ++] : nullptr; layer_to_print.support_layer = (idx_support_layer < object.support_layers.size()) ? object.support_layers[idx_support_layer ++] : nullptr; if (layer_to_print.object_layer && layer_to_print.support_layer) { if (layer_to_print.object_layer->print_z < layer_to_print.support_layer->print_z - EPSILON) { layer_to_print.support_layer = nullptr; -- idx_support_layer; } else if (layer_to_print.support_layer->print_z < layer_to_print.object_layer->print_z - EPSILON) { layer_to_print.object_layer = nullptr; -- idx_object_layer; } } layers_to_print.emplace_back(layer_to_print); } return layers_to_print; } // Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z // will be printed for all objects at once. // Return a list of items. std::vector>> GCode::collect_layers_to_print(const Print &print) { struct OrderingItem { coordf_t print_z; size_t object_idx; size_t layer_idx; }; std::vector> per_object(print.objects.size(), std::vector()); std::vector ordering; for (size_t i = 0; i < print.objects.size(); ++ i) { per_object[i] = collect_layers_to_print(*print.objects[i]); OrderingItem ordering_item; ordering_item.object_idx = i; ordering.reserve(ordering.size() + per_object[i].size()); const LayerToPrint &front = per_object[i].front(); for (const LayerToPrint <p : per_object[i]) { ordering_item.print_z = ltp.print_z(); ordering_item.layer_idx = <p - &front; ordering.emplace_back(ordering_item); } } std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; }); std::vector>> layers_to_print; // Merge numerically very close Z values. for (size_t i = 0; i < ordering.size();) { // Find the last layer with roughly the same print_z. size_t j = i + 1; coordf_t zmax = ordering[i].print_z + EPSILON; for (; j < ordering.size() && ordering[j].print_z <= zmax; ++ j) ; // Merge into layers_to_print. std::pair> merged; // Assign an average print_z to the set of layers with nearly equal print_z. merged.first = 0.5 * (ordering[i].print_z + ordering[j-1].print_z); merged.second.assign(print.objects.size(), LayerToPrint()); for (; i < j; ++ i) { const OrderingItem &oi = ordering[i]; assert(merged.second[oi.object_idx].layer() == nullptr); merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]); } layers_to_print.emplace_back(std::move(merged)); } return layers_to_print; } void GCode::do_export(Print *print, const char *path) { PROFILE_CLEAR(); // Remove the old g-code if it exists. boost::nowide::remove(path); std::string path_tmp(path); path_tmp += ".tmp"; FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb"); if (file == nullptr) throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); this->m_placeholder_parser_failed_templates.clear(); this->_do_export(*print, file); fflush(file); if (ferror(file)) { fclose(file); boost::nowide::remove(path_tmp.c_str()); throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); } fclose(file); if (! this->m_placeholder_parser_failed_templates.empty()) { // G-code export proceeded, but some of the PlaceholderParser substitutions failed. std::string msg = std::string("G-code export to ") + path + " failed due to invalid custom G-code sections:\n\n"; for (const std::string &name : this->m_placeholder_parser_failed_templates) msg += std::string("\t") + name + "\n"; msg += "\nPlease inspect the file "; msg += path_tmp + " for error messages enclosed between\n"; msg += " !!!!! Failed to process the custom G-code template ...\n"; msg += "and\n"; msg += " !!!!! End of an error report for the custom G-code template ...\n"; throw std::runtime_error(msg); } if (boost::nowide::rename(path_tmp.c_str(), path) != 0) throw std::runtime_error( std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "Is " + path_tmp + " locked?" + '\n'); // Write the profiler measurements to file PROFILE_UPDATE(); PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str()); } void GCode::_do_export(Print &print, FILE *file) { PROFILE_FUNC(); // resets time estimator m_time_estimator.reset(); m_time_estimator.set_dialect(print.config.gcode_flavor); // How many times will be change_layer() called? // change_layer() in turn increments the progress bar status. m_layer_count = 0; if (print.config.complete_objects.value) { // Add each of the object's layers separately. for (auto object : print.objects) { std::vector zs; zs.reserve(object->layers.size() + object->support_layers.size()); for (auto layer : object->layers) zs.push_back(layer->print_z); for (auto layer : object->support_layers) zs.push_back(layer->print_z); std::sort(zs.begin(), zs.end()); m_layer_count += (unsigned int)(object->copies().size() * (std::unique(zs.begin(), zs.end()) - zs.begin())); } } else { // Print all objects with the same print_z together. std::vector zs; for (auto object : print.objects) { zs.reserve(zs.size() + object->layers.size() + object->support_layers.size()); for (auto layer : object->layers) zs.push_back(layer->print_z); for (auto layer : object->support_layers) zs.push_back(layer->print_z); } std::sort(zs.begin(), zs.end()); m_layer_count = (unsigned int)(std::unique(zs.begin(), zs.end()) - zs.begin()); } m_enable_cooling_markers = true; this->apply_print_config(print.config); this->set_extruders(print.extruders()); // Initialize autospeed. { // get the minimum cross-section used in the print std::vector mm3_per_mm; for (auto object : print.objects) { for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) { auto region = print.regions[region_id]; for (auto layer : object->layers) { auto layerm = layer->regions[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) mm3_per_mm.push_back(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) mm3_per_mm.push_back(layerm->fills.min_mm3_per_mm()); } } if (object->config.get_abs_value("support_material_speed" ) == 0 || object->config.get_abs_value("support_material_interface_speed" ) == 0) for (auto layer : object->support_layers) mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm()); } // filter out 0-width segments mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end()); if (! mm3_per_mm.empty()) { // 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. m_volumetric_speed = *std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()) * print.config.max_print_speed.value; // limit such volumetric speed with max_volumetric_speed if set if (print.config.max_volumetric_speed.value > 0) m_volumetric_speed = std::min(m_volumetric_speed, print.config.max_volumetric_speed.value); } } m_cooling_buffer = make_unique(*this); if (print.config.spiral_vase.value) m_spiral_vase = make_unique(print.config); if (print.config.max_volumetric_extrusion_rate_slope_positive.value > 0 || print.config.max_volumetric_extrusion_rate_slope_negative.value > 0) m_pressure_equalizer = make_unique(&print.config); m_enable_extrusion_role_markers = (bool)m_pressure_equalizer; // Write information on the generator. _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str()); // Write notes (content of the Print Settings tab -> Notes) { std::list lines; boost::split(lines, print.config.notes.value, boost::is_any_of("\n"), boost::token_compress_off); for (auto line : lines) { // Remove the trailing '\r' from the '\r\n' sequence. if (! line.empty() && line.back() == '\r') line.pop_back(); _write_format(file, "; %s\n", line.c_str()); } if (! lines.empty()) _write(file, "\n"); } // Write some terse information on the slicing parameters. const PrintObject *first_object = print.objects.front(); const double layer_height = first_object->config.layer_height.value; const double first_layer_height = first_object->config.first_layer_height.get_abs_value(layer_height); for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) { auto region = print.regions[region_id]; _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width); _write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, false, -1., *first_object).width); _write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(frInfill, layer_height, false, false, -1., *first_object).width); _write_format(file, "; solid infill extrusion width = %.2fmm\n", region->flow(frSolidInfill, layer_height, false, false, -1., *first_object).width); _write_format(file, "; top infill extrusion width = %.2fmm\n", region->flow(frTopSolidInfill, layer_height, false, false, -1., *first_object).width); if (print.has_support_material()) _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width); if (print.config.first_layer_extrusion_width.value > 0) _write_format(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width); _write_format(file, "\n"); } // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser; m_placeholder_parser.update_timestamp(); // Get optimal tool ordering to minimize tool switches of a multi-exruder print. // For a print by objects, find the 1st printing object. ToolOrdering tool_ordering; unsigned int initial_extruder_id = (unsigned int)-1; unsigned int final_extruder_id = (unsigned int)-1; size_t initial_print_object_id = 0; bool has_wipe_tower = false; if (print.config.complete_objects.value) { // Find the 1st printing object, find its tool ordering and the initial extruder ID. for (; initial_print_object_id < print.objects.size(); ++initial_print_object_id) { tool_ordering = ToolOrdering(*print.objects[initial_print_object_id], initial_extruder_id); if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1) break; } } else { // Find tool ordering for all the objects at once, and the initial extruder ID. // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. tool_ordering = print.m_tool_ordering.empty() ? ToolOrdering(print, initial_extruder_id) : print.m_tool_ordering; initial_extruder_id = tool_ordering.first_extruder(); has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); } if (initial_extruder_id == (unsigned int)-1) { // Nothing to print! initial_extruder_id = 0; final_extruder_id = 0; } else { final_extruder_id = tool_ordering.last_extruder(); assert(final_extruder_id != (unsigned int)-1); } m_cooling_buffer->set_current_extruder(initial_extruder_id); // Disable fan. if (! print.config.cooling.get_at(initial_extruder_id) || print.config.disable_fan_first_layers.get_at(initial_extruder_id)) _write(file, m_writer.set_fan(0, true)); // Let the start-up script prime the 1st printing tool. m_placeholder_parser.set("initial_tool", initial_extruder_id); m_placeholder_parser.set("initial_extruder", initial_extruder_id); m_placeholder_parser.set("current_extruder", initial_extruder_id); // Useful for sequential prints. m_placeholder_parser.set("current_object_idx", 0); // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id); // Set bed temperature if the start G-code does not contain any bed temp control G-codes. this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); // Set extruder(s) temperature before and after start G-code. this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false); // Write the custom start G-code _writeln(file, start_gcode); // Process filament-specific gcode in extruder order. if (print.config.single_extruder_multi_material) { if (has_wipe_tower) { // Wipe tower will control the extruder switching, it will call the start_filament_gcode. } else { // Only initialize the initial extruder. _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config.start_filament_gcode.values[initial_extruder_id], initial_extruder_id)); } } else { for (const std::string &start_gcode : print.config.start_filament_gcode.values) _writeln(file, this->placeholder_parser_process("start_gcode", start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front()))); } this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true); // Set other general things. _write(file, this->preamble()); // Initialize a motion planner for object-to-object travel moves. if (print.config.avoid_crossing_perimeters.value) { // Collect outer contours of all objects over all layers. // Discard objects only containing thin walls (offset would fail on an empty polygon). Polygons islands; for (const PrintObject *object : print.objects) for (const Layer *layer : object->layers) for (const ExPolygon &expoly : layer->slices.expolygons) for (const Point © : object->_shifted_copies) { islands.emplace_back(expoly.contour); islands.back().translate(copy); } //FIXME Mege the islands in parallel. m_avoid_crossing_perimeters.init_external_mp(union_ex(islands)); } // Calculate wiping points if needed if (print.config.ooze_prevention.value && ! print.config.single_extruder_multi_material) { Points skirt_points; for (const ExtrusionEntity *ee : print.skirt.entities) for (const ExtrusionPath &path : dynamic_cast(ee)->paths) append(skirt_points, path.polyline.points); if (! skirt_points.empty()) { Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points); Polygons skirts; for (unsigned int extruder_id : print.extruders()) { const Pointf &extruder_offset = print.config.extruder_offset.get_at(extruder_id); Polygon s(outer_skirt); s.translate(-scale_(extruder_offset.x), -scale_(extruder_offset.y)); skirts.emplace_back(std::move(s)); } m_ooze_prevention.enable = true; m_ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), scale_(3.f)).front().equally_spaced_points(scale_(10.)); #if 0 require "Slic3r/SVG.pm"; Slic3r::SVG::output( "ooze_prevention.svg", red_polygons => \@skirts, polygons => [$outer_skirt], points => $gcodegen->ooze_prevention->standby_points, ); #endif } } // Set initial extruder only after custom start G-code. _write(file, this->set_extruder(initial_extruder_id)); // Do all objects for each layer. if (print.config.complete_objects.value) { // Print objects from the smallest to the tallest to avoid collisions // when moving onto next object starting point. std::vector objects(print.objects); std::sort(objects.begin(), objects.end(), [](const PrintObject* po1, const PrintObject* po2) { return po1->size.z < po2->size.z; }); size_t finished_objects = 0; for (size_t object_id = initial_print_object_id; object_id < objects.size(); ++ object_id) { const PrintObject &object = *objects[object_id]; for (const Point © : object._shifted_copies) { // Get optimal tool ordering to minimize tool switches of a multi-exruder print. if (object_id != initial_print_object_id || © != object._shifted_copies.data()) { // Don't initialize for the first object and first copy. tool_ordering = ToolOrdering(object, final_extruder_id); unsigned int new_extruder_id = tool_ordering.first_extruder(); if (new_extruder_id == (unsigned int)-1) // Skip this object. continue; initial_extruder_id = new_extruder_id; final_extruder_id = tool_ordering.last_extruder(); assert(final_extruder_id != (unsigned int)-1); } this->set_origin(unscale(copy.x), unscale(copy.y)); if (finished_objects > 0) { // 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. m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer m_avoid_crossing_perimeters.use_external_mp_once = true; _write(file, this->retract()); _write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. m_avoid_crossing_perimeters.disable_once = true; // Ff 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. m_placeholder_parser.set("current_object_idx", int(finished_objects)); std::string between_objects_gcode = this->placeholder_parser_process("between_objects_gcode", print.config.between_objects_gcode.value, initial_extruder_id); // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature. this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false); this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false); _writeln(file, between_objects_gcode); } // Reset the cooling buffer internal state (the current position, feed rate, accelerations). m_cooling_buffer->reset(); m_cooling_buffer->set_current_extruder(initial_extruder_id); // Pair the object layers with the support layers by z, extrude them. std::vector layers_to_print = collect_layers_to_print(object); for (const LayerToPrint <p : layers_to_print) { std::vector lrs; lrs.emplace_back(std::move(ltp)); this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), © - object._shifted_copies.data()); } if (m_pressure_equalizer) _write(file, m_pressure_equalizer->process("", true)); ++ finished_objects; // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. // Reset it when starting another object from 1st layer. m_second_layer_things_done = false; } } } else { // Order objects using a nearest neighbor search. std::vector object_indices; Points object_reference_points; for (PrintObject *object : print.objects) object_reference_points.push_back(object->_shifted_copies.front()); Slic3r::Geometry::chained_path(object_reference_points, object_indices); // Sort layers by Z. // All extrusion moves with the same top layer height are extruded uninterrupted. std::vector>> layers_to_print = collect_layers_to_print(print); // Prusa Multi-Material wipe tower. if (has_wipe_tower && ! layers_to_print.empty()) { m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get())); _write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height")); _write(file, m_wipe_tower->prime(*this)); // Verify, whether the print overaps the priming extrusions. BoundingBoxf bbox_print(get_print_extrusions_extents(print)); coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; for (const PrintObject *print_object : print.objects) bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz)); bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); bbox_prime.offset(0.5f); // Beep for 500ms, tone 800Hz. Yet better, play some Morse. _write(file, this->retract()); _write(file, "M300 S800 P500\n"); if (bbox_prime.overlap(bbox_print)) { // Wait for the user to remove the priming extrusions, otherwise they would // get covered by the print. _write(file, "M1 Remove priming towers and click button.\n"); } else { // Just wait for a bit to let the user check, that the priming succeeded. //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. _write(file, "M1 S10\n"); } } // Extrude the layers. for (auto &layer : layers_to_print) { const ToolOrdering::LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first); if (m_wipe_tower && layer_tools.has_wipe_tower) m_wipe_tower->next_layer(); this->process_layer(file, print, layer.second, layer_tools, size_t(-1)); } if (m_pressure_equalizer) _write(file, m_pressure_equalizer->process("", true)); if (m_wipe_tower) // Purge the extruder, pull out the active filament. _write(file, m_wipe_tower->finalize(*this)); } // Write end commands to file. _write(file, this->retract()); _write(file, m_writer.set_fan(false)); // Process filament-specific gcode in extruder order. if (print.config.single_extruder_multi_material) { // Process the end_filament_gcode for the active filament only. _writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config.end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id())); } else { for (const std::string &end_gcode : print.config.end_filament_gcode.values) _writeln(file, this->placeholder_parser_process("end_gcode", end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front()))); } _writeln(file, this->placeholder_parser_process("end_gcode", print.config.end_gcode, m_writer.extruder()->id())); _write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% _write(file, m_writer.postamble()); // calculates estimated printing time m_time_estimator.calculate_time(); // Get filament stats. print.filament_stats.clear(); print.total_used_filament = 0.; print.total_extruded_volume = 0.; print.total_weight = 0.; print.total_cost = 0.; print.estimated_print_time = m_time_estimator.get_time_hms(); for (const Extruder &extruder : m_writer.extruders()) { double used_filament = extruder.used_filament(); double extruded_volume = extruder.extruded_volume(); double filament_weight = extruded_volume * extruder.filament_density() * 0.001; double filament_cost = filament_weight * extruder.filament_cost() * 0.001; print.filament_stats.insert(std::pair(extruder.id(), used_filament)); _write_format(file, "; filament used = %.1lfmm (%.1lfcm3)\n", used_filament, extruded_volume * 0.001); if (filament_weight > 0.) { print.total_weight = print.total_weight + filament_weight; _write_format(file, "; filament used = %.1lf\n", filament_weight); if (filament_cost > 0.) { print.total_cost = print.total_cost + filament_cost; _write_format(file, "; filament cost = %.1lf\n", filament_cost); } } print.total_used_filament = print.total_used_filament + used_filament; print.total_extruded_volume = print.total_extruded_volume + extruded_volume; } _write_format(file, "; total filament cost = %.1lf\n", print.total_cost); _write_format(file, "; estimated printing time = %s\n", m_time_estimator.get_time_hms().c_str()); // Append full config. _write(file, "\n"); { StaticPrintConfig *configs[] = { &print.config, &print.default_object_config, &print.default_region_config }; for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++ i) { StaticPrintConfig *cfg = configs[i]; for (const std::string &key : cfg->keys()) if (key != "compatible_printers") _write_format(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str()); } } } std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) { try { return m_placeholder_parser.process(templ, current_extruder_id, config_override); } catch (std::runtime_error &err) { // Collect the names of failed template substitutions for error reporting. this->m_placeholder_parser_failed_templates.insert(name); // Insert the macro error message into the G-code. return std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + err.what() + "!!!!! End of an error report for the custom G-code template " + name + "\n\n"; } } // Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code. // Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out. static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, int &temp_out) { temp_out = -1; if (gcode.empty()) return false; const char *ptr = gcode.data(); bool temp_set_by_gcode = false; while (*ptr != 0) { // Skip whitespaces. for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); if (*ptr == 'M') { // Line starts with 'M'. It is a machine command. ++ ptr; // Parse the M code value. char *endptr = nullptr; int mcode = int(strtol(ptr, &endptr, 10)); if (endptr != nullptr && endptr != ptr && (mcode == mcode_set_temp_dont_wait || mcode == mcode_set_temp_and_wait)) { // M104/M109 or M140/M190 found. ptr = endptr; // Let the caller know that the custom G-code sets the temperature. temp_set_by_gcode = true; // Now try to parse the temperature value. // While not at the end of the line: while (strchr(";\r\n\0", *ptr) == nullptr) { // Skip whitespaces. for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); if (*ptr == 'S') { // Skip whitespaces. for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr); // Parse an int. endptr = nullptr; long temp_parsed = strtol(ptr, &endptr, 10); if (endptr > ptr) { ptr = endptr; temp_out = temp_parsed; } } else { // Skip this word. for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); } } } } // Skip the rest of the line. for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr); // Skip the end of line indicators. for (; *ptr == '\r' || *ptr == '\n'; ++ ptr); } return temp_set_by_gcode; } // Write 1st layer bed temperatures into the G-code. // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. // M140 - Set Extruder Temperature // M190 - Set Extruder Temperature and Wait void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) { // Initial bed temperature based on the first extruder. int temp = print.config.first_layer_bed_temperature.get_at(first_printing_extruder_id); // Is the bed temperature set by the provided custom G-code? int temp_by_gcode = -1; bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, temp_by_gcode); if (temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000) temp = temp_by_gcode; // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if // the custom start G-code emited these. std::string set_temp_gcode = m_writer.set_bed_temperature(temp, wait); if (! temp_set_by_gcode) _write(file, set_temp_gcode); } // Write 1st layer extruder temperatures into the G-code. // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. // M104 - Set Extruder Temperature // M109 - Set Extruder Temperature and Wait void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) { // Is the bed temperature set by the provided custom G-code? int temp_by_gcode = -1; if (custom_gcode_sets_temperature(gcode, 104, 109, temp_by_gcode)) { // Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code. int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id); if (temp_by_gcode >= 0 && temp_by_gcode < 1000) temp = temp_by_gcode; m_writer.set_temperature(temp_by_gcode, wait, first_printing_extruder_id); } else { // Custom G-code does not set the extruder temperature. Do it now. if (print.config.single_extruder_multi_material.value) { // Set temperature of the first printing extruder only. int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id); if (temp > 0) _write(file, m_writer.set_temperature(temp, wait, first_printing_extruder_id)); } else { // Set temperatures of all the printing extruders. for (unsigned int tool_id : print.extruders()) { int temp = print.config.first_layer_temperature.get_at(tool_id); if (print.config.ooze_prevention.value) temp += print.config.standby_temperature_delta.value; if (temp > 0) _write(file, m_writer.set_temperature(temp, wait, tool_id)); } } } } inline GCode::ObjectByExtruder& object_by_extruder( std::map> &by_extruder, unsigned int extruder_id, size_t object_idx, size_t num_objects) { std::vector &objects_by_extruder = by_extruder[extruder_id]; if (objects_by_extruder.empty()) objects_by_extruder.assign(num_objects, GCode::ObjectByExtruder()); return objects_by_extruder[object_idx]; } inline std::vector& object_islands_by_extruder( std::map> &by_extruder, unsigned int extruder_id, size_t object_idx, size_t num_objects, size_t num_islands) { std::vector &islands = object_by_extruder(by_extruder, extruder_id, object_idx, num_objects).islands; if (islands.empty()) islands.assign(num_islands, GCode::ObjectByExtruder::Island()); return islands; } // In sequential mode, process_layer is called once per each object and its copy, // therefore layers will contain a single entry and single_object_idx will point to the copy of the object. // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths // and performing the extruder specific extrusions together. void GCode::process_layer( // Write into the output file. FILE *file, const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, const ToolOrdering::LayerTools &layer_tools, // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_idx) { assert(! layers.empty()); assert(! layer_tools.extruders.empty()); // Either printing all copies of all objects, or just a single copy of a single object. assert(single_object_idx == size_t(-1) || layers.size() == 1); if (layer_tools.extruders.empty()) // Nothing to extrude. return; // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. const Layer *object_layer = nullptr; const SupportLayer *support_layer = nullptr; for (const LayerToPrint &l : layers) { if (l.object_layer != nullptr && object_layer == nullptr) object_layer = l.object_layer; if (l.support_layer != nullptr && support_layer == nullptr) support_layer = l.support_layer; } const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer; coordf_t print_z = layer.print_z; bool first_layer = layer.id() == 0; unsigned int first_extruder_id = layer_tools.extruders.front(); // Initialize config with the 1st object to be printed at this layer. m_config.apply(layer.object()->config, true); // Check whether it is possible to apply the spiral vase logic for this layer. // Just a reminder: A spiral vase mode is allowed for a single object, single material print only. if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) { bool enable = (layer.id() > 0 || print.config.brim_width.value == 0.) && (layer.id() >= print.config.skirt_height.value && ! print.has_infinite_skirt()); if (enable) { for (const LayerRegion *layer_region : layer.regions) if (layer_region->region()->config.bottom_solid_layers.value > layer.id() || layer_region->perimeters.items_count() > 1 || layer_region->fills.items_count() > 0) { enable = false; break; } } m_spiral_vase->enable = enable; } // If we're going to apply spiralvase to this layer, disable loop clipping m_enable_loop_clipping = ! m_spiral_vase || ! m_spiral_vase->enable; std::string gcode; // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. if (! print.config.before_layer_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); gcode += this->placeholder_parser_process("before_layer_gcode", print.config.before_layer_gcode.value, m_writer.extruder()->id(), &config) + "\n"; } gcode += this->change_layer(print_z); // this will increase m_layer_index m_layer = &layer; if (! print.config.layer_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); gcode += this->placeholder_parser_process("layer_gcode", print.config.layer_gcode.value, m_writer.extruder()->id(), &config) + "\n"; } if (! first_layer && ! m_second_layer_things_done) { // Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent // first_layer_temperature vs. temperature settings. for (const Extruder &extruder : m_writer.extruders()) { if (print.config.single_extruder_multi_material.value && extruder.id() != m_writer.extruder()->id()) // In single extruder multi material mode, set the temperature for the current extruder only. continue; int temperature = print.config.temperature.get_at(extruder.id()); if (temperature > 0 && temperature != print.config.first_layer_temperature.get_at(extruder.id())) gcode += m_writer.set_temperature(temperature, false, extruder.id()); } gcode += m_writer.set_bed_temperature(print.config.bed_temperature.get_at(first_extruder_id)); // Mark the temperature transition from 1st to 2nd layer to be finished. m_second_layer_things_done = true; } // Extrude skirt at the print_z of the raft layers and normal object layers // not at the print_z of the interlaced support material layers. bool extrude_skirt = ! print.skirt.entities.empty() && // Not enough skirt layers printed yet. (m_skirt_done.size() < print.config.skirt_height.value || print.has_infinite_skirt()) && // This print_z has not been extruded yet (m_skirt_done.empty() ? 0. : m_skirt_done.back()) < print_z - EPSILON && // and this layer is the 1st layer, or it is an object layer, or it is a raft layer. (first_layer || object_layer != nullptr || support_layer->id() < m_config.raft_layers.value); std::map> skirt_loops_per_extruder; coordf_t skirt_height = 0.; if (extrude_skirt) { // Fill in skirt_loops_per_extruder. skirt_height = print_z - (m_skirt_done.empty() ? 0. : m_skirt_done.back()); m_skirt_done.push_back(print_z); if (first_layer) { // Prime the extruders over the skirt lines. std::vector extruder_ids = m_writer.extruder_ids(); // Reorder the extruders, so that the last used extruder is at the front. for (size_t i = 1; i < extruder_ids.size(); ++ i) if (extruder_ids[i] == first_extruder_id) { // Move the last extruder to the front. memmove(extruder_ids.data() + 1, extruder_ids.data(), i * sizeof(unsigned int)); extruder_ids.front() = first_extruder_id; break; } size_t n_loops = print.skirt.entities.size(); if (n_loops <= extruder_ids.size()) { for (size_t i = 0; i < n_loops; ++i) skirt_loops_per_extruder[extruder_ids[i]] = std::pair(i, i + 1); } else { // Assign skirt loops to the extruders. std::vector extruder_loops(extruder_ids.size(), 1); n_loops -= extruder_loops.size(); while (n_loops > 0) { for (size_t i = 0; i < extruder_ids.size() && n_loops > 0; ++ i, -- n_loops) ++ extruder_loops[i]; } for (size_t i = 0; i < extruder_ids.size(); ++ i) skirt_loops_per_extruder[extruder_ids[i]] = std::make_pair( (i == 0) ? 0 : extruder_loops[i - 1], ((i == 0) ? 0 : extruder_loops[i - 1]) + extruder_loops[i]); } } else // Extrude all skirts with the current extruder. skirt_loops_per_extruder[first_extruder_id] = std::pair(0, print.config.skirts.value); } // Group extrusions by an extruder, then by an object, an island and a region. std::map> by_extruder; for (const LayerToPrint &layer_to_print : layers) { if (layer_to_print.support_layer != nullptr) { const SupportLayer &support_layer = *layer_to_print.support_layer; const PrintObject &object = *support_layer.object(); if (! support_layer.support_fills.entities.empty()) { ExtrusionRole role = support_layer.support_fills.role(); bool has_support = role == erMixed || role == erSupportMaterial; bool has_interface = role == erMixed || role == erSupportMaterialInterface; // Extruder ID of the support base. -1 if "don't care". unsigned int support_extruder = object.config.support_material_extruder.value - 1; // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? bool support_dontcare = object.config.support_material_extruder.value == 0; // Extruder ID of the support interface. -1 if "don't care". unsigned int interface_extruder = object.config.support_material_interface_extruder.value - 1; // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? bool interface_dontcare = object.config.support_material_interface_extruder.value == 0; if (support_dontcare || interface_dontcare) { // Some support will be printed with "don't care" material, preferably non-soluble. // Is the current extruder assigned a soluble filament? unsigned int dontcare_extruder = first_extruder_id; if (print.config.filament_soluble.get_at(dontcare_extruder)) { // The last extruder printed on the previous layer extrudes soluble filament. // Try to find a non-soluble extruder on the same layer. for (unsigned int extruder_id : layer_tools.extruders) if (! print.config.filament_soluble.get_at(extruder_id)) { dontcare_extruder = extruder_id; break; } } if (support_dontcare) support_extruder = dontcare_extruder; if (interface_dontcare) interface_extruder = dontcare_extruder; } // Both the support and the support interface are printed with the same extruder, therefore // the interface may be interleaved with the support base. bool single_extruder = ! has_support || support_extruder == interface_extruder; // Assign an extruder to the base. ObjectByExtruder &obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, &layer_to_print - layers.data(), layers.size()); obj.support = &support_layer.support_fills; obj.support_extrusion_role = single_extruder ? erMixed : erSupportMaterial; if (! single_extruder && has_interface) { ObjectByExtruder &obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), layers.size()); obj_interface.support = &support_layer.support_fills; obj_interface.support_extrusion_role = erSupportMaterialInterface; } } } if (layer_to_print.object_layer != nullptr) { const Layer &layer = *layer_to_print.object_layer; // 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) size_t n_slices = layer.slices.expolygons.size(); std::vector layer_surface_bboxes; layer_surface_bboxes.reserve(n_slices); for (const ExPolygon &expoly : layer.slices.expolygons) layer_surface_bboxes.push_back(get_extents(expoly.contour)); auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { const BoundingBox &bbox = layer_surface_bboxes[i]; return point.x >= bbox.min.x && point.x < bbox.max.x && point.y >= bbox.min.y && point.y < bbox.max.y && layer.slices.expolygons[i].contour.contains(point); }; for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) { const LayerRegion *layerm = layer.regions[region_id]; if (layerm == nullptr) continue; const PrintRegion ®ion = *print.regions[region_id]; // process perimeters for (const ExtrusionEntity *ee : layerm->perimeters.entities) { // perimeter_coll represents perimeter extrusions of a single island. const auto *perimeter_coll = dynamic_cast(ee); if (perimeter_coll->entities.empty()) // This shouldn't happen but first_point() would fail. continue; // Init by_extruder item only if we actually use the extruder. std::vector &islands = object_islands_by_extruder( by_extruder, std::max(region.config.perimeter_extruder.value - 1, 0), &layer_to_print - layers.data(), layers.size(), n_slices+1); for (size_t i = 0; i <= n_slices; ++ i) 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())) { if (islands[i].by_region.empty()) islands[i].by_region.assign(print.regions.size(), ObjectByExtruder::Island::Region()); islands[i].by_region[region_id].perimeters.append(perimeter_coll->entities); break; } } // process infill // layerm->fills is a collection of Slic3r::ExtrusionPath::Collection objects (C++ class ExtrusionEntityCollection), // 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. for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); if (fill->entities.empty()) // This shouldn't happen but first_point() would fail. continue; // init by_extruder item only if we actually use the extruder int extruder_id = std::max(0, (is_solid_infill(fill->entities.front()->role()) ? region.config.solid_infill_extruder : region.config.infill_extruder) - 1); // Init by_extruder item only if we actually use the extruder. std::vector &islands = object_islands_by_extruder( by_extruder, extruder_id, &layer_to_print - layers.data(), layers.size(), n_slices+1); for (size_t i = 0; i <= n_slices; ++i) if (// fill->first_point does not fit inside any slice i == n_slices || // fill->first_point fits inside ith slice point_inside_surface(i, fill->first_point())) { if (islands[i].by_region.empty()) islands[i].by_region.assign(print.regions.size(), ObjectByExtruder::Island::Region()); islands[i].by_region[region_id].infills.append(fill->entities); break; } } } // for regions } } // for objects // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. std::vector> lower_layer_edge_grids(layers.size()); for (unsigned int extruder_id : layer_tools.extruders) { gcode += (layer_tools.has_wipe_tower && m_wipe_tower) ? m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) : this->set_extruder(extruder_id); if (extrude_skirt) { auto loops_it = skirt_loops_per_extruder.find(extruder_id); if (loops_it != skirt_loops_per_extruder.end()) { const std::pair loops = loops_it->second; this->set_origin(0.,0.); m_avoid_crossing_perimeters.use_external_mp = true; Flow skirt_flow = print.skirt_flow(); for (size_t i = loops.first; i < loops.second; ++ i) { // Adjust flow according to this layer's layer height. ExtrusionLoop loop = *dynamic_cast(print.skirt.entities[i]); Flow layer_skirt_flow(skirt_flow); layer_skirt_flow.height = (float)skirt_height; double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); for (ExtrusionPath &path : loop.paths) { path.height = (float)layer.height; path.mm3_per_mm = mm3_per_mm; } gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value); } m_avoid_crossing_perimeters.use_external_mp = false; // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). if (first_layer && loops.first == 0) m_avoid_crossing_perimeters.disable_once = true; } } // Extrude brim with the extruder of the 1st region. if (! m_brim_done) { this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp = true; for (const ExtrusionEntity *ee : print.brim.entities) gcode += this->extrude_loop(*dynamic_cast(ee), "brim", m_config.support_material_speed.value); m_brim_done = true; m_avoid_crossing_perimeters.use_external_mp = false; // Allow a straight travel move to the first object point. m_avoid_crossing_perimeters.disable_once = true; } auto objects_by_extruder_it = by_extruder.find(extruder_id); if (objects_by_extruder_it == by_extruder.end()) continue; for (const ObjectByExtruder &object_by_extruder : objects_by_extruder_it->second) { const size_t layer_id = &object_by_extruder - objects_by_extruder_it->second.data(); const PrintObject *print_object = layers[layer_id].object(); if (print_object == nullptr) // This layer is empty for this particular object, it has neither object extrusions nor support extrusions at this print_z. continue; if (m_enable_analyzer_markers) { // Store the binary pointer to the layer object directly into the G-code to be accessed by the GCodeAnalyzer. char buf[64]; sprintf(buf, ";_LAYEROBJ:%p\n", m_layer); gcode += buf; } m_config.apply(print_object->config, true); m_layer = layers[layer_id].layer(); if (m_config.avoid_crossing_perimeters) m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->slices, true)); Points copies; if (single_object_idx == size_t(-1)) copies = print_object->_shifted_copies; else copies.push_back(print_object->_shifted_copies[single_object_idx]); // Sort the copies by the closest point starting with the current print position. for (const Point © : copies) { // When starting a new object, use the external motion planner for the first travel move. std::pair this_object_copy(print_object, copy); if (m_last_obj_copy != this_object_copy) m_avoid_crossing_perimeters.use_external_mp_once = true; m_last_obj_copy = this_object_copy; this->set_origin(unscale(copy.x), unscale(copy.y)); if (object_by_extruder.support != nullptr) { m_layer = layers[layer_id].support_layer; gcode += this->extrude_support( // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. object_by_extruder.support->chained_path_from(m_last_pos, false, object_by_extruder.support_extrusion_role)); m_layer = layers[layer_id].layer(); } for (const ObjectByExtruder::Island &island : object_by_extruder.islands) { if (print.config.infill_first) { gcode += this->extrude_infill(print, island.by_region); gcode += this->extrude_perimeters(print, island.by_region, lower_layer_edge_grids[layer_id]); } else { gcode += this->extrude_perimeters(print, island.by_region, lower_layer_edge_grids[layer_id]); gcode += this->extrude_infill(print, island.by_region); } } } } } // 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. // Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only. if (m_spiral_vase) gcode = m_spiral_vase->process_layer(gcode); // Apply cooling logic; this may alter speeds. if (m_cooling_buffer) gcode = m_cooling_buffer->process_layer(gcode, layer.id()); // Apply pressure equalization if enabled; // printf("G-code before filter:\n%s\n", gcode.c_str()); if (m_pressure_equalizer) gcode = m_pressure_equalizer->process(gcode.c_str(), false); // printf("G-code after filter:\n%s\n", out.c_str()); _write(file, gcode); } void GCode::apply_print_config(const PrintConfig &print_config) { m_writer.apply_print_config(print_config); m_config.apply(print_config); } void GCode::set_extruders(const std::vector &extruder_ids) { m_writer.set_extruders(extruder_ids); // enable wipe path generation if any extruder has wipe enabled m_wipe.enable = false; for (auto id : extruder_ids) if (m_config.wipe.get_at(id)) { m_wipe.enable = true; break; } } void GCode::set_origin(const Pointf &pointf) { // if origin increases (goes towards right), last_pos decreases because it goes towards left const Point translate( scale_(m_origin.x - pointf.x), scale_(m_origin.y - pointf.y) ); m_last_pos.translate(translate); m_wipe.path.translate(translate); m_origin = pointf; } std::string GCode::preamble() { std::string gcode = m_writer.preamble(); /* Perform a *silent* move to z_offset: we need this to initialize the Z position of our writer object so that any initial lift taking place before the first layer change will raise the extruder from the correct initial Z instead of 0. */ m_writer.travel_to_z(m_config.z_offset.value); return gcode; } // called by GCode::process_layer() std::string GCode::change_layer(coordf_t print_z) { std::string gcode; if (m_layer_count > 0) // Increment a progress bar indicator. gcode += m_writer.update_progress(++ m_layer_index, m_layer_count); coordf_t z = print_z + m_config.z_offset.value; // in unscaled coordinates if (EXTRUDER_CONFIG(retract_layer_change) && m_writer.will_move_z(z)) gcode += this->retract(); { std::ostringstream comment; comment << "move to next layer (" << m_layer_index << ")"; gcode += m_writer.travel_to_z(z, comment.str()); } // forget last wiping path as wiping after raising Z is pointless m_wipe.reset_path(); return gcode; } static inline const char* ExtrusionRole2String(const ExtrusionRole role) { switch (role) { case erNone: return "erNone"; case erPerimeter: return "erPerimeter"; case erExternalPerimeter: return "erExternalPerimeter"; case erOverhangPerimeter: return "erOverhangPerimeter"; case erInternalInfill: return "erInternalInfill"; case erSolidInfill: return "erSolidInfill"; case erTopSolidInfill: return "erTopSolidInfill"; case erBridgeInfill: return "erBridgeInfill"; case erGapFill: return "erGapFill"; case erSkirt: return "erSkirt"; case erSupportMaterial: return "erSupportMaterial"; case erSupportMaterialInterface: return "erSupportMaterialInterface"; case erMixed: return "erMixed"; default: return "erInvalid"; }; } static inline const char* ExtrusionLoopRole2String(const ExtrusionLoopRole role) { switch (role) { case elrDefault: return "elrDefault"; case elrContourInternalPerimeter: return "elrContourInternalPerimeter"; case elrSkirt: return "elrSkirt"; default: return "elrInvalid"; } }; // Return a value in <0, 1> of a cubic B-spline kernel centered around zero. // The B-spline is re-scaled so it has value 1 at zero. static inline float bspline_kernel(float x) { x = std::abs(x); if (x < 1.f) { return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x; } else if (x < 2.f) { x -= 1.f; float x2 = x * x; float x3 = x2 * x; return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3; } else return 0; } static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance) { // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve. // Solved by sympy package: /* from sympy import * (x,a,b,c,d,r,z)=symbols('x a b c d r z') p = a + b*x + c*x*x + d*x*x*x p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d])) from sympy.plotting import plot plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400) */ if (overlap_distance < - nozzle_r) { // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty. return 0.f; } else { float x = overlap_distance / nozzle_r; float x2 = x * x; float x3 = x2 * x; return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3); } } static Points::iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps) { assert(polygon.points.size() >= 2); if (polygon.points.size() <= 1) if (polygon.points.size() == 1) return polygon.points.begin(); Point pt_min; double d_min = std::numeric_limits::max(); size_t i_min = size_t(-1); for (size_t i = 0; i < polygon.points.size(); ++ i) { size_t j = i + 1; if (j == polygon.points.size()) j = 0; const Point &p1 = polygon.points[i]; const Point &p2 = polygon.points[j]; const Slic3r::Point v_seg = p1.vector_to(p2); const Slic3r::Point v_pt = p1.vector_to(pt); const int64_t l2_seg = int64_t(v_seg.x) * int64_t(v_seg.x) + int64_t(v_seg.y) * int64_t(v_seg.y); int64_t t_pt = int64_t(v_seg.x) * int64_t(v_pt.x) + int64_t(v_seg.y) * int64_t(v_pt.y); if (t_pt < 0) { // Closest to p1. double dabs = sqrt(int64_t(v_pt.x) * int64_t(v_pt.x) + int64_t(v_pt.y) * int64_t(v_pt.y)); if (dabs < d_min) { d_min = dabs; i_min = i; pt_min = p1; } } else if (t_pt > l2_seg) { // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step. continue; } else { // Closest to the segment. assert(t_pt >= 0 && t_pt <= l2_seg); int64_t d_seg = int64_t(v_seg.y) * int64_t(v_pt.x) - int64_t(v_seg.x) * int64_t(v_pt.y); double d = double(d_seg) / sqrt(double(l2_seg)); double dabs = std::abs(d); if (dabs < d_min) { d_min = dabs; i_min = i; // Evaluate the foot point. pt_min = p1; double linv = double(d_seg) / double(l2_seg); pt_min.x = pt.x - coord_t(floor(double(v_seg.y) * linv + 0.5)); pt_min.y = pt.y + coord_t(floor(double(v_seg.x) * linv + 0.5)); assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5)); } } } assert(i_min != size_t(-1)); if (pt_min.distance_to(polygon.points[i_min]) > eps) { // Insert a new point on the segment i_min, i_min+1. return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min); } return polygon.points.begin() + i_min; } std::vector polygon_parameter_by_length(const Polygon &polygon) { // Parametrize the polygon by its length. std::vector lengths(polygon.points.size()+1, 0.); for (size_t i = 1; i < polygon.points.size(); ++ i) lengths[i] = lengths[i-1] + float(polygon.points[i].distance_to(polygon.points[i-1])); lengths.back() = lengths[lengths.size()-2] + float(polygon.points.front().distance_to(polygon.points.back())); return lengths; } std::vector polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, float min_arm_length) { assert(polygon.points.size() + 1 == lengths.size()); if (min_arm_length > 0.25f * lengths.back()) min_arm_length = 0.25f * lengths.back(); // Find the initial prev / next point span. size_t idx_prev = polygon.points.size(); size_t idx_curr = 0; size_t idx_next = 1; while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length) -- idx_prev; while (idx_next < idx_prev && lengths[idx_next] < min_arm_length) ++ idx_next; std::vector angles(polygon.points.size(), 0.f); for (; idx_curr < polygon.points.size(); ++ idx_curr) { // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length. if (idx_prev >= idx_curr) { while (idx_prev < polygon.points.size() && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length) ++ idx_prev; if (idx_prev == polygon.points.size()) idx_prev = 0; } while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length) ++ idx_prev; // Move idx_prev one step back. if (idx_prev == 0) idx_prev = polygon.points.size() - 1; else -- idx_prev; // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length. if (idx_curr <= idx_next) { while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length) ++ idx_next; if (idx_next == polygon.points.size()) idx_next = 0; } while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length) ++ idx_next; // Calculate angle between idx_prev, idx_curr, idx_next. const Point &p0 = polygon.points[idx_prev]; const Point &p1 = polygon.points[idx_curr]; const Point &p2 = polygon.points[idx_next]; const Point v1 = p0.vector_to(p1); const Point v2 = p1.vector_to(p2); int64_t dot = int64_t(v1.x)*int64_t(v2.x) + int64_t(v1.y)*int64_t(v2.y); int64_t cross = int64_t(v1.x)*int64_t(v2.y) - int64_t(v1.y)*int64_t(v2.x); float angle = float(atan2(double(cross), double(dot))); angles[idx_curr] = angle; } return angles; } std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, std::unique_ptr *lower_layer_edge_grid) { // get a copy; don't modify the orientation of the original loop object otherwise // next copies (if any) would not detect the correct orientation if (m_layer->lower_layer != nullptr && lower_layer_edge_grid != nullptr) { if (! *lower_layer_edge_grid) { // Create the distance field for a layer below. const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5); *lower_layer_edge_grid = make_unique(); (*lower_layer_edge_grid)->create(m_layer->lower_layer->slices, distance_field_resolution); (*lower_layer_edge_grid)->calculate_sdf(); #if 0 { static int iRun = 0; BoundingBox bbox = (*lower_layer_edge_grid)->bbox(); bbox.min.x -= scale_(5.f); bbox.min.y -= scale_(5.f); bbox.max.x += scale_(5.f); bbox.max.y += scale_(5.f); EdgeGrid::save_png(*(*lower_layer_edge_grid), bbox, scale_(0.1f), debug_out_path("GCode_extrude_loop_edge_grid-%d.png", iRun++)); } #endif } } // extrude all loops ccw bool was_clockwise = loop.make_counter_clockwise(); SeamPosition seam_position = m_config.seam_position; if (loop.loop_role() == elrSkirt) seam_position = spNearest; // find the point of the loop that is closest to the current extruder position // or randomize if requested Point last_pos = this->last_pos(); if (m_config.spiral_vase) { loop.split_at(last_pos, false); } else if (seam_position == spNearest || seam_position == spAligned || seam_position == spRear) { Polygon polygon = loop.polygon(); const coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); // Retrieve the last start position for this object. float last_pos_weight = 1.f; switch (seam_position) { case spAligned: // Seam is aligned to the seam at the preceding layer. if (m_layer != NULL && m_seam_position.count(m_layer->object()) > 0) { last_pos = m_seam_position[m_layer->object()]; last_pos_weight = 1.f; } break; case spRear: last_pos = m_layer->object()->bounding_box().center(); last_pos.y += coord_t(3. * m_layer->object()->bounding_box().radius()); last_pos_weight = 5.f; break; } // Insert a projection of last_pos into the polygon. size_t last_pos_proj_idx; { Points::iterator it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); last_pos_proj_idx = it - polygon.points.begin(); } Point last_pos_proj = polygon.points[last_pos_proj_idx]; // Parametrize the polygon by its length. std::vector lengths = polygon_parameter_by_length(polygon); // For each polygon point, store a penalty. // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r. std::vector penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r)); // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. const float penaltyConvexVertex = 1.f; const float penaltyFlatSurface = 5.f; const float penaltySeam = 1.3f; const float penaltyOverhangHalf = 10.f; // Penalty for visible seams. for (size_t i = 0; i < polygon.points.size(); ++ i) { float ccwAngle = penalties[i]; if (was_clockwise) ccwAngle = - ccwAngle; float penalty = 0; // if (ccwAngle <- float(PI/3.)) if (ccwAngle <- float(0.6 * PI)) // Sharp reflex vertex. We love that, it hides the seam perfectly. penalty = 0.f; // else if (ccwAngle > float(PI/3.)) else if (ccwAngle > float(0.6 * PI)) // Seams on sharp convex vertices are more visible than on reflex vertices. penalty = penaltyConvexVertex; else if (ccwAngle < 0.f) { // Interpolate penalty between maximum and zero. penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); } else { assert(ccwAngle >= 0.f); // Interpolate penalty between maximum and the penalty for a convex vertex. penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); } // Give a negative penalty for points close to the last point or the prefered seam location. //float dist_to_last_pos_proj = last_pos_proj.distance_to(polygon.points[i]); float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max); penalties[i] = std::max(0.f, penalty); } // Penalty for overhangs. if (lower_layer_edge_grid && (*lower_layer_edge_grid)) { // Use the edge grid distance field structure over the lower layer to calculate overhangs. coord_t nozzle_r = coord_t(floor(scale_(0.5 * nozzle_dmr) + 0.5)); coord_t search_r = coord_t(floor(scale_(0.8 * nozzle_dmr) + 0.5)); for (size_t i = 0; i < polygon.points.size(); ++ i) { const Point &p = polygon.points[i]; coordf_t dist; // Signed distance is positive outside the object, negative inside the object. // The point is considered at an overhang, if it is more than nozzle radius // outside of the lower layer contour. bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, // then the signed distnace shall always be known. assert(found); penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); } } // Find a point with a minimum penalty. size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); // if (seam_position == spAligned) // For all (aligned, nearest, rear) seams: { // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. // In that case use last_pos_proj_idx instead. float penalty_aligned = penalties[last_pos_proj_idx]; float penalty_min = penalties[idx_min]; float penalty_diff_abs = std::abs(penalty_min - penalty_aligned); float penalty_max = std::max(penalty_min, penalty_aligned); float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max; // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel); if (penalty_diff_rel < 0.05) { // Penalty of the aligned point is very close to the minimum penalty. // Align the seams as accurately as possible. idx_min = last_pos_proj_idx; } m_seam_position[m_layer->object()] = polygon.points[idx_min]; } // Export the contour into a SVG file. #if 0 { static int iRun = 0; SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++)); if (m_layer->lower_layer != NULL) svg.draw(m_layer->lower_layer->slices.expolygons); for (size_t i = 0; i < loop.paths.size(); ++ i) svg.draw(loop.paths[i].as_polyline(), "red"); Polylines polylines; for (size_t i = 0; i < loop.paths.size(); ++ i) polylines.push_back(loop.paths[i].as_polyline()); Slic3r::Polygons polygons; coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); coord_t delta = scale_(0.5*nozzle_dmr); Slic3r::offset(polylines, &polygons, delta); // for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue"); svg.draw(last_pos, "green", 3); svg.draw(polygon.points[idx_min], "yellow", 3); svg.Close(); } #endif // Split the loop at the point with a minium penalty. if (!loop.split_at_vertex(polygon.points[idx_min])) // The point is not in the original loop. Insert it. loop.split_at(polygon.points[idx_min], true); } else if (seam_position == spRandom) { if (loop.loop_role() == elrContourInternalPerimeter) { // This loop does not contain any other loop. Set a random position. // The other loops will get a seam close to the random point chosen // on the inner most contour. //FIXME This works correctly for inner contours first only. //FIXME Better parametrize the loop by its length. Polygon polygon = loop.polygon(); Point centroid = polygon.centroid(); last_pos = Point(polygon.bounding_box().max.x, centroid.y); last_pos.rotate(fmod((float)rand()/16.0, 2.0*PI), centroid); } // Find the closest point, avoid overhangs. loop.split_at(last_pos, true); } // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so // we discard it in that case double clip_length = m_enable_loop_clipping ? scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER : 0; // get paths ExtrusionPaths paths; loop.clip_end(clip_length, &paths); if (paths.empty()) return ""; // apply the small perimeter speed if (is_perimeter(paths.front().role()) && loop.length() <= SMALL_PERIMETER_LENGTH && speed == -1) speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed); // extrude along the path std::string gcode; for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { // description += ExtrusionLoopRole2String(loop.loop_role()); // description += ExtrusionRole2String(path->role); path->simplify(SCALED_RESOLUTION); gcode += this->_extrude(*path, description, speed); } // reset acceleration gcode += m_writer.set_acceleration((unsigned int)(m_config.default_acceleration.value + 0.5)); if (m_wipe.enable) m_wipe.path = paths.front().polyline; // TODO: don't limit wipe to last path // make a little move inwards before leaving loop if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1) { // detect angle between last and first segment // the side depends on the original winding order of the polygon (left for contours, right for holes) Point a = paths.front().polyline.points[1]; // second point Point b = *(paths.back().polyline.points.end()-3); // second to last point if (was_clockwise) { // swap points Point c = a; a = b; b = c; } double angle = paths.front().first_point().ccw_angle(a, b) / 3; // turn left if contour, turn right if hole if (was_clockwise) angle *= -1; // create the destination point along the first segment and rotate it // we make sure we don't exceed the segment length because we don't know // the rotation of the second segment so we might cross the object boundary Line first_segment( paths.front().polyline.points[0], paths.front().polyline.points[1] ); double distance = std::min( scale_(EXTRUDER_CONFIG(nozzle_diameter)), first_segment.length() ); Point point = first_segment.point_at(distance); point.rotate(angle, first_segment.a); // generate the travel move gcode += m_writer.travel_to_xy(this->point_to_gcode(point), "move inwards before travel"); } return gcode; } std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string description, double speed) { // extrude along the path std::string gcode; for (ExtrusionPath path : multipath.paths) { // description += ExtrusionLoopRole2String(loop.loop_role()); // description += ExtrusionRole2String(path->role); path.simplify(SCALED_RESOLUTION); gcode += this->_extrude(path, description, speed); } if (m_wipe.enable) { m_wipe.path = std::move(multipath.paths.back().polyline); // TODO: don't limit wipe to last path m_wipe.path.reverse(); } // reset acceleration gcode += m_writer.set_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5)); return gcode; } std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed, std::unique_ptr *lower_layer_edge_grid) { if (const ExtrusionPath* path = dynamic_cast(&entity)) return this->extrude_path(*path, description, speed); else if (const ExtrusionMultiPath* multipath = dynamic_cast(&entity)) return this->extrude_multi_path(*multipath, description, speed); else if (const ExtrusionLoop* loop = dynamic_cast(&entity)) return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid); else { CONFESS("Invalid argument supplied to extrude()"); return ""; } } std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed) { // description += ExtrusionRole2String(path.role()); path.simplify(SCALED_RESOLUTION); std::string gcode = this->_extrude(path, description, speed); if (m_wipe.enable) { m_wipe.path = std::move(path.polyline); m_wipe.path.reverse(); } // reset acceleration gcode += m_writer.set_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5)); return gcode; } // Extrude perimeters: Decide where to put seams (hide or align seams). std::string GCode::extrude_perimeters(const Print &print, const std::vector &by_region, std::unique_ptr &lower_layer_edge_grid) { std::string gcode; for (const ObjectByExtruder::Island::Region ®ion : by_region) { m_config.apply(print.regions[®ion - &by_region.front()]->config); for (ExtrusionEntity *ee : region.perimeters.entities) gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid); } return gcode; } // Chain the paths hierarchically by a greedy algorithm to minimize a travel distance. std::string GCode::extrude_infill(const Print &print, const std::vector &by_region) { std::string gcode; for (const ObjectByExtruder::Island::Region ®ion : by_region) { m_config.apply(print.regions[®ion - &by_region.front()]->config); ExtrusionEntityCollection chained = region.infills.chained_path_from(m_last_pos, false); for (ExtrusionEntity *fill : chained.entities) { auto *eec = dynamic_cast(fill); if (eec) { ExtrusionEntityCollection chained2 = eec->chained_path_from(m_last_pos, false); for (ExtrusionEntity *ee : chained2.entities) gcode += this->extrude_entity(*ee, "infill"); } else gcode += this->extrude_entity(*fill, "infill"); } } return gcode; } std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills) { std::string gcode; if (! support_fills.entities.empty()) { const char *support_label = "support material"; const char *support_interface_label = "support material interface"; const double support_speed = m_config.support_material_speed.value; const double support_interface_speed = m_config.support_material_interface_speed.get_abs_value(support_speed); for (const ExtrusionEntity *ee : support_fills.entities) { ExtrusionRole role = ee->role(); assert(role == erSupportMaterial || role == erSupportMaterialInterface); const char *label = (role == erSupportMaterial) ? support_label : support_interface_label; const double speed = (role == erSupportMaterial) ? support_speed : support_interface_speed; const ExtrusionPath *path = dynamic_cast(ee); if (path) gcode += this->extrude_path(*path, label, speed); else { const ExtrusionMultiPath *multipath = dynamic_cast(ee); assert(multipath != nullptr); if (multipath) gcode += this->extrude_multi_path(*multipath, label, speed); } } } return gcode; } void GCode::_write(FILE* file, const char *what, size_t size) { if (size > 0) { // writes string to file fwrite(what, 1, size, file); // updates time estimator and gcode lines vector m_time_estimator.add_gcode_block(what); } } void GCode::_writeln(FILE* file, const std::string &what) { if (! what.empty()) _write(file, (what.back() == '\n') ? what : (what + '\n')); } void GCode::_write_format(FILE* file, const char* format, ...) { va_list args; va_start(args, format); int buflen; { va_list args2; va_copy(args2, args); buflen = #ifdef _MSC_VER ::_vscprintf(format, args2) #else ::vsnprintf(nullptr, 0, format, args2) #endif + 1; va_end(args2); } char buffer[1024]; bool buffer_dynamic = buflen > 1024; char *bufptr = buffer_dynamic ? (char*)malloc(buflen) : buffer; int res = ::vsnprintf(bufptr, buflen, format, args); if (res > 0) _write(file, bufptr, res); if (buffer_dynamic) free(bufptr); va_end(args); } std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed) { std::string gcode; // go to first point of extrusion path if (!m_last_pos_defined || !m_last_pos.coincides_with(path.first_point())) { gcode += this->travel_to( path.first_point(), path.role(), "move to first " + description + " point" ); } // compensate retraction gcode += this->unretract(); // adjust acceleration { double acceleration; if (this->on_first_layer() && m_config.first_layer_acceleration.value > 0) { acceleration = m_config.first_layer_acceleration.value; } else if (m_config.perimeter_acceleration.value > 0 && is_perimeter(path.role())) { acceleration = m_config.perimeter_acceleration.value; } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())) { acceleration = m_config.bridge_acceleration.value; } else if (m_config.infill_acceleration.value > 0 && is_infill(path.role())) { acceleration = m_config.infill_acceleration.value; } else { acceleration = m_config.default_acceleration.value; } gcode += m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); } // calculate extrusion length per distance unit double e_per_mm = m_writer.extruder()->e_per_mm3() * path.mm3_per_mm; if (m_writer.extrusion_axis().empty()) e_per_mm = 0; // set speed if (speed == -1) { if (path.role() == erPerimeter) { speed = m_config.get_abs_value("perimeter_speed"); } else if (path.role() == erExternalPerimeter) { speed = m_config.get_abs_value("external_perimeter_speed"); } else if (path.role() == erOverhangPerimeter || path.role() == erBridgeInfill) { speed = m_config.get_abs_value("bridge_speed"); } else if (path.role() == erInternalInfill) { speed = m_config.get_abs_value("infill_speed"); } else if (path.role() == erSolidInfill) { speed = m_config.get_abs_value("solid_infill_speed"); } else if (path.role() == erTopSolidInfill) { speed = m_config.get_abs_value("top_solid_infill_speed"); } else if (path.role() == erGapFill) { speed = m_config.get_abs_value("gap_fill_speed"); } else { CONFESS("Invalid speed"); } } if (this->on_first_layer()) speed = m_config.get_abs_value("first_layer_speed", speed); if (m_volumetric_speed != 0. && speed == 0) speed = m_volumetric_speed / path.mm3_per_mm; if (m_config.max_volumetric_speed.value > 0) { // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) speed = std::min( speed, m_config.max_volumetric_speed.value / path.mm3_per_mm ); } if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) speed = std::min( speed, EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm ); } double F = speed * 60; // convert mm/sec to mm/min // extrude arc or line if (m_enable_extrusion_role_markers || m_enable_analyzer_markers) { if (path.role() != m_last_extrusion_role) { m_last_extrusion_role = path.role(); char buf[32]; sprintf(buf, ";_EXTRUSION_ROLE:%d\n", int(path.role())); gcode += buf; } } std::string comment; if (m_enable_cooling_markers) { if (is_bridge(path.role())) gcode += ";_BRIDGE_FAN_START\n"; else comment = ";_EXTRUDE_SET_SPEED"; if (path.role() == erExternalPerimeter) comment += ";_EXTERNAL_PERIMETER"; } // F is mm per minute. gcode += m_writer.set_speed(F, "", comment); double path_length = 0.; { std::string comment = m_config.gcode_comments ? description : ""; for (const Line &line : path.polyline.lines()) { const double line_length = line.length() * SCALING_FACTOR; path_length += line_length; gcode += m_writer.extrude_to_xy( this->point_to_gcode(line.b), e_per_mm * line_length, comment); } } if (m_enable_cooling_markers) gcode += is_bridge(path.role()) ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n"; this->set_last_pos(path.last_point()); return gcode; } // This method accepts &point in print coordinates. std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment) { /* Define the travel move as a line between current position and the taget point. This is expressed in print coordinates, so it will need to be translated by this->origin in order to get G-code coordinates. */ Polyline travel; travel.append(this->last_pos()); travel.append(point); // check whether a straight travel move would need retraction bool needs_retraction = this->needs_retraction(travel, role); // if a retraction would be needed, try to use avoid_crossing_perimeters to plan a // multi-hop travel path inside the configuration space if (needs_retraction && m_config.avoid_crossing_perimeters && ! m_avoid_crossing_perimeters.disable_once) { travel = m_avoid_crossing_perimeters.travel_to(*this, point); // check again whether the new travel path still needs a retraction needs_retraction = this->needs_retraction(travel, role); //if (needs_retraction && m_layer_index > 1) exit(0); } // Re-allow avoid_crossing_perimeters for the next travel moves m_avoid_crossing_perimeters.disable_once = false; m_avoid_crossing_perimeters.use_external_mp_once = false; // generate G-code for the travel move std::string gcode; if (needs_retraction) gcode += this->retract(); else // Reset the wipe path when traveling, so one would not wipe along an old path. m_wipe.reset_path(); // use G1 because we rely on paths being straight (G0 may make round paths) Lines lines = travel.lines(); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) gcode += m_writer.travel_to_xy(this->point_to_gcode(line->b), comment); return gcode; } bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) { if (travel.length() < scale_(EXTRUDER_CONFIG(retract_before_travel))) { // skip retraction if the move is shorter than the configured threshold return false; } if (role == erSupportMaterial) { const SupportLayer* support_layer = dynamic_cast(m_layer); //FIXME support_layer->support_islands.contains should use some search structure! if (support_layer != NULL && support_layer->support_islands.contains(travel)) // skip retraction if this is a travel move inside a support material island //FIXME not retracting over a long path may cause oozing, which in turn may result in missing material // at the end of the extrusion path! return false; } if (m_config.only_retract_when_crossing_perimeters && m_layer != nullptr && m_config.fill_density.value > 0 && m_layer->any_internal_region_slice_contains(travel)) // Skip retraction if travel is contained in an internal slice *and* // internal infill is enabled (so that stringing is entirely not visible). //FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first. return false; // retract if only_retract_when_crossing_perimeters is disabled or doesn't apply return true; } std::string GCode::retract(bool toolchange) { std::string gcode; if (m_writer.extruder() == nullptr) return gcode; // wipe (if it's enabled for this extruder and we have a stored wipe path) if (EXTRUDER_CONFIG(wipe) && m_wipe.has_path()) { gcode += toolchange ? m_writer.retract_for_toolchange(true) : m_writer.retract(true); gcode += m_wipe.wipe(*this, toolchange); } /* The parent class will decide whether we need to perform an actual retraction (the extruder might be already retracted fully or partially). We call these methods even if we performed wipe, since this will ensure the entire retraction length is honored in case wipe path was too short. */ gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract(); gcode += m_writer.reset_e(); if (m_writer.extruder()->retract_length() > 0 || m_config.use_firmware_retraction) gcode += m_writer.lift(); return gcode; } std::string GCode::set_extruder(unsigned int extruder_id) { if (!m_writer.need_toolchange(extruder_id)) return ""; // if we are running a single-extruder setup, just set the extruder and return nothing if (!m_writer.multiple_extruders) { m_placeholder_parser.set("current_extruder", extruder_id); return m_writer.toolchange(extruder_id); } // prepend retraction on the current extruder std::string gcode = this->retract(true); // Always reset the extrusion path, even if the tool change retract is set to zero. m_wipe.reset_path(); if (m_writer.extruder() != nullptr) { // Process the custom end_filament_gcode in case of single_extruder_multi_material. unsigned int old_extruder_id = m_writer.extruder()->id(); const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id); if (m_config.single_extruder_multi_material && ! end_filament_gcode.empty()) { gcode += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); check_add_eol(gcode); } } m_placeholder_parser.set("current_extruder", extruder_id); if (m_writer.extruder() != nullptr && ! m_config.toolchange_gcode.value.empty()) { // Process the custom toolchange_gcode. DynamicConfig config; config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id())); config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); gcode += placeholder_parser_process("toolchange_gcode", m_config.toolchange_gcode.value, extruder_id, &config); check_add_eol(gcode); } // If ooze prevention is enabled, park current extruder in the nearest // standby point and set it to the standby temperature. if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) gcode += m_ooze_prevention.pre_toolchange(*this); // Append the toolchange command. gcode += m_writer.toolchange(extruder_id); // Append the filament start G-code for single_extruder_multi_material. const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); if (m_config.single_extruder_multi_material && ! start_filament_gcode.empty()) { // Process the start_filament_gcode for the active filament only. gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); check_add_eol(gcode); } // Set the new extruder to the operating temperature. if (m_ooze_prevention.enable) gcode += m_ooze_prevention.post_toolchange(*this); return gcode; } // convert a model-space scaled point into G-code coordinates Pointf GCode::point_to_gcode(const Point &point) const { Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset); return Pointf( unscale(point.x) + m_origin.x - extruder_offset.x, unscale(point.y) + m_origin.y - extruder_offset.y); } // convert a model-space scaled point into G-code coordinates Point GCode::gcode_to_point(const Pointf &point) const { Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset); return Point( scale_(point.x - m_origin.x + extruder_offset.x), scale_(point.y - m_origin.y + extruder_offset.y)); } } Slic3r-version_1.39.1/xs/src/libslic3r/GCode.hpp000066400000000000000000000363651324354444700213410ustar00rootroot00000000000000#ifndef slic3r_GCode_hpp_ #define slic3r_GCode_hpp_ #include "libslic3r.h" #include "ExPolygon.hpp" #include "GCodeWriter.hpp" #include "Layer.hpp" #include "MotionPlanner.hpp" #include "Point.hpp" #include "PlaceholderParser.hpp" #include "Print.hpp" #include "PrintConfig.hpp" #include "GCode/CoolingBuffer.hpp" #include "GCode/PressureEqualizer.hpp" #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" #include "GCodeTimeEstimator.hpp" #include "EdgeGrid.hpp" #include #include namespace Slic3r { // Forward declarations. class GCode; class AvoidCrossingPerimeters { public: // this flag triggers the use of the external configuration space bool use_external_mp; bool use_external_mp_once; // just for the next travel move // this flag disables avoid_crossing_perimeters just for the next travel move // we enable it by default for the first travel move in print bool disable_once; AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {} ~AvoidCrossingPerimeters() {} void init_external_mp(const ExPolygons &islands) { m_external_mp = Slic3r::make_unique(islands); } void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } Polyline travel_to(const GCode &gcodegen, const Point &point); private: std::unique_ptr m_external_mp; std::unique_ptr m_layer_mp; }; class OozePrevention { public: bool enable; Points standby_points; OozePrevention() : enable(false) {} std::string pre_toolchange(GCode &gcodegen); std::string post_toolchange(GCode &gcodegen); private: int _get_temp(GCode &gcodegen); }; class Wipe { public: bool enable; Polyline path; Wipe() : enable(false) {} bool has_path() const { return !this->path.points.empty(); } void reset_path() { this->path = Polyline(); } std::string wipe(GCode &gcodegen, bool toolchange = false); }; class WipeTowerIntegration { public: WipeTowerIntegration( const PrintConfig &print_config, const WipeTower::ToolChangeResult &priming, const std::vector> &tool_changes, const WipeTower::ToolChangeResult &final_purge) : m_left(float(print_config.wipe_tower_x.value)), m_right(float(print_config.wipe_tower_x.value + print_config.wipe_tower_width.value)), m_priming(priming), m_tool_changes(tool_changes), m_final_purge(final_purge), m_layer_idx(-1), m_tool_change_idx(0), m_brim_done(false) {} std::string prime(GCode &gcodegen); void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer); std::string finalize(GCode &gcodegen); private: WipeTowerIntegration& operator=(const WipeTowerIntegration&); std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const; // Left / right edges of the wipe tower, for the planning of wipe moves. const float m_left; const float m_right; // Reference to cached values at the Printer class. const WipeTower::ToolChangeResult &m_priming; const std::vector> &m_tool_changes; const WipeTower::ToolChangeResult &m_final_purge; // Current layer index. int m_layer_idx; int m_tool_change_idx; bool m_brim_done; }; class GCode { public: GCode() : m_enable_loop_clipping(true), m_enable_cooling_markers(false), m_enable_extrusion_role_markers(false), m_enable_analyzer_markers(false), m_layer_count(0), m_layer_index(-1), m_layer(nullptr), m_volumetric_speed(0), m_last_pos_defined(false), m_last_extrusion_role(erNone), m_brim_done(false), m_second_layer_things_done(false), m_last_obj_copy(nullptr, Point(std::numeric_limits::max(), std::numeric_limits::max())) {} ~GCode() {} // throws std::runtime_exception void do_export(Print *print, const char *path); // Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests. const Pointf& origin() const { return m_origin; } void set_origin(const Pointf &pointf); void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Pointf(x, y)); } const Point& last_pos() const { return m_last_pos; } Pointf point_to_gcode(const Point &point) const; Point gcode_to_point(const Pointf &point) const; const FullPrintConfig &config() const { return m_config; } const Layer* layer() const { return m_layer; } GCodeWriter& writer() { return m_writer; } PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } // Process a template through the placeholder parser, collect error messages to be reported // inside the generated string and after the G-code export finishes. std::string placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr); bool enable_cooling_markers() const { return m_enable_cooling_markers; } // For Perl bindings, to be used exclusively by unit tests. unsigned int layer_count() const { return m_layer_count; } void set_layer_count(unsigned int value) { m_layer_count = value; } void apply_print_config(const PrintConfig &print_config); protected: void _do_export(Print &print, FILE *file); // Object and support extrusions of the same PrintObject at the same print_z. struct LayerToPrint { LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} const Layer *object_layer; const SupportLayer *support_layer; const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; } const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } }; static std::vector collect_layers_to_print(const PrintObject &object); static std::vector>> collect_layers_to_print(const Print &print); void process_layer( // Write into the output file. FILE *file, const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, const ToolOrdering::LayerTools &layer_tools, // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_idx = size_t(-1)); void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } bool last_pos_defined() const { return m_last_pos_defined; } void set_extruders(const std::vector &extruder_ids); std::string preamble(); std::string change_layer(coordf_t print_z); std::string extrude_entity(const ExtrusionEntity &entity, std::string description = "", double speed = -1., std::unique_ptr *lower_layer_edge_grid = nullptr); std::string extrude_loop(ExtrusionLoop loop, std::string description, double speed = -1., std::unique_ptr *lower_layer_edge_grid = nullptr); std::string extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.); std::string extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.); // Extruding multiple objects with soluble / non-soluble / combined supports // on a multi-material printer, trying to minimize tool switches. // Following structures sort extrusions by the extruder ID, by an order of objects and object islands. struct ObjectByExtruder { ObjectByExtruder() : support(nullptr), support_extrusion_role(erNone) {} const ExtrusionEntityCollection *support; // erSupportMaterial / erSupportMaterialInterface or erMixed. ExtrusionRole support_extrusion_role; struct Island { struct Region { ExtrusionEntityCollection perimeters; ExtrusionEntityCollection infills; }; std::vector by_region; }; std::vector islands; }; std::string extrude_perimeters(const Print &print, const std::vector &by_region, std::unique_ptr &lower_layer_edge_grid); std::string extrude_infill(const Print &print, const std::vector &by_region); std::string extrude_support(const ExtrusionEntityCollection &support_fills); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone); std::string retract(bool toolchange = false); std::string unretract() { return m_writer.unlift() + m_writer.unretract(); } std::string set_extruder(unsigned int extruder_id); /* Origin of print coordinates expressed in unscaled G-code coordinates. This affects the input arguments supplied to the extrude*() and travel_to() methods. */ Pointf m_origin; FullPrintConfig m_config; GCodeWriter m_writer; PlaceholderParser m_placeholder_parser; // Collection of templates, on which the placeholder substitution failed. std::set m_placeholder_parser_failed_templates; OozePrevention m_ooze_prevention; Wipe m_wipe; AvoidCrossingPerimeters m_avoid_crossing_perimeters; bool m_enable_loop_clipping; // If enabled, the G-code generator will put following comments at the ends // of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _BRIDGE_FAN_START, _BRIDGE_FAN_END // Those comments are received and consumed (removed from the G-code) by the CoolingBuffer.pm Perl module. bool m_enable_cooling_markers; // Markers for the Pressure Equalizer to recognize the extrusion type. // The Pressure Equalizer removes the markers from the final G-code. bool m_enable_extrusion_role_markers; // Extended markers for the G-code Analyzer. // The G-code Analyzer will remove these comments from the final G-code. bool m_enable_analyzer_markers; // How many times will change_layer() be called? // change_layer() will update the progress bar. unsigned int m_layer_count; // Progress bar indicator. Increments from -1 up to layer_count. int m_layer_index; // Current layer processed. Insequential printing mode, only a single copy will be printed. // In non-sequential mode, all its copies will be printed. const Layer* m_layer; std::map m_seam_position; double m_volumetric_speed; // Support for the extrusion role markers. Which marker is active? ExtrusionRole m_last_extrusion_role; Point m_last_pos; bool m_last_pos_defined; std::unique_ptr m_cooling_buffer; std::unique_ptr m_spiral_vase; std::unique_ptr m_pressure_equalizer; std::unique_ptr m_wipe_tower; // Heights at which the skirt has already been extruded. std::vector m_skirt_done; // Has the brim been extruded already? Brim is being extruded only for the first object of a multi-object print. bool m_brim_done; // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. bool m_second_layer_things_done; // Index of a last object copy extruded. std::pair m_last_obj_copy; // Time estimator GCodeTimeEstimator m_time_estimator; // Write a string into a file. void _write(FILE* file, const std::string& what) { this->_write(file, what.c_str(), what.size()); } void _write(FILE* file, const char *what, size_t size); // Write a string into a file. // Add a newline, if the string does not end with a newline already. // Used to export a custom G-code section processed by the PlaceholderParser. void _writeln(FILE* file, const std::string& what); // Formats and write into a file the given data. void _write_format(FILE* file, const char* format, ...); std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1); void _print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); // this flag triggers first layer speeds bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; } friend ObjectByExtruder& object_by_extruder( std::map> &by_extruder, unsigned int extruder_id, size_t object_idx, size_t num_objects); friend std::vector& object_islands_by_extruder( std::map> &by_extruder, unsigned int extruder_id, size_t object_idx, size_t num_objects, size_t num_islands); friend class WipeTowerIntegration; }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/GCode/000077500000000000000000000000001324354444700206135ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/libslic3r/GCode/Analyzer.cpp000066400000000000000000000244701324354444700231130ustar00rootroot00000000000000#include #include #include #include "../libslic3r.h" #include "../PrintConfig.hpp" #include "Analyzer.hpp" namespace Slic3r { void GCodeMovesDB::reset() { for (size_t i = 0; i < m_layers.size(); ++ i) delete m_layers[i]; m_layers.clear(); } GCodeAnalyzer::GCodeAnalyzer(const Slic3r::GCodeConfig *config) : m_config(config) { reset(); m_moves = new GCodeMovesDB(); } GCodeAnalyzer::~GCodeAnalyzer() { delete m_moves; } void GCodeAnalyzer::reset() { output_buffer.clear(); output_buffer_length = 0; m_current_extruder = 0; // Zero the position of the XYZE axes + the current feed memset(m_current_pos, 0, sizeof(float) * 5); m_current_extrusion_role = erNone; m_current_extrusion_width = 0; m_current_extrusion_height = 0; // Expect the first command to fill the nozzle (deretract). m_retracted = true; m_moves->reset(); } const char* GCodeAnalyzer::process(const char *szGCode, bool flush) { // Reset length of the output_buffer. output_buffer_length = 0; if (szGCode != 0) { const char *p = szGCode; while (*p != 0) { // Find end of the line. const char *endl = p; // Slic3r always generates end of lines in a Unix style. for (; *endl != 0 && *endl != '\n'; ++ endl) ; // Process a G-code line, store it into the provided GCodeLine object. bool should_output = process_line(p, endl - p); if (*endl == '\n') ++ endl; if (should_output) push_to_output(p, endl - p); p = endl; } } return output_buffer.data(); } // Is a white space? static inline bool is_ws(const char c) { return c == ' ' || c == '\t'; } // Is it an end of line? Consider a comment to be an end of line as well. static inline bool is_eol(const char c) { return c == 0 || c == '\r' || c == '\n' || c == ';'; }; // Is it a white space or end of line? static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); }; // Eat whitespaces. static void eatws(const char *&line) { while (is_ws(*line)) ++ line; } // Parse an int starting at the current position of a line. // If succeeded, the line pointer is advanced. static inline int parse_int(const char *&line) { char *endptr = NULL; long result = strtol(line, &endptr, 10); if (endptr == NULL || !is_ws_or_eol(*endptr)) throw std::runtime_error("GCodeAnalyzer: Error parsing an int"); line = endptr; return int(result); }; // Parse an int starting at the current position of a line. // If succeeded, the line pointer is advanced. static inline float parse_float(const char *&line) { char *endptr = NULL; float result = strtof(line, &endptr); if (endptr == NULL || !is_ws_or_eol(*endptr)) throw std::runtime_error("GCodeAnalyzer: Error parsing a float"); line = endptr; return result; }; #define EXTRUSION_ROLE_TAG ";_EXTRUSION_ROLE:" bool GCodeAnalyzer::process_line(const char *line, const size_t len) { if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) { line += strlen(EXTRUSION_ROLE_TAG); int role = atoi(line); this->m_current_extrusion_role = ExtrusionRole(role); return false; } /* // Set the type, copy the line to the buffer. buf.type = GCODE_MOVE_TYPE_OTHER; buf.modified = false; if (buf.raw.size() < len + 1) buf.raw.assign(line, line + len + 1); else memcpy(buf.raw.data(), line, len); buf.raw[len] = 0; buf.raw_length = len; memcpy(buf.pos_start, m_current_pos, sizeof(float)*5); memcpy(buf.pos_end, m_current_pos, sizeof(float)*5); memset(buf.pos_provided, 0, 5); buf.volumetric_extrusion_rate = 0.f; buf.volumetric_extrusion_rate_start = 0.f; buf.volumetric_extrusion_rate_end = 0.f; buf.max_volumetric_extrusion_rate_slope_positive = 0.f; buf.max_volumetric_extrusion_rate_slope_negative = 0.f; buf.extrusion_role = m_current_extrusion_role; // Parse the G-code line, store the result into the buf. switch (toupper(*line ++)) { case 'G': { int gcode = parse_int(line); eatws(line); switch (gcode) { case 0: case 1: { // G0, G1: A FFF 3D printer does not make a difference between the two. float new_pos[5]; memcpy(new_pos, m_current_pos, sizeof(float)*5); bool changed[5] = { false, false, false, false, false }; while (!is_eol(*line)) { char axis = toupper(*line++); int i = -1; switch (axis) { case 'X': case 'Y': case 'Z': i = axis - 'X'; break; case 'E': i = 3; break; case 'F': i = 4; break; default: assert(false); } if (i == -1) throw std::runtime_error(std::string("GCodeAnalyzer: Invalid axis for G0/G1: ") + axis); buf.pos_provided[i] = true; new_pos[i] = parse_float(line); if (i == 3 && m_config->use_relative_e_distances.value) new_pos[i] += m_current_pos[i]; changed[i] = new_pos[i] != m_current_pos[i]; eatws(line); } if (changed[3]) { // Extrusion, retract or unretract. float diff = new_pos[3] - m_current_pos[3]; if (diff < 0) { buf.type = GCODE_MOVE_TYPE_RETRACT; m_retracted = true; } else if (! changed[0] && ! changed[1] && ! changed[2]) { // assert(m_retracted); buf.type = GCODE_MOVE_TYPE_UNRETRACT; m_retracted = false; } else { assert(changed[0] || changed[1]); // Moving in XY plane. buf.type = GCODE_MOVE_TYPE_EXTRUDE; // Calculate the volumetric extrusion rate. float diff[4]; for (size_t i = 0; i < 4; ++ i) diff[i] = new_pos[i] - m_current_pos[i]; // volumetric extrusion rate = A_filament * F_xyz * L_e / L_xyz [mm^3/min] float len2 = diff[0]*diff[0]+diff[1]*diff[1]+diff[2]*diff[2]; float rate = m_filament_crossections[m_current_extruder] * new_pos[4] * sqrt((diff[3]*diff[3])/len2); buf.volumetric_extrusion_rate = rate; buf.volumetric_extrusion_rate_start = rate; buf.volumetric_extrusion_rate_end = rate; m_stat.update(rate, sqrt(len2)); if (rate < 10.f) { printf("Extremely low flow rate: %f\n", rate); } } } else if (changed[0] || changed[1] || changed[2]) { // Moving without extrusion. buf.type = GCODE_MOVE_TYPE_MOVE; } memcpy(m_current_pos, new_pos, sizeof(float) * 5); break; } case 92: { // G92 : Set Position // Set a logical coordinate position to a new value without actually moving the machine motors. // Which axes to set? bool set = false; while (!is_eol(*line)) { char axis = toupper(*line++); switch (axis) { case 'X': case 'Y': case 'Z': m_current_pos[axis - 'X'] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f; set = true; break; case 'E': m_current_pos[3] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f; set = true; break; default: throw std::runtime_error(std::string("GCodeAnalyzer: Incorrect axis in a G92 G-code: ") + axis); } eatws(line); } assert(set); break; } case 10: case 22: // Firmware retract. buf.type = GCODE_MOVE_TYPE_RETRACT; m_retracted = true; break; case 11: case 23: // Firmware unretract. buf.type = GCODE_MOVE_TYPE_UNRETRACT; m_retracted = false; break; default: // Ignore the rest. break; } break; } case 'M': { int mcode = parse_int(line); eatws(line); switch (mcode) { default: // Ignore the rest of the M-codes. break; } break; } case 'T': { // Activate an extruder head. int new_extruder = parse_int(line); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; m_retracted = true; buf.type = GCODE_MOVE_TYPE_TOOL_CHANGE; } else { buf.type = GCODE_MOVE_TYPE_NOOP; } break; } } buf.extruder_id = m_current_extruder; memcpy(buf.pos_end, m_current_pos, sizeof(float)*5); */ return true; } void GCodeAnalyzer::push_to_output(const char *text, const size_t len, bool add_eol) { // New length of the output buffer content. size_t len_new = output_buffer_length + len + 1; if (add_eol) ++ len_new; // Resize the output buffer to a power of 2 higher than the required memory. if (output_buffer.size() < len_new) { size_t v = len_new; // Compute the next highest power of 2 of 32-bit v // http://graphics.stanford.edu/~seander/bithacks.html v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; output_buffer.resize(v); } // Copy the text to the output. if (len != 0) { memcpy(output_buffer.data() + output_buffer_length, text, len); output_buffer_length += len; } if (add_eol) output_buffer[output_buffer_length ++] = '\n'; output_buffer[output_buffer_length] = 0; } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/GCode/Analyzer.hpp000066400000000000000000000153551324354444700231220ustar00rootroot00000000000000#ifndef slic3r_GCode_PressureEqualizer_hpp_ #define slic3r_GCode_PressureEqualizer_hpp_ #include "../libslic3r.h" #include "../PrintConfig.hpp" #include "../ExtrusionEntity.hpp" namespace Slic3r { enum GCodeMoveType { GCODE_MOVE_TYPE_NOOP, GCODE_MOVE_TYPE_RETRACT, GCODE_MOVE_TYPE_UNRETRACT, GCODE_MOVE_TYPE_TOOL_CHANGE, GCODE_MOVE_TYPE_MOVE, GCODE_MOVE_TYPE_EXTRUDE, }; // For visualization purposes, for the purposes of the G-code analysis and timing. // The size of this structure is 56B. // Keep the size of this structure as small as possible, because all moves of a complete print // may be held in RAM. struct GCodeMove { bool moving_xy(const float* pos_start) const { return fabs(pos_end[0] - pos_start[0]) > 0.f || fabs(pos_end[1] - pos_start[1]) > 0.f; } bool moving_xy() const { return moving_xy(get_pos_start()); } bool moving_z (const float* pos_start) const { return fabs(pos_end[2] - pos_start[2]) > 0.f; } bool moving_z () const { return moving_z(get_pos_start()); } bool extruding(const float* pos_start) const { return moving_xy() && pos_end[3] > pos_start[3]; } bool extruding() const { return extruding(get_pos_start()); } bool retracting(const float* pos_start) const { return pos_end[3] < pos_start[3]; } bool retracting() const { return retracting(get_pos_start()); } bool deretracting(const float* pos_start) const { return ! moving_xy() && pos_end[3] > pos_start[3]; } bool deretracting() const { return deretracting(get_pos_start()); } float dist_xy2(const float* pos_start) const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]); } float dist_xy2() const { return dist_xy2(get_pos_start()); } float dist_xyz2(const float* pos_start) const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]) + (pos_end[2] - pos_start[2]) * (pos_end[2] - pos_start[2]); } float dist_xyz2() const { return dist_xyz2(get_pos_start()); } float dist_xy(const float* pos_start) const { return sqrt(dist_xy2(pos_start)); } float dist_xy() const { return dist_xy(get_pos_start()); } float dist_xyz(const float* pos_start) const { return sqrt(dist_xyz2(pos_start)); } float dist_xyz() const { return dist_xyz(get_pos_start()); } float dist_e(const float* pos_start) const { return fabs(pos_end[3] - pos_start[3]); } float dist_e() const { return dist_e(get_pos_start()); } float feedrate() const { return pos_end[4]; } float time(const float* pos_start) const { return dist_xyz(pos_start) / feedrate(); } float time() const { return time(get_pos_start()); } float time_inv(const float* pos_start) const { return feedrate() / dist_xyz(pos_start); } float time_inv() const { return time_inv(get_pos_start()); } const float* get_pos_start() const { assert(type != GCODE_MOVE_TYPE_NOOP); return this[-1].pos_end; } // Pack the enums to conserve space. With C++x11 the allocation size could be declared for enums, but for old C++ this is the only portable way. // GCodeLineType uint8_t type; // Index of the active extruder. uint8_t extruder_id; // ExtrusionRole uint8_t extrusion_role; // For example, is it a bridge flow? Is the fan on? uint8_t flags; // X,Y,Z,E,F. Storing the state of the currently active extruder only. float pos_end[5]; // Extrusion width, height for this segment in um. uint16_t extrusion_width; uint16_t extrusion_height; }; typedef std::vector GCodeMoves; struct GCodeLayer { // Index of an object printed. size_t object_idx; // Index of an object instance printed. size_t object_instance_idx; // Index of the layer printed. size_t layer_idx; // Top z coordinate of the layer printed. float layer_z_top; // Moves over this layer. The 0th move is always of type GCODELINETYPE_NOOP and // it sets the initial position and tool for the layer. GCodeMoves moves; // Indices into m_moves, where the tool changes happen. // This is useful, if one wants to display just only a piece of the path quickly. std::vector tool_changes; }; typedef std::vector GCodeLayerPtrs; class GCodeMovesDB { public: GCodeMovesDB() {}; ~GCodeMovesDB() { reset(); } void reset(); GCodeLayerPtrs m_layers; }; // Processes a G-code to extract moves and their types. // This information is then used to render the print simulation colored by the extrusion type // or various speeds. // The GCodeAnalyzer is employed as a G-Code filter. It reads the G-code as it is generated, // parses the comments generated by Slic3r just for the analyzer, and removes these comments. class GCodeAnalyzer { public: GCodeAnalyzer(const Slic3r::GCodeConfig *config); ~GCodeAnalyzer(); void reset(); // Process a next batch of G-code lines. Flush the internal buffers if asked for. const char* process(const char *szGCode, bool flush); // Length of the buffer returned by process(). size_t get_output_buffer_length() const { return output_buffer_length; } private: // Keeps the reference, does not own the config. const Slic3r::GCodeConfig *m_config; // Internal data. // X,Y,Z,E,F float m_current_pos[5]; size_t m_current_extruder; ExtrusionRole m_current_extrusion_role; uint16_t m_current_extrusion_width; uint16_t m_current_extrusion_height; bool m_retracted; GCodeMovesDB *m_moves; // Output buffer will only grow. It will not be reallocated over and over. std::vector output_buffer; size_t output_buffer_length; bool process_line(const char *line, const size_t len); // Push the text to the end of the output_buffer. void push_to_output(const char *text, const size_t len, bool add_eol = true); }; } // namespace Slic3r #endif /* slic3r_GCode_PressureEqualizer_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/GCode/CoolingBuffer.cpp000066400000000000000000000563611324354444700240560ustar00rootroot00000000000000#include "../GCode.hpp" #include "CoolingBuffer.hpp" #include #include #include #include #if 0 #define DEBUG #define _DEBUG #undef NDEBUG #endif #include namespace Slic3r { CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_gcodegen(gcodegen), m_current_extruder(0) { this->reset(); } void CoolingBuffer::reset() { m_current_pos.assign(5, 0.f); Pointf3 pos = m_gcodegen.writer().get_position(); m_current_pos[0] = float(pos.x); m_current_pos[1] = float(pos.y); m_current_pos[2] = float(pos.z); m_current_pos[4] = float(m_gcodegen.config().travel_speed.value); } #define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id) { const FullPrintConfig &config = m_gcodegen.config(); const std::vector &extruders = m_gcodegen.writer().extruders(); const size_t num_extruders = extruders.size(); // Calculate the required per extruder time stretches. struct Adjustment { Adjustment(unsigned int extruder_id = 0) : extruder_id(extruder_id) {} // Calculate the total elapsed time per this extruder, adjusted for the slowdown. float elapsed_time_total() { float time_total = 0.f; for (const Line &line : lines) time_total += line.time; return time_total; } // Calculate the maximum time when slowing down. float maximum_time(bool slowdown_external_perimeters) { float time_total = 0.f; for (const Line &line : lines) if (line.adjustable(slowdown_external_perimeters)) { if (line.time_max == FLT_MAX) return FLT_MAX; else time_total += line.time_max; } else time_total += line.time; return time_total; } // Calculate the non-adjustable part of the total time. float non_adjustable_time(bool slowdown_external_perimeters) { float time_total = 0.f; for (const Line &line : lines) if (! line.adjustable(slowdown_external_perimeters)) time_total += line.time; return time_total; } float slow_down_maximum(bool slowdown_external_perimeters) { float time_total = 0.f; for (Line &line : lines) { if (line.adjustable(slowdown_external_perimeters)) { assert(line.time_max >= 0.f && line.time_max < FLT_MAX); line.slowdown = true; line.time = line.time_max; } time_total += line.time; } return time_total; } float slow_down_proportional(float factor, bool slowdown_external_perimeters) { assert(factor >= 1.f); float time_total = 0.f; for (Line &line : lines) { if (line.adjustable(slowdown_external_perimeters)) { line.slowdown = true; line.time = std::min(line.time_max, line.time * factor); } time_total += line.time; } return time_total; } bool operator<(const Adjustment &rhs) const { return this->extruder_id < rhs.extruder_id; } struct Line { enum Type { TYPE_SET_TOOL = 1 << 0, TYPE_EXTRUDE_END = 1 << 1, TYPE_BRIDGE_FAN_START = 1 << 2, TYPE_BRIDGE_FAN_END = 1 << 3, TYPE_G0 = 1 << 4, TYPE_G1 = 1 << 5, TYPE_ADJUSTABLE = 1 << 6, TYPE_EXTERNAL_PERIMETER = 1 << 7, TYPE_WIPE = 1 << 8, TYPE_G4 = 1 << 9, TYPE_G92 = 1 << 10, }; Line(unsigned int type, size_t line_start, size_t line_end) : type(type), line_start(line_start), line_end(line_end), length(0.f), time(0.f), time_max(0.f), slowdown(false) {} bool adjustable(bool slowdown_external_perimeters) const { return (this->type & TYPE_ADJUSTABLE) && (! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) && this->time < this->time_max; } size_t type; // Start of this line at the G-code snippet. size_t line_start; // End of this line at the G-code snippet. size_t line_end; // XY Euclidian length of this segment. float length; // Current duration of this segment. float time; // Maximum duration of this segment. float time_max; // If marked with the "slowdown" flag, the line has been slowed down. bool slowdown; }; // Extruder, for which the G-code will be adjusted. unsigned int extruder_id; // Parsed lines. std::vector lines; }; std::vector adjustments(num_extruders, Adjustment()); for (size_t i = 0; i < num_extruders; ++ i) adjustments[i].extruder_id = extruders[i].id(); const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); // Parse the layer G-code for the moves, which could be adjusted. { float min_print_speed = float(EXTRUDER_CONFIG(min_print_speed)); auto adjustment = std::lower_bound(adjustments.begin(), adjustments.end(), Adjustment(m_current_extruder)); unsigned int initial_extruder = m_current_extruder; const char *line_start = gcode.c_str(); const char *line_end = line_start; const char extrusion_axis = config.get_extrusion_axis()[0]; // Index of an existing Adjustment::Line of the current adjustment, which holds the feedrate setting command // for a sequence of extrusion moves. size_t active_speed_modifier = size_t(-1); for (; *line_start != 0; line_start = line_end) { while (*line_end != '\n' && *line_end != 0) ++ line_end; // sline will not contain the trailing '\n'. std::string sline(line_start, line_end); // Adjustment::Line will contain the trailing '\n'. if (*line_end == '\n') ++ line_end; Adjustment::Line line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); if (boost::starts_with(sline, "G0 ")) line.type = Adjustment::Line::TYPE_G0; else if (boost::starts_with(sline, "G1 ")) line.type = Adjustment::Line::TYPE_G1; else if (boost::starts_with(sline, "G92 ")) line.type = Adjustment::Line::TYPE_G92; if (line.type) { // G0, G1 or G92 // Parse the G-code line. std::vector new_pos(m_current_pos); const char *c = sline.data() + 3; for (;;) { // Skip whitespaces. for (; *c == ' ' || *c == '\t'; ++ c); if (*c == 0 || *c == ';') break; // Parse the axis. size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); if (axis != size_t(-1)) { new_pos[axis] = float(atof(++c)); if (axis == 4) // Convert mm/min to mm/sec. new_pos[4] /= 60.f; } // Skip this word. for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); } bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); bool wipe = boost::contains(sline, ";_WIPE"); if (external_perimeter) line.type |= Adjustment::Line::TYPE_EXTERNAL_PERIMETER; if (wipe) line.type |= Adjustment::Line::TYPE_WIPE; if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) { line.type |= Adjustment::Line::TYPE_ADJUSTABLE; active_speed_modifier = adjustment->lines.size(); } if ((line.type & Adjustment::Line::TYPE_G92) == 0) { // G0 or G1. Calculate the duration. if (config.use_relative_e_distances.value) // Reset extruder accumulator. m_current_pos[3] = 0.f; float dif[4]; for (size_t i = 0; i < 4; ++ i) dif[i] = new_pos[i] - m_current_pos[i]; float dxy2 = dif[0] * dif[0] + dif[1] * dif[1]; float dxyz2 = dxy2 + dif[2] * dif[2]; if (dxyz2 > 0.f) { // Movement in xyz, calculate time from the xyz Euclidian distance. line.length = sqrt(dxyz2); } else if (std::abs(dif[3]) > 0.f) { // Movement in the extruder axis. line.length = std::abs(dif[3]); } if (line.length > 0) line.time = line.length / new_pos[4]; // current F line.time_max = line.time; if ((line.type & Adjustment::Line::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) line.time_max = (min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / min_print_speed); if (active_speed_modifier < adjustment->lines.size() && (line.type & Adjustment::Line::TYPE_G1)) { Adjustment::Line &sm = adjustment->lines[active_speed_modifier]; sm.length += line.length; sm.time += line.time; if (sm.time_max != FLT_MAX) { if (line.time_max == FLT_MAX) sm.time_max = FLT_MAX; else sm.time_max += line.time_max; } // Don't store this line. line.type = 0; } } m_current_pos = std::move(new_pos); } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { line.type = Adjustment::Line::TYPE_EXTRUDE_END; active_speed_modifier = size_t(-1); } else if (boost::starts_with(sline, toolchange_prefix)) { // Switch the tool. line.type = Adjustment::Line::TYPE_SET_TOOL; unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size()); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; min_print_speed = float(EXTRUDER_CONFIG(min_print_speed)); adjustment = std::lower_bound(adjustments.begin(), adjustments.end(), Adjustment(m_current_extruder)); } } else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) { line.type = Adjustment::Line::TYPE_BRIDGE_FAN_START; } else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) { line.type = Adjustment::Line::TYPE_BRIDGE_FAN_END; } else if (boost::starts_with(sline, "G4 ")) { // Parse the wait time. line.type = Adjustment::Line::TYPE_G4; size_t pos_S = sline.find('S', 3); size_t pos_P = sline.find('P', 3); line.time = line.time_max = float( (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); } if (line.type != 0) adjustment->lines.emplace_back(std::move(line)); } m_current_extruder = initial_extruder; } // Sort the extruders by the increasing slowdown_below_layer_time. std::vector by_slowdown_layer_time; by_slowdown_layer_time.reserve(num_extruders); // Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time). // Collect total print time of non-adjustable extruders. float elapsed_time_total_non_adjustable = 0.f; for (size_t i = 0; i < num_extruders; ++ i) { if (config.cooling.get_at(extruders[i].id())) by_slowdown_layer_time.emplace_back(i); else elapsed_time_total_non_adjustable += adjustments[i].elapsed_time_total(); } std::sort(by_slowdown_layer_time.begin(), by_slowdown_layer_time.end(), [&config, &extruders](const size_t idx1, const size_t idx2){ return config.slowdown_below_layer_time.get_at(extruders[idx1].id()) < config.slowdown_below_layer_time.get_at(extruders[idx2].id()); }); // Elapsed time after adjustment. float elapsed_time_total = 0.f; { // Elapsed time for the already adjusted extruders. float elapsed_time_total0 = elapsed_time_total_non_adjustable; for (size_t i_by_slowdown_layer_time = 0; i_by_slowdown_layer_time < by_slowdown_layer_time.size(); ++ i_by_slowdown_layer_time) { // Idx in adjustments. size_t idx = by_slowdown_layer_time[i_by_slowdown_layer_time]; // Macro to sum or adjust all sections starting with i_by_slowdown_layer_time. #define FORALL_UNPROCESSED(ACCUMULATOR, ACTION) \ ACCUMULATOR = elapsed_time_total0;\ for (size_t j = i_by_slowdown_layer_time; j < by_slowdown_layer_time.size(); ++ j) \ ACCUMULATOR += adjustments[by_slowdown_layer_time[j]].ACTION // Calculate the current adjusted elapsed_time_total over the non-finalized extruders. float total; FORALL_UNPROCESSED(total, elapsed_time_total()); float slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(adjustments[idx].extruder_id)) * 1.001f; if (total > slowdown_below_layer_time) { // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything. } else { // Adjust this and all the following (higher config.slowdown_below_layer_time) extruders. // Sum maximum slow down time as if everything was slowed down including the external perimeters. float max_time; FORALL_UNPROCESSED(max_time, maximum_time(true)); if (max_time > slowdown_below_layer_time) { // By slowing every possible movement, the layer time could be reached. Now decide // whether the external perimeters shall be slowed down as well. float max_time_nep; FORALL_UNPROCESSED(max_time_nep, maximum_time(false)); if (max_time_nep > slowdown_below_layer_time) { // It is sufficient to slow down the non-external perimeter moves to reach the target layer time. // Slow down the non-external perimeters proportionally. float non_adjustable_time; FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(false)); // The following step is a linear programming task due to the minimum movement speeds of the print moves. // Run maximum 5 iterations until a good enough approximation is reached. for (size_t iter = 0; iter < 5; ++ iter) { float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time); assert(factor > 1.f); FORALL_UNPROCESSED(total, slow_down_proportional(factor, false)); if (total > 0.95f * slowdown_below_layer_time) break; } } else { // Slow down everything. First slow down the non-external perimeters to maximum. FORALL_UNPROCESSED(total, slow_down_maximum(false)); // Slow down the external perimeters proportionally. float non_adjustable_time; FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(true)); for (size_t iter = 0; iter < 5; ++ iter) { float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time); assert(factor > 1.f); FORALL_UNPROCESSED(total, slow_down_proportional(factor, true)); if (total > 0.95f * slowdown_below_layer_time) break; } } } else { // Slow down to maximum possible. FORALL_UNPROCESSED(total, slow_down_maximum(true)); } } #undef FORALL_UNPROCESSED // Sum the final elapsed time for all extruders up to i_by_slowdown_layer_time. if (i_by_slowdown_layer_time + 1 == by_slowdown_layer_time.size()) // Optimization for single extruder prints. elapsed_time_total0 = total; else elapsed_time_total0 += adjustments[idx].elapsed_time_total(); } elapsed_time_total = elapsed_time_total0; } // Transform the G-code. // First sort the adjustment lines by their position in the source G-code. std::vector lines; { size_t n_lines = 0; for (const Adjustment &adj : adjustments) n_lines += adj.lines.size(); lines.reserve(n_lines); for (const Adjustment &adj : adjustments) for (const Adjustment::Line &line : adj.lines) lines.emplace_back(&line); std::sort(lines.begin(), lines.end(), [](const Adjustment::Line *ln1, const Adjustment::Line *ln2) { return ln1->line_start < ln2->line_start; } ); } // Second generate the adjusted G-code. std::string new_gcode; new_gcode.reserve(gcode.size() * 2); int fan_speed = -1; bool bridge_fan_control = false; int bridge_fan_speed = 0; auto change_extruder_set_fan = [ this, layer_id, elapsed_time_total, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() { const FullPrintConfig &config = m_gcodegen.config(); int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; if (layer_id >= EXTRUDER_CONFIG(disable_fan_first_layers)) { int max_fan_speed = EXTRUDER_CONFIG(max_fan_speed); float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time)); float fan_below_layer_time = float(EXTRUDER_CONFIG(fan_below_layer_time)); if (EXTRUDER_CONFIG(cooling)) { if (elapsed_time_total < slowdown_below_layer_time) { // Layer time very short. Enable the fan to a full throttle. fan_speed_new = max_fan_speed; } else if (elapsed_time_total < fan_below_layer_time) { // Layer time quite short. Enable the fan proportionally according to the current layer time. assert(elapsed_time_total >= slowdown_below_layer_time); double t = (elapsed_time_total - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time); fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5); } } bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed); bridge_fan_control = bridge_fan_speed > fan_speed_new; } else { bridge_fan_control = false; bridge_fan_speed = 0; fan_speed_new = 0; } if (fan_speed_new != fan_speed) { fan_speed = fan_speed_new; new_gcode += m_gcodegen.writer().set_fan(fan_speed); } }; change_extruder_set_fan(); size_t pos = 0; for (const Adjustment::Line *line : lines) { if (line->line_start > pos) new_gcode.append(gcode.c_str() + pos, line->line_start - pos); if (line->type & Adjustment::Line::TYPE_SET_TOOL) { unsigned int new_extruder = (unsigned int)atoi(gcode.c_str() + line->line_start + toolchange_prefix.size()); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; change_extruder_set_fan(); } new_gcode.append(gcode.c_str() + line->line_start, line->line_end - line->line_start); } else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_START) { if (bridge_fan_control) new_gcode += m_gcodegen.writer().set_fan(bridge_fan_speed, true); } else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_END) { if (bridge_fan_control) new_gcode += m_gcodegen.writer().set_fan(fan_speed, true); } else if (line->type & Adjustment::Line::TYPE_EXTRUDE_END) { // Just remove this comment. } else if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) { // Start of the comment. The line type indicates there must be some comment present. const char *end = strchr(gcode.c_str() + line->line_start, ';'); if (line->slowdown) { // Replace the feedrate. const char *pos = strstr(gcode.c_str() + line->line_start + 2, " F") + 2; new_gcode.append(gcode.c_str() + line->line_start, pos - gcode.c_str() - line->line_start); char buf[64]; sprintf(buf, "%d", int(floor(60. * (line->length / line->time) + 0.5))); new_gcode += buf; // Skip the non-whitespaces up to the comment. for (; *pos != ' ' && *pos != ';'; ++ pos); // Append the rest of the line without the comment. if (pos < end) new_gcode.append(pos, end - pos); } else { // Append the line without the comment. new_gcode.append(gcode.c_str() + line->line_start, end - gcode.c_str() - line->line_start); } // Process the comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE" std::string comment(end, gcode.c_str() + line->line_end); boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", ""); if (line->type & Adjustment::Line::TYPE_EXTERNAL_PERIMETER) boost::replace_all(comment, ";_EXTERNAL_PERIMETER", ""); if (line->type & Adjustment::Line::TYPE_WIPE) boost::replace_all(comment, ";_WIPE", ""); new_gcode += comment; } else { new_gcode.append(gcode.c_str() + line->line_start, line->line_end - line->line_start); } pos = line->line_end; } if (pos < gcode.size()) new_gcode.append(gcode.c_str() + pos, gcode.size() - pos); return new_gcode; } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/GCode/CoolingBuffer.hpp000066400000000000000000000017721324354444700240570ustar00rootroot00000000000000#ifndef slic3r_CoolingBuffer_hpp_ #define slic3r_CoolingBuffer_hpp_ #include "libslic3r.h" #include #include namespace Slic3r { class GCode; class Layer; /* A standalone G-code filter, to control cooling of the print. The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited and the print is modified to stretch over a minimum layer time. */ class CoolingBuffer { public: CoolingBuffer(GCode &gcodegen); void reset(); void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; } std::string process_layer(const std::string &gcode, size_t layer_id); GCode* gcodegen() { return &m_gcodegen; } private: CoolingBuffer& operator=(const CoolingBuffer&); GCode& m_gcodegen; std::string m_gcode; // Internal data. // X,Y,Z,E,F std::vector m_axis; std::vector m_current_pos; unsigned int m_current_extruder; }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/GCode/PressureEqualizer.cpp000066400000000000000000000621611324354444700250170ustar00rootroot00000000000000#include #include #include #include "../libslic3r.h" #include "../PrintConfig.hpp" #include "PressureEqualizer.hpp" namespace Slic3r { PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig *config) : m_config(config) { reset(); } PressureEqualizer::~PressureEqualizer() { } void PressureEqualizer::reset() { circular_buffer_pos = 0; circular_buffer_size = 100; circular_buffer_items = 0; circular_buffer.assign(circular_buffer_size, GCodeLine()); // Preallocate some data, so that output_buffer.data() will return an empty string. output_buffer.assign(32, 0); output_buffer_length = 0; m_current_extruder = 0; // Zero the position of the XYZE axes + the current feed memset(m_current_pos, 0, sizeof(float) * 5); m_current_extrusion_role = erNone; // Expect the first command to fill the nozzle (deretract). m_retracted = true; // Calculate filamet crossections for the multiple extruders. m_filament_crossections.clear(); for (size_t i = 0; i < m_config->filament_diameter.values.size(); ++ i) { double r = m_config->filament_diameter.values[i]; double a = 0.25f*M_PI*r*r; m_filament_crossections.push_back(float(a)); } m_max_segment_length = 20.f; // Volumetric rate of a 0.45mm x 0.2mm extrusion at 60mm/s XY movement: 0.45*0.2*60*60=5.4*60 = 324 mm^3/min // Volumetric rate of a 0.45mm x 0.2mm extrusion at 20mm/s XY movement: 0.45*0.2*20*60=1.8*60 = 108 mm^3/min // Slope of the volumetric rate, changing from 20mm/s to 60mm/s over 2 seconds: (5.4-1.8)*60*60/2=60*60*1.8 = 6480 mm^3/min^2 = 1.8 mm^3/s^2 m_max_volumetric_extrusion_rate_slope_positive = (this->m_config == NULL) ? 6480.f : this->m_config->max_volumetric_extrusion_rate_slope_positive.value * 60.f * 60.f; m_max_volumetric_extrusion_rate_slope_negative = (this->m_config == NULL) ? 6480.f : this->m_config->max_volumetric_extrusion_rate_slope_negative.value * 60.f * 60.f; for (size_t i = 0; i < numExtrusionRoles; ++ i) { m_max_volumetric_extrusion_rate_slopes[i].negative = m_max_volumetric_extrusion_rate_slope_negative; m_max_volumetric_extrusion_rate_slopes[i].positive = m_max_volumetric_extrusion_rate_slope_positive; } // Don't regulate the pressure in infill. m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].negative = 0; m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].positive = 0; // Don't regulate the pressure in gap fill. m_max_volumetric_extrusion_rate_slopes[erGapFill].negative = 0; m_max_volumetric_extrusion_rate_slopes[erGapFill].positive = 0; m_stat.reset(); line_idx = 0; } const char* PressureEqualizer::process(const char *szGCode, bool flush) { // Reset length of the output_buffer. output_buffer_length = 0; if (szGCode != 0) { const char *p = szGCode; while (*p != 0) { // Find end of the line. const char *endl = p; // Slic3r always generates end of lines in a Unix style. for (; *endl != 0 && *endl != '\n'; ++ endl) ; if (circular_buffer_items == circular_buffer_size) // Buffer is full. Push out the oldest line. output_gcode_line(circular_buffer[circular_buffer_pos]); else ++ circular_buffer_items; // Process a G-code line, store it into the provided GCodeLine object. size_t idx_tail = circular_buffer_pos; circular_buffer_pos = circular_buffer_idx_next(circular_buffer_pos); if (! process_line(p, endl - p, circular_buffer[idx_tail])) { // The line has to be forgotten. It contains comment marks, which shall be // filtered out of the target g-code. circular_buffer_pos = idx_tail; -- circular_buffer_items; } p = endl; if (*p == '\n') ++ p; } } if (flush) { // Flush the remaining valid lines of the circular buffer. for (size_t idx = circular_buffer_idx_head(); circular_buffer_items > 0; -- circular_buffer_items) { output_gcode_line(circular_buffer[idx]); if (++ idx == circular_buffer_size) idx = 0; } // Reset the index pointer. assert(circular_buffer_items == 0); circular_buffer_pos = 0; #if 1 printf("Statistics: \n"); printf("Minimum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_min); printf("Maximum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_max); if (m_stat.extrusion_length > 0) m_stat.volumetric_extrusion_rate_avg /= m_stat.extrusion_length; printf("Average volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_avg); m_stat.reset(); #endif } return output_buffer.data(); } // Is a white space? static inline bool is_ws(const char c) { return c == ' ' || c == '\t'; } // Is it an end of line? Consider a comment to be an end of line as well. static inline bool is_eol(const char c) { return c == 0 || c == '\r' || c == '\n' || c == ';'; }; // Is it a white space or end of line? static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); }; // Eat whitespaces. static void eatws(const char *&line) { while (is_ws(*line)) ++ line; } // Parse an int starting at the current position of a line. // If succeeded, the line pointer is advanced. static inline int parse_int(const char *&line) { char *endptr = NULL; long result = strtol(line, &endptr, 10); if (endptr == NULL || !is_ws_or_eol(*endptr)) throw std::runtime_error("PressureEqualizer: Error parsing an int"); line = endptr; return int(result); }; // Parse an int starting at the current position of a line. // If succeeded, the line pointer is advanced. static inline float parse_float(const char *&line) { char *endptr = NULL; float result = strtof(line, &endptr); if (endptr == NULL || !is_ws_or_eol(*endptr)) throw std::runtime_error("PressureEqualizer: Error parsing a float"); line = endptr; return result; }; #define EXTRUSION_ROLE_TAG ";_EXTRUSION_ROLE:" bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLine &buf) { if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) { line += strlen(EXTRUSION_ROLE_TAG); int role = atoi(line); this->m_current_extrusion_role = ExtrusionRole(role); ++ line_idx; return false; } // Set the type, copy the line to the buffer. buf.type = GCODELINETYPE_OTHER; buf.modified = false; if (buf.raw.size() < len + 1) buf.raw.assign(line, line + len + 1); else memcpy(buf.raw.data(), line, len); buf.raw[len] = 0; buf.raw_length = len; memcpy(buf.pos_start, m_current_pos, sizeof(float)*5); memcpy(buf.pos_end, m_current_pos, sizeof(float)*5); memset(buf.pos_provided, 0, 5); buf.volumetric_extrusion_rate = 0.f; buf.volumetric_extrusion_rate_start = 0.f; buf.volumetric_extrusion_rate_end = 0.f; buf.max_volumetric_extrusion_rate_slope_positive = 0.f; buf.max_volumetric_extrusion_rate_slope_negative = 0.f; buf.extrusion_role = m_current_extrusion_role; // Parse the G-code line, store the result into the buf. switch (toupper(*line ++)) { case 'G': { int gcode = parse_int(line); eatws(line); switch (gcode) { case 0: case 1: { // G0, G1: A FFF 3D printer does not make a difference between the two. float new_pos[5]; memcpy(new_pos, m_current_pos, sizeof(float)*5); bool changed[5] = { false, false, false, false, false }; while (!is_eol(*line)) { char axis = toupper(*line++); int i = -1; switch (axis) { case 'X': case 'Y': case 'Z': i = axis - 'X'; break; case 'E': i = 3; break; case 'F': i = 4; break; default: assert(false); } if (i == -1) throw std::runtime_error(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis); buf.pos_provided[i] = true; new_pos[i] = parse_float(line); if (i == 3 && m_config->use_relative_e_distances.value) new_pos[i] += m_current_pos[i]; changed[i] = new_pos[i] != m_current_pos[i]; eatws(line); } if (changed[3]) { // Extrusion, retract or unretract. float diff = new_pos[3] - m_current_pos[3]; if (diff < 0) { buf.type = GCODELINETYPE_RETRACT; m_retracted = true; } else if (! changed[0] && ! changed[1] && ! changed[2]) { // assert(m_retracted); buf.type = GCODELINETYPE_UNRETRACT; m_retracted = false; } else { assert(changed[0] || changed[1]); // Moving in XY plane. buf.type = GCODELINETYPE_EXTRUDE; // Calculate the volumetric extrusion rate. float diff[4]; for (size_t i = 0; i < 4; ++ i) diff[i] = new_pos[i] - m_current_pos[i]; // volumetric extrusion rate = A_filament * F_xyz * L_e / L_xyz [mm^3/min] float len2 = diff[0]*diff[0]+diff[1]*diff[1]+diff[2]*diff[2]; float rate = m_filament_crossections[m_current_extruder] * new_pos[4] * sqrt((diff[3]*diff[3])/len2); buf.volumetric_extrusion_rate = rate; buf.volumetric_extrusion_rate_start = rate; buf.volumetric_extrusion_rate_end = rate; m_stat.update(rate, sqrt(len2)); if (rate < 40.f) { printf("Extremely low flow rate: %f. Line %d, Length: %f, extrusion: %f Old position: (%f, %f, %f), new position: (%f, %f, %f)\n", rate, int(line_idx), sqrt(len2), sqrt((diff[3]*diff[3])/len2), m_current_pos[0], m_current_pos[1], m_current_pos[2], new_pos[0], new_pos[1], new_pos[2]); } } } else if (changed[0] || changed[1] || changed[2]) { // Moving without extrusion. buf.type = GCODELINETYPE_MOVE; } memcpy(m_current_pos, new_pos, sizeof(float) * 5); break; } case 92: { // G92 : Set Position // Set a logical coordinate position to a new value without actually moving the machine motors. // Which axes to set? bool set = false; while (!is_eol(*line)) { char axis = toupper(*line++); switch (axis) { case 'X': case 'Y': case 'Z': m_current_pos[axis - 'X'] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f; set = true; break; case 'E': m_current_pos[3] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f; set = true; break; default: throw std::runtime_error(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis); } eatws(line); } assert(set); break; } case 10: case 22: // Firmware retract. buf.type = GCODELINETYPE_RETRACT; m_retracted = true; break; case 11: case 23: // Firmware unretract. buf.type = GCODELINETYPE_UNRETRACT; m_retracted = false; break; default: // Ignore the rest. break; } break; } case 'M': { int mcode = parse_int(line); eatws(line); switch (mcode) { default: // Ignore the rest of the M-codes. break; } break; } case 'T': { // Activate an extruder head. int new_extruder = parse_int(line); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; m_retracted = true; buf.type = GCODELINETYPE_TOOL_CHANGE; } else { buf.type = GCODELINETYPE_NOOP; } break; } } buf.extruder_id = m_current_extruder; memcpy(buf.pos_end, m_current_pos, sizeof(float)*5); adjust_volumetric_rate(); ++ line_idx; return true; } void PressureEqualizer::output_gcode_line(GCodeLine &line) { if (! line.modified) { push_to_output(line.raw.data(), line.raw_length, true); return; } // The line was modified. // Find the comment. const char *comment = line.raw.data(); while (*comment != ';' && *comment != 0) ++comment; if (*comment != ';') comment = NULL; // Emit the line with lowered extrusion rates. float l2 = line.dist_xyz2(); float l = sqrt(l2); size_t nSegments = size_t(ceil(l / m_max_segment_length)); if (nSegments == 1) { // Just update this segment. push_line_to_output(line, line.feedrate() * line.volumetric_correction_avg(), comment); } else { bool accelerating = line.volumetric_extrusion_rate_start < line.volumetric_extrusion_rate_end; // Update the initial and final feed rate values. line.pos_start[4] = line.volumetric_extrusion_rate_start * line.pos_end[4] / line.volumetric_extrusion_rate; line.pos_end [4] = line.volumetric_extrusion_rate_end * line.pos_end[4] / line.volumetric_extrusion_rate; float feed_avg = 0.5f * (line.pos_start[4] + line.pos_end[4]); // Limiting volumetric extrusion rate slope for this segment. float max_volumetric_extrusion_rate_slope = accelerating ? line.max_volumetric_extrusion_rate_slope_positive : line.max_volumetric_extrusion_rate_slope_negative; // Total time for the segment, corrected for the possibly lowered volumetric feed rate, // if accelerating / decelerating over the complete segment. float t_total = line.dist_xyz() / feed_avg; // Time of the acceleration / deceleration part of the segment, if accelerating / decelerating // with the maximum volumetric extrusion rate slope. float t_acc = 0.5f * (line.volumetric_extrusion_rate_start + line.volumetric_extrusion_rate_end) / max_volumetric_extrusion_rate_slope; float l_acc = l; float l_steady = 0.f; if (t_acc < t_total) { // One may achieve higher print speeds if part of the segment is not speed limited. float l_acc = t_acc * feed_avg; float l_steady = l - l_acc; if (l_steady < 0.5f * m_max_segment_length) { l_acc = l; l_steady = 0.f; } else nSegments = size_t(ceil(l_acc / m_max_segment_length)); } float pos_start[5]; float pos_end [5]; float pos_end2 [4]; memcpy(pos_start, line.pos_start, sizeof(float)*5); memcpy(pos_end , line.pos_end , sizeof(float)*5); if (l_steady > 0.f) { // There will be a steady feed segment emitted. if (accelerating) { // Prepare the final steady feed rate segment. memcpy(pos_end2, pos_end, sizeof(float)*4); float t = l_acc / l; for (int i = 0; i < 4; ++ i) { pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t; line.pos_provided[i] = true; } } else { // Emit the steady feed rate segment. float t = l_steady / l; for (int i = 0; i < 4; ++ i) { line.pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t; line.pos_provided[i] = true; } push_line_to_output(line, pos_start[4], comment); comment = NULL; memcpy(line.pos_start, line.pos_end, sizeof(float)*5); memcpy(pos_start, line.pos_end, sizeof(float)*5); } } // Split the segment into pieces. for (size_t i = 1; i < nSegments; ++ i) { float t = float(i) / float(nSegments); for (size_t j = 0; j < 4; ++ j) { line.pos_end[j] = pos_start[j] + (pos_end[j] - pos_start[j]) * t; line.pos_provided[j] = true; } // Interpolate the feed rate at the center of the segment. push_line_to_output(line, pos_start[4] + (pos_end[4] - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), comment); comment = NULL; memcpy(line.pos_start, line.pos_end, sizeof(float)*5); } if (l_steady > 0.f && accelerating) { for (int i = 0; i < 4; ++ i) { line.pos_end[i] = pos_end2[i]; line.pos_provided[i] = true; } push_line_to_output(line, pos_end[4], comment); } } } void PressureEqualizer::adjust_volumetric_rate() { if (circular_buffer_items < 2) return; // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes. const size_t idx_head = circular_buffer_idx_head(); const size_t idx_tail = circular_buffer_idx_prev(circular_buffer_idx_tail()); size_t idx = idx_tail; if (idx == idx_head || ! circular_buffer[idx].extruding()) // Nothing to do, the last move is not extruding. return; float feedrate_per_extrusion_role[numExtrusionRoles]; for (size_t i = 0; i < numExtrusionRoles; ++ i) feedrate_per_extrusion_role[i] = FLT_MAX; feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_start; bool modified = true; while (modified && idx != idx_head) { size_t idx_prev = circular_buffer_idx_prev(idx); for (; ! circular_buffer[idx_prev].extruding() && idx_prev != idx_head; idx_prev = circular_buffer_idx_prev(idx_prev)) ; if (! circular_buffer[idx_prev].extruding()) break; // Volumetric extrusion rate at the start of the succeding segment. float rate_succ = circular_buffer[idx].volumetric_extrusion_rate_start; // What is the gradient of the extrusion rate between idx_prev and idx? idx = idx_prev; GCodeLine &line = circular_buffer[idx]; for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) { float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].negative; if (rate_slope == 0) // The negative rate is unlimited. continue; float rate_end = feedrate_per_extrusion_role[iRole]; if (iRole == line.extrusion_role && rate_succ < rate_end) // Limit by the succeeding volumetric flow rate. rate_end = rate_succ; if (line.volumetric_extrusion_rate_end > rate_end) { line.volumetric_extrusion_rate_end = rate_end; line.modified = true; } else if (iRole == line.extrusion_role) { rate_end = line.volumetric_extrusion_rate_end; } else if (rate_end == FLT_MAX) { // The rate for ExtrusionRole iRole is unlimited. continue; } else { // Use the original, 'floating' extrusion rate as a starting point for the limiter. } // modified = false; float rate_start = rate_end + rate_slope * line.time_corrected(); if (rate_start < line.volumetric_extrusion_rate_start) { // Limit the volumetric extrusion rate at the start of this segment due to a segment // of ExtrusionType iRole, which will be extruded in the future. line.volumetric_extrusion_rate_start = rate_start; line.max_volumetric_extrusion_rate_slope_negative = rate_slope; line.modified = true; // modified = true; } feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start; } } // Go forward and adjust the feedrate to decrease the slope of the extrusion rate changes. for (size_t i = 0; i < numExtrusionRoles; ++ i) feedrate_per_extrusion_role[i] = FLT_MAX; feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_end; assert(circular_buffer[idx].extruding()); while (idx != idx_tail) { size_t idx_next = circular_buffer_idx_next(idx); for (; ! circular_buffer[idx_next].extruding() && idx_next != idx_tail; idx_next = circular_buffer_idx_next(idx_next)) ; if (! circular_buffer[idx_next].extruding()) break; float rate_prec = circular_buffer[idx].volumetric_extrusion_rate_end; // What is the gradient of the extrusion rate between idx_prev and idx? idx = idx_next; GCodeLine &line = circular_buffer[idx]; for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) { float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].positive; if (rate_slope == 0) // The positive rate is unlimited. continue; float rate_start = feedrate_per_extrusion_role[iRole]; if (iRole == line.extrusion_role && rate_prec < rate_start) rate_start = rate_prec; if (line.volumetric_extrusion_rate_start > rate_start) { line.volumetric_extrusion_rate_start = rate_start; line.modified = true; } else if (iRole == line.extrusion_role) { rate_start = line.volumetric_extrusion_rate_start; } else if (rate_start == FLT_MAX) { // The rate for ExtrusionRole iRole is unlimited. continue; } else { // Use the original, 'floating' extrusion rate as a starting point for the limiter. } float rate_end = (rate_slope == 0) ? FLT_MAX : rate_start + rate_slope * line.time_corrected(); if (rate_end < line.volumetric_extrusion_rate_end) { // Limit the volumetric extrusion rate at the start of this segment due to a segment // of ExtrusionType iRole, which was extruded before. line.volumetric_extrusion_rate_end = rate_end; line.max_volumetric_extrusion_rate_slope_positive = rate_slope; line.modified = true; } feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end; } } } void PressureEqualizer::push_axis_to_output(const char axis, const float value, bool add_eol) { char buf[2048]; int len = sprintf(buf, (axis == 'E') ? " %c%.3f" : " %c%.5f", axis, value); push_to_output(buf, len, add_eol); } void PressureEqualizer::push_to_output(const char *text, const size_t len, bool add_eol) { // New length of the output buffer content. size_t len_new = output_buffer_length + len + 1; if (add_eol) ++ len_new; // Resize the output buffer to a power of 2 higher than the required memory. if (output_buffer.size() < len_new) { size_t v = len_new; // Compute the next highest power of 2 of 32-bit v // http://graphics.stanford.edu/~seander/bithacks.html v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; output_buffer.resize(v); } // Copy the text to the output. if (len != 0) { memcpy(output_buffer.data() + output_buffer_length, text, len); output_buffer_length += len; } if (add_eol) output_buffer[output_buffer_length ++] = '\n'; output_buffer[output_buffer_length] = 0; } void PressureEqualizer::push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment) { push_to_output("G1", 2, false); for (char i = 0; i < 3; ++ i) if (line.pos_provided[i]) push_axis_to_output('X'+i, line.pos_end[i]); push_axis_to_output('E', m_config->use_relative_e_distances.value ? (line.pos_end[3] - line.pos_start[3]) : line.pos_end[3]); // if (line.pos_provided[4] || fabs(line.feedrate() - new_feedrate) > 1e-5) push_axis_to_output('F', new_feedrate); // output comment and EOL push_to_output(comment, (comment == NULL) ? 0 : strlen(comment), true); } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/GCode/PressureEqualizer.hpp000066400000000000000000000220741324354444700250230ustar00rootroot00000000000000#ifndef slic3r_GCode_PressureEqualizer_hpp_ #define slic3r_GCode_PressureEqualizer_hpp_ #include "../libslic3r.h" #include "../PrintConfig.hpp" #include "../ExtrusionEntity.hpp" namespace Slic3r { // Processes a G-code. Finds changes in the volumetric extrusion speed and adjusts the transitions // between these paths to limit fast changes in the volumetric extrusion speed. class PressureEqualizer { public: PressureEqualizer(const Slic3r::GCodeConfig *config); ~PressureEqualizer(); void reset(); // Process a next batch of G-code lines. Flush the internal buffers if asked for. const char* process(const char *szGCode, bool flush); size_t get_output_buffer_length() const { return output_buffer_length; } private: struct Statistics { void reset() { volumetric_extrusion_rate_min = std::numeric_limits::max(); volumetric_extrusion_rate_max = 0.f; volumetric_extrusion_rate_avg = 0.f; extrusion_length = 0.f; } void update(float volumetric_extrusion_rate, float length) { volumetric_extrusion_rate_min = std::min(volumetric_extrusion_rate_min, volumetric_extrusion_rate); volumetric_extrusion_rate_max = std::max(volumetric_extrusion_rate_max, volumetric_extrusion_rate); volumetric_extrusion_rate_avg += volumetric_extrusion_rate * length; extrusion_length += length; } float volumetric_extrusion_rate_min; float volumetric_extrusion_rate_max; float volumetric_extrusion_rate_avg; float extrusion_length; }; struct Statistics m_stat; // Keeps the reference, does not own the config. const Slic3r::GCodeConfig *m_config; // Private configuration values // How fast could the volumetric extrusion rate increase / decrase? mm^3/sec^2 struct ExtrusionRateSlope { float positive; float negative; }; enum { numExtrusionRoles = erSupportMaterialInterface + 1 }; ExtrusionRateSlope m_max_volumetric_extrusion_rate_slopes[numExtrusionRoles]; float m_max_volumetric_extrusion_rate_slope_positive; float m_max_volumetric_extrusion_rate_slope_negative; // Maximum segment length to split a long segment, if the initial and the final flow rate differ. float m_max_segment_length; // Configuration extracted from config. // Area of the crossestion of each filament. Necessary to calculate the volumetric flow rate. std::vector m_filament_crossections; // Internal data. // X,Y,Z,E,F float m_current_pos[5]; size_t m_current_extruder; ExtrusionRole m_current_extrusion_role; bool m_retracted; enum GCodeLineType { GCODELINETYPE_INVALID, GCODELINETYPE_NOOP, GCODELINETYPE_OTHER, GCODELINETYPE_RETRACT, GCODELINETYPE_UNRETRACT, GCODELINETYPE_TOOL_CHANGE, GCODELINETYPE_MOVE, GCODELINETYPE_EXTRUDE, }; struct GCodeLine { GCodeLine() : type(GCODELINETYPE_INVALID), raw_length(0), modified(false), extruder_id(0), volumetric_extrusion_rate(0.f), volumetric_extrusion_rate_start(0.f), volumetric_extrusion_rate_end(0.f) {} bool moving_xy() const { return fabs(pos_end[0] - pos_start[0]) > 0.f || fabs(pos_end[1] - pos_start[1]) > 0.f; } bool moving_z () const { return fabs(pos_end[2] - pos_start[2]) > 0.f; } bool extruding() const { return moving_xy() && pos_end[3] > pos_start[3]; } bool retracting() const { return pos_end[3] < pos_start[3]; } bool deretracting() const { return ! moving_xy() && pos_end[3] > pos_start[3]; } float dist_xy2() const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]); } float dist_xyz2() const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]) + (pos_end[2] - pos_start[2]) * (pos_end[2] - pos_start[2]); } float dist_xy() const { return sqrt(dist_xy2()); } float dist_xyz() const { return sqrt(dist_xyz2()); } float dist_e() const { return fabs(pos_end[3] - pos_start[3]); } float feedrate() const { return pos_end[4]; } float time() const { return dist_xyz() / feedrate(); } float time_inv() const { return feedrate() / dist_xyz(); } float volumetric_correction_avg() const { float avg_correction = 0.5f * (volumetric_extrusion_rate_start + volumetric_extrusion_rate_end) / volumetric_extrusion_rate; assert(avg_correction > 0.f); assert(avg_correction <= 1.00000001f); return avg_correction; } float time_corrected() const { return time() * volumetric_correction_avg(); } GCodeLineType type; // We try to keep the string buffer once it has been allocated, so it will not be reallocated over and over. std::vector raw; size_t raw_length; // If modified, the raw text has to be adapted by the new extrusion rate, // or maybe the line needs to be split into multiple lines. bool modified; // float timeStart; // float timeEnd; // X,Y,Z,E,F. Storing the state of the currently active extruder only. float pos_start[5]; float pos_end[5]; // Was the axis found on the G-code line? X,Y,Z,F bool pos_provided[5]; // Index of the active extruder. size_t extruder_id; // Extrusion role of this segment. ExtrusionRole extrusion_role; // Current volumetric extrusion rate. float volumetric_extrusion_rate; // Volumetric extrusion rate at the start of this segment. float volumetric_extrusion_rate_start; // Volumetric extrusion rate at the end of this segment. float volumetric_extrusion_rate_end; // Volumetric extrusion rate slope limiting this segment. // If set to zero, the slope is unlimited. float max_volumetric_extrusion_rate_slope_positive; float max_volumetric_extrusion_rate_slope_negative; }; // Circular buffer of GCode lines. The circular buffer size will be limited to circular_buffer_size. std::vector circular_buffer; // Current position of the circular buffer (index, where to write the next line to, the line has to be pushed out before it is overwritten). size_t circular_buffer_pos; // Circular buffer size, configuration value. size_t circular_buffer_size; // Number of valid lines in the circular buffer. Lower or equal to circular_buffer_size. size_t circular_buffer_items; // Output buffer will only grow. It will not be reallocated over and over. std::vector output_buffer; size_t output_buffer_length; // For debugging purposes. Index of the G-code line processed. size_t line_idx; bool process_line(const char *line, const size_t len, GCodeLine &buf); void output_gcode_line(GCodeLine &buf); // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes. // Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes. void adjust_volumetric_rate(); // Push the text to the end of the output_buffer. void push_to_output(const char *text, const size_t len, bool add_eol = true); // Push an axis assignment to the end of the output buffer. void push_axis_to_output(const char axis, const float value, bool add_eol = false); // Push a G-code line to the output, void push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment); size_t circular_buffer_idx_head() const { size_t idx = circular_buffer_pos + circular_buffer_size - circular_buffer_items; if (idx >= circular_buffer_size) idx -= circular_buffer_size; return idx; } size_t circular_buffer_idx_tail() const { return circular_buffer_pos; } size_t circular_buffer_idx_prev(size_t idx) const { idx += circular_buffer_size - 1; if (idx >= circular_buffer_size) idx -= circular_buffer_size; return idx; } size_t circular_buffer_idx_next(size_t idx) const { if (++ idx >= circular_buffer_size) idx -= circular_buffer_size; return idx; } }; } // namespace Slic3r #endif /* slic3r_GCode_PressureEqualizer_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/GCode/PrintExtents.cpp000066400000000000000000000166621324354444700240010ustar00rootroot00000000000000// Calculate extents of the extrusions assigned to Print / PrintObject. // The extents are used for assessing collisions of the print with the priming towers, // to decide whether to pause the print after the priming towers are extruded // to let the operator remove them from the print bed. #include "../BoundingBox.hpp" #include "../ExtrusionEntity.hpp" #include "../ExtrusionEntityCollection.hpp" #include "../Print.hpp" #include "PrintExtents.hpp" #include "WipeTower.hpp" namespace Slic3r { static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, const coord_t radius) { BoundingBox bbox; if (! polyline.points.empty()) bbox.merge(polyline.points.front()); for (const Point &pt : polyline.points) { bbox.min.x = std::min(bbox.min.x, pt.x - radius); bbox.min.y = std::min(bbox.min.y, pt.y - radius); bbox.max.x = std::max(bbox.max.x, pt.x + radius); bbox.max.y = std::max(bbox.max.y, pt.y + radius); } return bbox; } static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path) { BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)); BoundingBoxf bboxf; if (! empty(bbox)) { bboxf.min = Pointf::new_unscale(bbox.min); bboxf.max = Pointf::new_unscale(bbox.max); bboxf.defined = true; } return bboxf; } static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusion_loop) { BoundingBox bbox; for (const ExtrusionPath &extrusion_path : extrusion_loop.paths) bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width))); BoundingBoxf bboxf; if (! empty(bbox)) { bboxf.min = Pointf::new_unscale(bbox.min); bboxf.max = Pointf::new_unscale(bbox.max); bboxf.defined = true; } return bboxf; } static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &extrusion_multi_path) { BoundingBox bbox; for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths) bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width))); BoundingBoxf bboxf; if (! empty(bbox)) { bboxf.min = Pointf::new_unscale(bbox.min); bboxf.max = Pointf::new_unscale(bbox.max); bboxf.defined = true; } return bboxf; } static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_entity); static inline BoundingBoxf extrusionentity_extents(const ExtrusionEntityCollection &extrusion_entity_collection) { BoundingBoxf bbox; for (const ExtrusionEntity *extrusion_entity : extrusion_entity_collection.entities) bbox.merge(extrusionentity_extents(extrusion_entity)); return bbox; } static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_entity) { if (extrusion_entity == nullptr) return BoundingBoxf(); auto *extrusion_path = dynamic_cast(extrusion_entity); if (extrusion_path != nullptr) return extrusionentity_extents(*extrusion_path); auto *extrusion_loop = dynamic_cast(extrusion_entity); if (extrusion_loop != nullptr) return extrusionentity_extents(*extrusion_loop); auto *extrusion_multi_path = dynamic_cast(extrusion_entity); if (extrusion_multi_path != nullptr) return extrusionentity_extents(*extrusion_multi_path); auto *extrusion_entity_collection = dynamic_cast(extrusion_entity); if (extrusion_entity_collection != nullptr) return extrusionentity_extents(*extrusion_entity_collection); CONFESS("Unexpected extrusion_entity type in extrusionentity_extents()"); return BoundingBoxf(); } BoundingBoxf get_print_extrusions_extents(const Print &print) { BoundingBoxf bbox(extrusionentity_extents(print.brim)); bbox.merge(extrusionentity_extents(print.skirt)); return bbox; } BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object, const coordf_t max_print_z) { BoundingBoxf bbox; for (const Layer *layer : print_object.layers) { if (layer->print_z > max_print_z) break; BoundingBoxf bbox_this; for (const LayerRegion *layerm : layer->regions) { bbox_this.merge(extrusionentity_extents(layerm->perimeters)); for (const ExtrusionEntity *ee : layerm->fills.entities) // fill represents infill extrusions of a single island. bbox_this.merge(extrusionentity_extents(*dynamic_cast(ee))); } const SupportLayer *support_layer = dynamic_cast(layer); if (support_layer) for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) bbox_this.merge(extrusionentity_extents(extrusion_entity)); for (const Point &offset : print_object._shifted_copies) { BoundingBoxf bbox_translated(bbox_this); bbox_translated.translate(Pointf::new_unscale(offset)); bbox.merge(bbox_translated); } } return bbox; } // Returns a bounding box of a projection of the wipe tower for the layers <= max_print_z. // The projection does not contain the priming regions. BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z) { BoundingBoxf bbox; for (const std::vector &tool_changes : print.m_wipe_tower_tool_changes) { if (! tool_changes.empty() && tool_changes.front().print_z > max_print_z) break; for (const WipeTower::ToolChangeResult &tcr : tool_changes) { for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { const WipeTower::Extrusion &e = tcr.extrusions[i]; if (e.width > 0) { Pointf p1((&e - 1)->pos.x, (&e - 1)->pos.y); Pointf p2(e.pos.x, e.pos.y); bbox.merge(p1); coordf_t radius = 0.5 * e.width; bbox.min.x = std::min(bbox.min.x, std::min(p1.x, p2.x) - radius); bbox.min.y = std::min(bbox.min.y, std::min(p1.y, p2.y) - radius); bbox.max.x = std::max(bbox.max.x, std::max(p1.x, p2.x) + radius); bbox.max.y = std::max(bbox.max.y, std::max(p1.y, p2.y) + radius); } } } } return bbox; } // Returns a bounding box of the wipe tower priming extrusions. BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print) { BoundingBoxf bbox; if (print.m_wipe_tower_priming) { const WipeTower::ToolChangeResult &tcr = *print.m_wipe_tower_priming.get(); for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { const WipeTower::Extrusion &e = tcr.extrusions[i]; if (e.width > 0) { Pointf p1((&e - 1)->pos.x, (&e - 1)->pos.y); Pointf p2(e.pos.x, e.pos.y); bbox.merge(p1); coordf_t radius = 0.5 * e.width; bbox.min.x = std::min(bbox.min.x, std::min(p1.x, p2.x) - radius); bbox.min.y = std::min(bbox.min.y, std::min(p1.y, p2.y) - radius); bbox.max.x = std::max(bbox.max.x, std::max(p1.x, p2.x) + radius); bbox.max.y = std::max(bbox.max.y, std::max(p1.y, p2.y) + radius); } } } return bbox; } } Slic3r-version_1.39.1/xs/src/libslic3r/GCode/PrintExtents.hpp000066400000000000000000000017561324354444700240040ustar00rootroot00000000000000// Measure extents of the planned extrusions. // To be used for collision reporting. #ifndef slic3r_PrintExtents_hpp_ #define slic3r_PrintExtents_hpp_ #include "libslic3r.h" namespace Slic3r { class Print; class PrintObject; class BoundingBoxf; // Returns a bounding box of a projection of the brim and skirt. BoundingBoxf get_print_extrusions_extents(const Print &print); // Returns a bounding box of a projection of the object extrusions at z <= max_print_z. BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object, const coordf_t max_print_z); // Returns a bounding box of a projection of the wipe tower for the layers <= max_print_z. // The projection does not contain the priming regions. BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z); // Returns a bounding box of the wipe tower priming extrusions. BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print); }; #endif /* slic3r_PrintExtents_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/GCode/SpiralVase.cpp000066400000000000000000000063711324354444700233770ustar00rootroot00000000000000#include "SpiralVase.hpp" #include "GCode.hpp" #include namespace Slic3r { std::string SpiralVase::process_layer(const std::string &gcode) { /* This post-processor relies on several assumptions: - all layers are processed through it, including those that are not supposed to be transformed, in order to update the reader with the XY positions - each call to this method includes a full layer, with a single Z move at the beginning - each layer is composed by suitable geometry (i.e. a single complete loop) - loops were not clipped before calling this method */ // If we're not going to modify G-code, just feed it to the reader // in order to update positions. if (!this->enable) { this->_reader.parse_buffer(gcode); return gcode; } // Get total XY length for this layer by summing all extrusion moves. float total_layer_length = 0; float layer_height = 0; float z; bool set_z = false; { //FIXME Performance warning: This copies the GCodeConfig of the reader. GCodeReader r = this->_reader; // clone r.parse_buffer(gcode, [&total_layer_length, &layer_height, &z, &set_z] (GCodeReader &reader, const GCodeReader::GCodeLine &line) { if (line.cmd_is("G1")) { if (line.extruding(reader)) { total_layer_length += line.dist_XY(reader); } else if (line.has(Z)) { layer_height += line.dist_Z(reader); if (!set_z) { z = line.new_Z(reader); set_z = true; } } } }); } // Remove layer height from initial Z. z -= layer_height; std::string new_gcode; this->_reader.parse_buffer(gcode, [&new_gcode, &z, &layer_height, &total_layer_length] (GCodeReader &reader, GCodeReader::GCodeLine line) { if (line.cmd_is("G1")) { if (line.has_z()) { // If this is the initial Z move of the layer, replace it with a // (redundant) move to the last Z of previous layer. line.set(reader, Z, z); new_gcode += line.raw() + '\n'; return; } else { float dist_XY = line.dist_XY(reader); if (dist_XY > 0) { // horizontal move if (line.extruding(reader)) { z += dist_XY * layer_height / total_layer_length; line.set(reader, Z, z); new_gcode += line.raw() + '\n'; } return; /* Skip travel moves: the move to first perimeter point will cause a visible seam when loops are not aligned in XY; by skipping it we blend the first loop move in the XY plane (although the smoothness of such blend depend on how long the first segment is; maybe we should enforce some minimum length?). */ } } } new_gcode += line.raw() + '\n'; }); return new_gcode; } } Slic3r-version_1.39.1/xs/src/libslic3r/GCode/SpiralVase.hpp000066400000000000000000000010271324354444700233750ustar00rootroot00000000000000#ifndef slic3r_SpiralVase_hpp_ #define slic3r_SpiralVase_hpp_ #include "libslic3r.h" #include "GCodeReader.hpp" namespace Slic3r { class SpiralVase { public: bool enable; SpiralVase(const PrintConfig &config) : enable(false), _config(&config) { this->_reader.z() = this->_config->z_offset; this->_reader.apply_config(*this->_config); }; std::string process_layer(const std::string &gcode); private: const PrintConfig* _config; GCodeReader _reader; }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/GCode/ToolOrdering.cpp000066400000000000000000000333441324354444700237350ustar00rootroot00000000000000#include "Print.hpp" #include "ToolOrdering.hpp" // #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG #ifdef SLIC3R_DEBUG #define DEBUG #define _DEBUG #undef NDEBUG #endif #include #include namespace Slic3r { // For the use case when each object is printed separately // (print.config.complete_objects is true). ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material) { if (object.layers.empty()) return; // Initialize the print layers for just a single object. { std::vector zs; zs.reserve(zs.size() + object.layers.size() + object.support_layers.size()); for (auto layer : object.layers) zs.emplace_back(layer->print_z); for (auto layer : object.support_layers) zs.emplace_back(layer->print_z); this->initialize_layers(zs); } // Collect extruders reuqired to print the layers. this->collect_extruders(object); // Reorder the extruders to minimize tool switches. this->reorder_extruders(first_extruder); this->fill_wipe_tower_partitions(object.print()->config, object.layers.front()->print_z - object.layers.front()->height); this->collect_extruder_statistics(prime_multi_material); } // For the use case when all objects are printed at once. // (print.config.complete_objects is false). ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool prime_multi_material) { // Initialize the print layers for all objects and all layers. coordf_t object_bottom_z = 0.; { std::vector zs; for (auto object : print.objects) { zs.reserve(zs.size() + object->layers.size() + object->support_layers.size()); for (auto layer : object->layers) zs.emplace_back(layer->print_z); for (auto layer : object->support_layers) zs.emplace_back(layer->print_z); if (! object->layers.empty()) object_bottom_z = object->layers.front()->print_z - object->layers.front()->height; } this->initialize_layers(zs); } // Collect extruders reuqired to print the layers. for (auto object : print.objects) this->collect_extruders(*object); // Reorder the extruders to minimize tool switches. this->reorder_extruders(first_extruder); this->fill_wipe_tower_partitions(print.config, object_bottom_z); this->collect_extruder_statistics(prime_multi_material); } ToolOrdering::LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) { auto it_layer_tools = std::lower_bound(m_layer_tools.begin(), m_layer_tools.end(), ToolOrdering::LayerTools(print_z - EPSILON)); assert(it_layer_tools != m_layer_tools.end()); coordf_t dist_min = std::abs(it_layer_tools->print_z - print_z); for (++ it_layer_tools; it_layer_tools != m_layer_tools.end(); ++it_layer_tools) { coordf_t d = std::abs(it_layer_tools->print_z - print_z); if (d >= dist_min) break; dist_min = d; } -- it_layer_tools; assert(dist_min < EPSILON); return *it_layer_tools; } void ToolOrdering::initialize_layers(std::vector &zs) { sort_remove_duplicates(zs); // Merge numerically very close Z values. for (size_t i = 0; i < zs.size();) { // Find the last layer with roughly the same print_z. size_t j = i + 1; coordf_t zmax = zs[i] + EPSILON; for (; j < zs.size() && zs[j] <= zmax; ++ j) ; // Assign an average print_z to the set of layers with nearly equal print_z. m_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1]))); i = j; } } // Collect extruders reuqired to print layers. void ToolOrdering::collect_extruders(const PrintObject &object) { // Collect the support extruders. for (auto support_layer : object.support_layers) { LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z); ExtrusionRole role = support_layer->support_fills.role(); bool has_support = role == erMixed || role == erSupportMaterial; bool has_interface = role == erMixed || role == erSupportMaterialInterface; unsigned int extruder_support = object.config.support_material_extruder.value; unsigned int extruder_interface = object.config.support_material_interface_extruder.value; if (has_support) layer_tools.extruders.push_back(extruder_support); if (has_interface) layer_tools.extruders.push_back(extruder_interface); if (has_support || has_interface) layer_tools.has_support = true; } // Collect the object extruders. for (auto layer : object.layers) { LayerTools &layer_tools = this->tools_for_layer(layer->print_z); // What extruders are required to print this object layer? for (size_t region_id = 0; region_id < object.print()->regions.size(); ++ region_id) { const LayerRegion *layerm = (region_id < layer->regions.size()) ? layer->regions[region_id] : nullptr; if (layerm == nullptr) continue; const PrintRegion ®ion = *object.print()->regions[region_id]; if (! layerm->perimeters.entities.empty()) { layer_tools.extruders.push_back(region.config.perimeter_extruder.value); layer_tools.has_object = true; } bool has_infill = false; bool has_solid_infill = false; for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role(); if (is_solid_infill(role)) has_solid_infill = true; else if (role != erNone) has_infill = true; } if (has_solid_infill) layer_tools.extruders.push_back(region.config.solid_infill_extruder); if (has_infill) layer_tools.extruders.push_back(region.config.infill_extruder); if (has_solid_infill || has_infill) layer_tools.has_object = true; } } // Sort and remove duplicates for (LayerTools < : m_layer_tools) sort_remove_duplicates(lt.extruders); } // Reorder extruders to minimize layer changes. void ToolOrdering::reorder_extruders(unsigned int last_extruder_id) { if (m_layer_tools.empty()) return; if (last_extruder_id == (unsigned int)-1) { // The initial print extruder has not been decided yet. // Initialize the last_extruder_id with the first non-zero extruder id used for the print. last_extruder_id = 0; for (size_t i = 0; i < m_layer_tools.size() && last_extruder_id == 0; ++ i) { const LayerTools < = m_layer_tools[i]; for (unsigned int extruder_id : lt.extruders) if (extruder_id > 0) { last_extruder_id = extruder_id; break; } } if (last_extruder_id == 0) // Nothing to extrude. return; } else // 1 based index ++ last_extruder_id; for (LayerTools < : m_layer_tools) { if (lt.extruders.empty()) continue; if (lt.extruders.size() == 1 && lt.extruders.front() == 0) lt.extruders.front() = last_extruder_id; else { if (lt.extruders.front() == 0) // Pop the "don't care" extruder, the "don't care" region will be merged with the next one. lt.extruders.erase(lt.extruders.begin()); // Reorder the extruders to start with the last one. for (size_t i = 1; i < lt.extruders.size(); ++ i) if (lt.extruders[i] == last_extruder_id) { // Move the last extruder to the front. memmove(lt.extruders.data() + 1, lt.extruders.data(), i * sizeof(unsigned int)); lt.extruders.front() = last_extruder_id; break; } } last_extruder_id = lt.extruders.back(); } // Reindex the extruders, so they are zero based, not 1 based. for (LayerTools < : m_layer_tools) for (unsigned int &extruder_id : lt.extruders) { assert(extruder_id > 0); -- extruder_id; } } void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z) { if (m_layer_tools.empty()) return; // Count the minimum number of tool changes per layer. size_t last_extruder = size_t(-1); for (LayerTools < : m_layer_tools) { lt.wipe_tower_partitions = lt.extruders.size(); if (! lt.extruders.empty()) { if (last_extruder == size_t(-1) || last_extruder == lt.extruders.front()) // The first extruder on this layer is equal to the current one, no need to do an initial tool change. -- lt.wipe_tower_partitions; last_extruder = lt.extruders.back(); } } // Propagate the wipe tower partitions down to support the upper partitions by the lower partitions. for (int i = int(m_layer_tools.size()) - 2; i >= 0; -- i) m_layer_tools[i].wipe_tower_partitions = std::max(m_layer_tools[i + 1].wipe_tower_partitions, m_layer_tools[i].wipe_tower_partitions); //FIXME this is a hack to get the ball rolling. for (LayerTools < : m_layer_tools) lt.has_wipe_tower = (lt.has_object && lt.wipe_tower_partitions > 0) || lt.print_z < object_bottom_z + EPSILON; // Test for a raft, insert additional wipe tower layer to fill in the raft separation gap. double max_layer_height = std::numeric_limits::max(); for (size_t i = 0; i < config.nozzle_diameter.values.size(); ++ i) { double mlh = config.max_layer_height.values[i]; if (mlh == 0.) mlh = 0.75 * config.nozzle_diameter.values[i]; max_layer_height = std::min(max_layer_height, mlh); } for (size_t i = 0; i + 1 < m_layer_tools.size(); ++ i) { const LayerTools < = m_layer_tools[i]; const LayerTools <_next = m_layer_tools[i + 1]; if (lt.print_z < object_bottom_z + EPSILON && lt_next.print_z >= object_bottom_z + EPSILON) { // lt is the last raft layer. Find the 1st object layer. size_t j = i + 1; for (; j < m_layer_tools.size() && ! m_layer_tools[j].has_wipe_tower; ++ j); if (j < m_layer_tools.size()) { const LayerTools <_object = m_layer_tools[j]; coordf_t gap = lt_object.print_z - lt.print_z; assert(gap > 0.f); if (gap > max_layer_height + EPSILON) { // Insert one additional wipe tower layer between lh.print_z and lt_object.print_z. LayerTools lt_new(0.5f * (lt.print_z + lt_object.print_z)); // Find the 1st layer above lt_new. for (j = i + 1; j < m_layer_tools.size() && m_layer_tools[j].print_z < lt_new.print_z - EPSILON; ++ j); if (std::abs(m_layer_tools[j].print_z - lt_new.print_z) < EPSILON) { m_layer_tools[j].has_wipe_tower = true; } else { LayerTools <_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new); LayerTools <_prev = m_layer_tools[j - 1]; LayerTools <_next = m_layer_tools[j + 1]; assert(! lt_prev.extruders.empty() && ! lt_next.extruders.empty()); assert(lt_prev.extruders.back() == lt_next.extruders.front()); lt_extra.has_wipe_tower = true; lt_extra.extruders.push_back(lt_next.extruders.front()); lt_extra.wipe_tower_partitions = lt_next.wipe_tower_partitions; } } } break; } } // Calculate the wipe_tower_layer_height values. coordf_t wipe_tower_print_z_last = 0.; for (LayerTools < : m_layer_tools) if (lt.has_wipe_tower) { lt.wipe_tower_layer_height = lt.print_z - wipe_tower_print_z_last; wipe_tower_print_z_last = lt.print_z; } } void ToolOrdering::collect_extruder_statistics(bool prime_multi_material) { m_first_printing_extruder = (unsigned int)-1; for (const auto < : m_layer_tools) if (! lt.extruders.empty()) { m_first_printing_extruder = lt.extruders.front(); break; } m_last_printing_extruder = (unsigned int)-1; for (auto lt_it = m_layer_tools.rbegin(); lt_it != m_layer_tools.rend(); ++ lt_it) if (! lt_it->extruders.empty()) { m_last_printing_extruder = lt_it->extruders.back(); break; } m_all_printing_extruders.clear(); for (const auto < : m_layer_tools) { append(m_all_printing_extruders, lt.extruders); sort_remove_duplicates(m_all_printing_extruders); } if (prime_multi_material && ! m_all_printing_extruders.empty()) { // Reorder m_all_printing_extruders in the sequence they will be primed, the last one will be m_first_printing_extruder. // Then set m_first_printing_extruder to the 1st extruder primed. m_all_printing_extruders.erase( std::remove_if(m_all_printing_extruders.begin(), m_all_printing_extruders.end(), [ this ](const unsigned int eid) { return eid == m_first_printing_extruder; }), m_all_printing_extruders.end()); m_all_printing_extruders.emplace_back(m_first_printing_extruder); m_first_printing_extruder = m_all_printing_extruders.front(); } } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/GCode/ToolOrdering.hpp000066400000000000000000000077541324354444700237500ustar00rootroot00000000000000// Ordering of the tools to minimize tool switches. #ifndef slic3r_ToolOrdering_hpp_ #define slic3r_ToolOrdering_hpp_ #include "libslic3r.h" namespace Slic3r { class Print; class PrintObject; class ToolOrdering { public: struct LayerTools { LayerTools(const coordf_t z) : print_z(z), has_object(false), has_support(false), has_wipe_tower(false), wipe_tower_partitions(0), wipe_tower_layer_height(0.) {} bool operator< (const LayerTools &rhs) const { return print_z < rhs.print_z; } bool operator==(const LayerTools &rhs) const { return print_z == rhs.print_z; } coordf_t print_z; bool has_object; bool has_support; // Zero based extruder IDs, ordered to minimize tool switches. std::vector extruders; // Will there be anything extruded on this layer for the wipe tower? // Due to the support layers possibly interleaving the object layers, // wipe tower will be disabled for some support only layers. bool has_wipe_tower; // Number of wipe tower partitions to support the required number of tool switches // and to support the wipe tower partitions above this one. size_t wipe_tower_partitions; coordf_t wipe_tower_layer_height; }; ToolOrdering() {} // For the use case when each object is printed separately // (print.config.complete_objects is true). ToolOrdering(const PrintObject &object, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false); // For the use case when all objects are printed at once. // (print.config.complete_objects is false). ToolOrdering(const Print &print, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false); void clear() { m_layer_tools.clear(); } // Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed. unsigned int first_extruder() const { return m_first_printing_extruder; } // Get the first extruder printing the layer_tools, returns -1 if there is no layer printed. unsigned int last_extruder() const { return m_last_printing_extruder; } // For a multi-material print, the printing extruders are ordered in the order they shall be primed. const std::vector& all_extruders() const { return m_all_printing_extruders; } // Find LayerTools with the closest print_z. LayerTools& tools_for_layer(coordf_t print_z); const LayerTools& tools_for_layer(coordf_t print_z) const { return *const_cast(&const_cast(this)->tools_for_layer(print_z)); } const LayerTools& front() const { return m_layer_tools.front(); } const LayerTools& back() const { return m_layer_tools.back(); } std::vector::const_iterator begin() const { return m_layer_tools.begin(); } std::vector::const_iterator end() const { return m_layer_tools.end(); } bool empty() const { return m_layer_tools.empty(); } const std::vector& layer_tools() const { return m_layer_tools; } bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().wipe_tower_partitions > 0; } private: void initialize_layers(std::vector &zs); void collect_extruders(const PrintObject &object); void reorder_extruders(unsigned int last_extruder_id); void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z); void collect_extruder_statistics(bool prime_multi_material); std::vector m_layer_tools; // First printing extruder, including the multi-material priming sequence. unsigned int m_first_printing_extruder = (unsigned int)-1; // Final printing extruder. unsigned int m_last_printing_extruder = (unsigned int)-1; // All extruders, which extrude some material over m_layer_tools. std::vector m_all_printing_extruders; }; } // namespace SLic3r #endif /* slic3r_ToolOrdering_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/GCode/WipeTower.hpp000066400000000000000000000120601324354444700232500ustar00rootroot00000000000000#ifndef slic3r_WipeTower_hpp_ #define slic3r_WipeTower_hpp_ #include #include #include namespace Slic3r { // A pure virtual WipeTower definition. class WipeTower { public: // Internal point class, to make the wipe tower independent from other slic3r modules. // This is important for Prusa Research as we want to build the wipe tower post-processor independently from slic3r. struct xy { xy(float x = 0.f, float y = 0.f) : x(x), y(y) {} xy operator+(const xy &rhs) const { xy out(*this); out.x += rhs.x; out.y += rhs.y; return out; } xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; } xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; } xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; } bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; } bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; } float x; float y; }; WipeTower() {} virtual ~WipeTower() {} // Return the wipe tower position. virtual const xy& position() const = 0; // Return the wipe tower width. virtual float width() const = 0; // The wipe tower is finished, there should be no more tool changes or wipe tower prints. virtual bool finished() const = 0; // Switch to a next layer. virtual void set_layer( // Print height of this layer. float print_z, // Layer height, used to calculate extrusion the rate. float layer_height, // Maximum number of tool changes on this layer or the layers below. size_t max_tool_changes, // Is this the first layer of the print? In that case print the brim first. bool is_first_layer, // Is this the last layer of the wipe tower? bool is_last_layer) = 0; enum Purpose { PURPOSE_MOVE_TO_TOWER, PURPOSE_EXTRUDE, PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE, }; // Extrusion path of the wipe tower, for 3D preview of the generated tool paths. struct Extrusion { Extrusion(const xy &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {} // End position of this extrusion. xy pos; // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. float width; // Current extruder index. unsigned int tool; }; struct ToolChangeResult { // Print heigh of this tool change. float print_z; float layer_height; // G-code section to be directly included into the output G-code. std::string gcode; // For path preview. std::vector extrusions; // Initial position, at which the wipe tower starts its action. // At this position the extruder is loaded and there is no Z-hop applied. xy start_pos; // Last point, at which the normal G-code generator of Slic3r shall continue. // At this position the extruder is loaded and there is no Z-hop applied. xy end_pos; // Time elapsed over this tool change. // This is useful not only for the print time estimation, but also for the control of layer cooling. float elapsed_time; // Sum the total length of the extrusion. float total_extrusion_length_in_plane() { float e_length = 0.f; for (size_t i = 1; i < this->extrusions.size(); ++ i) { const Extrusion &e = this->extrusions[i]; if (e.width > 0) { xy v = e.pos - (&e - 1)->pos; e_length += sqrt(v.x*v.x+v.y*v.y); } } return e_length; } }; // Returns gcode to prime the nozzles at the front edge of the print bed. virtual ToolChangeResult prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. const std::vector &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. bool last_wipe_inside_wipe_tower, // May be used by a stand alone post processor. Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; // Returns gcode for toolchange and the end position. // if new_tool == -1, just unload the current filament over the wipe tower. virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer, Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag. // Call this method only if layer_finished() is false. virtual ToolChangeResult finish_layer(Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; // Is the current layer finished? A layer is finished if either the wipe tower is finished, or // the wipe tower has been completely covered by the tool change extrusions, // or the rest of the tower has been filled by a sparse infill with the finish_layer() method. virtual bool layer_finished() const = 0; }; }; // namespace Slic3r #endif /* slic3r_WipeTower_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp000066400000000000000000001121021324354444700245060ustar00rootroot00000000000000#include "WipeTowerPrusaMM.hpp" #include #include #include #include #include #if defined(__linux) || defined(__GNUC__ ) #include #endif /* __linux */ #ifdef _MSC_VER #define strcasecmp _stricmp #endif namespace Slic3r { namespace PrusaMultiMaterial { class Writer { public: Writer() : m_current_pos(std::numeric_limits::max(), std::numeric_limits::max()), m_current_z(0.f), m_current_feedrate(0.f), m_extrusion_flow(0.f), m_layer_height(0.f), m_preview_suppressed(false), m_elapsed_time(0.f) {} Writer& set_initial_position(const WipeTower::xy &pos) { m_start_pos = pos; m_current_pos = pos; return *this; } Writer& set_initial_tool(const unsigned int tool) { m_current_tool = tool; return *this; } Writer& set_z(float z) { m_current_z = z; return *this; } Writer& set_layer_height(float layer_height) { m_layer_height = layer_height; return *this; } Writer& set_extrusion_flow(float flow) { m_extrusion_flow = flow; return *this; } // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various // filament loading and cooling moves from normal extrusion moves. Therefore the writer // is asked to suppres output of some lines, which look like extrusions. Writer& suppress_preview() { m_preview_suppressed = true; return *this; } Writer& resume_preview() { m_preview_suppressed = false; return *this; } Writer& feedrate(float f) { if (f != m_current_feedrate) m_gcode += "G1" + set_format_F(f) + "\n"; return *this; } const std::string& gcode() const { return m_gcode; } const std::vector& extrusions() const { return m_extrusions; } float x() const { return m_current_pos.x; } float y() const { return m_current_pos.y; } const WipeTower::xy& start_pos() const { return m_start_pos; } const WipeTower::xy& pos() const { return m_current_pos; } float elapsed_time() const { return m_elapsed_time; } // Extrude with an explicitely provided amount of extrusion. Writer& extrude_explicit(float x, float y, float e, float f = 0.f) { if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate)) // Neither extrusion nor a travel move. return *this; float dx = x - m_current_pos.x; float dy = y - m_current_pos.y; double len = sqrt(dx*dx+dy*dy); if (! m_preview_suppressed && e > 0.f && len > 0.) { // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. float width = float(double(e) * m_filament_area / (len * m_layer_height)); // Correct for the roundings of a squished extrusion. width += float(m_layer_height * (1. - M_PI / 4.)); if (m_extrusions.empty() || m_extrusions.back().pos != m_current_pos) m_extrusions.emplace_back(WipeTower::Extrusion(m_current_pos, 0, m_current_tool)); m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(x, y), width, m_current_tool)); } m_gcode += "G1"; if (x != m_current_pos.x) m_gcode += set_format_X(x); if (y != m_current_pos.y) m_gcode += set_format_Y(y); if (e != 0.f) m_gcode += set_format_E(e); if (f != 0.f && f != m_current_feedrate) m_gcode += set_format_F(f); // Update the elapsed time with a rough estimate. m_elapsed_time += ((len == 0) ? std::abs(e) : len) / m_current_feedrate * 60.f; m_gcode += "\n"; return *this; } Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f) { return extrude_explicit(dest.x, dest.y, e, f); } // Travel to a new XY position. f=0 means use the current value. Writer& travel(float x, float y, float f = 0.f) { return extrude_explicit(x, y, 0.f, f); } Writer& travel(const WipeTower::xy &dest, float f = 0.f) { return extrude_explicit(dest.x, dest.y, 0.f, f); } // Extrude a line from current position to x, y with the extrusion amount given by m_extrusion_flow. Writer& extrude(float x, float y, float f = 0.f) { float dx = x - m_current_pos.x; float dy = y - m_current_pos.y; return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f); } Writer& extrude(const WipeTower::xy &dest, const float f = 0.f) { return extrude(dest.x, dest.y, f); } Writer& load(float e, float f = 0.f) { if (e == 0.f && (f == 0.f || f == m_current_feedrate)) return *this; m_gcode += "G1"; if (e != 0.f) m_gcode += set_format_E(e); if (f != 0.f && f != m_current_feedrate) m_gcode += set_format_F(f); m_gcode += "\n"; return *this; } // Derectract while moving in the X direction. // If |x| > 0, the feed rate relates to the x distance, // otherwise the feed rate relates to the e distance. Writer& load_move_x(float x, float e, float f = 0.f) { return extrude_explicit(x, m_current_pos.y, e, f); } Writer& retract(float e, float f = 0.f) { return load(-e, f); } // Elevate the extruder head above the current print_z position. Writer& z_hop(float hop, float f = 0.f) { m_gcode += std::string("G1") + set_format_Z(m_current_z + hop); if (f != 0 && f != m_current_feedrate) m_gcode += set_format_F(f); m_gcode += "\n"; return *this; } // Lower the extruder head back to the current print_z position. Writer& z_hop_reset(float f = 0.f) { return z_hop(0, f); } // Move to x1, +y_increment, // extrude quickly amount e to x2 with feed f. Writer& ram(float x1, float x2, float dy, float e0, float e, float f) { extrude_explicit(x1, m_current_pos.y + dy, e0, f); extrude_explicit(x2, m_current_pos.y, e); return *this; } // Let the end of the pulled out filament cool down in the cooling tube // by moving up and down and moving the print head left / right // at the current Y position to spread the leaking material. Writer& cool(float x1, float x2, float e1, float e2, float f) { extrude_explicit(x1, m_current_pos.y, e1, f); extrude_explicit(x2, m_current_pos.y, e2); return *this; } Writer& set_tool(int tool) { char buf[64]; sprintf(buf, "T%d\n", tool); m_gcode += buf; m_current_tool = tool; return *this; } // Set extruder temperature, don't wait by default. Writer& set_extruder_temp(int temperature, bool wait = false) { char buf[128]; sprintf(buf, "M%d S%d\n", wait ? 109 : 104, temperature); m_gcode += buf; return *this; }; // Set speed factor override percentage. Writer& speed_override(int speed) { char buf[128]; sprintf(buf, "M220 S%d\n", speed); m_gcode += buf; return *this; }; // Set digital trimpot motor Writer& set_extruder_trimpot(int current) { char buf[128]; sprintf(buf, "M907 E%d\n", current); m_gcode += buf; return *this; }; Writer& flush_planner_queue() { m_gcode += "G4 S0\n"; return *this; } // Reset internal extruder counter. Writer& reset_extruder() { m_gcode += "G92 E0\n"; return *this; } Writer& comment_with_value(const char *comment, int value) { char strvalue[64]; sprintf(strvalue, "%d", value); m_gcode += std::string(";") + comment + strvalue + "\n"; return *this; }; Writer& comment_material(WipeTowerPrusaMM::material_type material) { m_gcode += "; material : "; switch (material) { case WipeTowerPrusaMM::PVA: m_gcode += "#8 (PVA)"; break; case WipeTowerPrusaMM::SCAFF: m_gcode += "#5 (Scaffold)"; break; case WipeTowerPrusaMM::FLEX: m_gcode += "#4 (Flex)"; break; default: m_gcode += "DEFAULT (PLA)"; break; } m_gcode += "\n"; return *this; }; Writer& append(const char *text) { m_gcode += text; return *this; } private: WipeTower::xy m_start_pos; WipeTower::xy m_current_pos; float m_current_z; float m_current_feedrate; unsigned int m_current_tool; float m_layer_height; float m_extrusion_flow; bool m_preview_suppressed; std::string m_gcode; std::vector m_extrusions; float m_elapsed_time; const double m_filament_area = 0.25*M_PI*1.75*1.75; std::string set_format_X(float x) { char buf[64]; sprintf(buf, " X%.3f", x); m_current_pos.x = x; return buf; } std::string set_format_Y(float y) { char buf[64]; sprintf(buf, " Y%.3f", y); m_current_pos.y = y; return buf; } std::string set_format_Z(float z) { char buf[64]; sprintf(buf, " Z%.3f", z); return buf; } std::string set_format_E(float e) { char buf[64]; sprintf(buf, " E%.4f", e); return buf; } std::string set_format_F(float f) { char buf[64]; sprintf(buf, " F%d", int(floor(f + 0.5f))); m_current_feedrate = f; return buf; } Writer& operator=(const Writer &rhs); }; /* class Material { public: std::string name; std::string type; struct RammingStep { // float length; float extrusion_multiplier; // sirka linky float extrusion; float speed; }; std::vector ramming_sequence; // Number and speed of the cooling moves. std::vector cooling_moves; // Percentage of the speed overide, in pairs of std::vector> speed_override; }; */ } // namespace PrusaMultiMaterial WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *name) { if (strcasecmp(name, "PLA") == 0) return PLA; if (strcasecmp(name, "ABS") == 0) return ABS; if (strcasecmp(name, "PET") == 0) return PET; if (strcasecmp(name, "HIPS") == 0) return HIPS; if (strcasecmp(name, "FLEX") == 0) return FLEX; if (strcasecmp(name, "SCAFF") == 0) return SCAFF; if (strcasecmp(name, "EDGE") == 0) return EDGE; if (strcasecmp(name, "NGEN") == 0) return NGEN; if (strcasecmp(name, "PVA") == 0) return PVA; return INVALID; } // Returns gcode to prime the nozzles at the front edge of the print bed. WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. const std::vector &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. bool last_wipe_inside_wipe_tower, // May be used by a stand alone post processor. Purpose purpose) { this->set_layer(first_layer_height, first_layer_height, tools.size(), true, false); float wipe_area = m_wipe_area; // Calculate the amount of wipe over the wipe tower brim following the prime, decrease wipe_area // with the amount of material extruded over the brim. { // Simulate the brim extrusions, summ the length of the extrusion. float e_length = this->tool_change(0, false, PURPOSE_EXTRUDE).total_extrusion_length_in_plane(); // Shrink wipe_area by the amount of extrusion extruded by the finish_layer(). // Y stepping of the wipe extrusions. float dy = m_perimeter_width * 0.8f; // Number of whole wipe lines, that would be extruded to wipe as much material as the finish_layer(). // Minimum wipe area is 5mm wide. //FIXME calculate the purge_lines_width precisely. float purge_lines_width = 1.3f; wipe_area = std::max(5.f, m_wipe_area - float(floor(e_length / m_wipe_tower_width)) * dy - purge_lines_width); } this->set_layer(first_layer_height, first_layer_height, tools.size(), true, false); this->m_num_layer_changes = 0; this->m_current_tool = tools.front(); // The Prusa i3 MK2 has a working space of [0, -2.2] to [250, 210]. // Due to the XYZ calibration, this working space may shrink slightly from all directions, // therefore the homing position is shifted inside the bed by 0.2 in the firmware to [0.2, -2.0]. // box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area); box_coordinates cleaning_box(xy(5.f, 0.f), m_wipe_tower_width, wipe_area); PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_layer_height(m_layer_height) .set_initial_tool(m_current_tool) .append(";--------------------\n" "; CP PRIMING START\n") .append(";--------------------\n") .speed_override(100); // Always move to the starting position. writer.set_initial_position(xy(0.f, 0.f)) .travel(cleaning_box.ld, 7200) // Increase the extruder driver current to allow fast ramming. .set_extruder_trimpot(750); if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { float y_end = 0.f; for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { unsigned int tool = tools[idx_tool]; // Select the tool, set a speed override for soluble and flex materials. toolchange_Change(writer, tool, m_material[tool]); // Prime the tool. toolchange_Load(writer, cleaning_box); if (idx_tool + 1 == tools.size()) { // Last tool should not be unloaded, but it should be wiped enough to become of a pure color. if (last_wipe_inside_wipe_tower) { // Shrink the last wipe area to the area of the other purge areas, // remember the last initial wipe width to be purged into the 1st layer of the wipe tower. this->m_initial_extra_wipe = std::max(0.f, wipe_area - (y_end + 0.5f * 0.85f * m_perimeter_width - cleaning_box.ld.y)); cleaning_box.lu.y -= this->m_initial_extra_wipe; cleaning_box.ru.y -= this->m_initial_extra_wipe; } toolchange_Wipe(writer, cleaning_box, false); } else { // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200); // Change the extruder temperature to the temperature of the next filament before starting the cooling moves. toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], m_first_layer_temperature[tools[idx_tool+1]]); // Save the y end of the non-last priming area. y_end = writer.y(); cleaning_box.translate(m_wipe_tower_width, 0.f); writer.travel(cleaning_box.ld, 7200); } ++ m_num_tool_changes; } } // Reset the extruder current to a normal value. writer.set_extruder_trimpot(550) .feedrate(6000) .flush_planner_queue() .reset_extruder() .append("; CP PRIMING END\n" ";------------------\n" "\n\n"); // Force m_idx_tool_change_in_layer to -1, so that tool_change() will know to extrude the wipe tower brim. m_idx_tool_change_in_layer = (unsigned int)(-1); ToolChangeResult result; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); result.start_pos = writer.start_pos(); result.end_pos = writer.pos(); return result; } WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, bool last_in_layer, Purpose purpose) { // Either it is the last tool unload, // or there must be a nonzero wipe tower partitions available. // assert(tool < 0 || it_layer_tools->wipe_tower_partitions > 0); if (m_idx_tool_change_in_layer == (unsigned int)(-1)) { // First layer, prime the extruder. return toolchange_Brim(purpose); } float wipe_area = m_wipe_area; if (++ m_idx_tool_change_in_layer < (unsigned int)m_max_color_changes && last_in_layer) { // This tool_change() call will be followed by a finish_layer() call. // Try to shrink the wipe_area to save material, as less than usual wipe is required // if this step is foolowed by finish_layer() extrusions wiping the same extruder. for (size_t iter = 0; iter < 3; ++ iter) { // Simulate the finish_layer() extrusions, summ the length of the extrusion. float e_length = 0.f; { unsigned int old_idx_tool_change = m_idx_tool_change_in_layer; float old_wipe_start_y = m_current_wipe_start_y; m_current_wipe_start_y += wipe_area; e_length = this->finish_layer(PURPOSE_EXTRUDE).total_extrusion_length_in_plane(); m_idx_tool_change_in_layer = old_idx_tool_change; m_current_wipe_start_y = old_wipe_start_y; } // Shrink wipe_area by the amount of extrusion extruded by the finish_layer(). // Y stepping of the wipe extrusions. float dy = m_perimeter_width * 0.8f; // Number of whole wipe lines, that would be extruded to wipe as much material as the finish_layer(). float num_lines_extruded = floor(e_length / m_wipe_tower_width); // Minimum wipe area is 5mm wide. wipe_area = m_wipe_area - num_lines_extruded * dy; if (wipe_area < 5.) { wipe_area = 5.; break; } } } box_coordinates cleaning_box( m_wipe_tower_pos + xy(0.f, m_current_wipe_start_y + 0.5f * m_perimeter_width), m_wipe_tower_width, wipe_area - m_perimeter_width); PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_layer_height(m_layer_height) .set_initial_tool(m_current_tool) .append(";--------------------\n" "; CP TOOLCHANGE START\n") .comment_with_value(" toolchange #", m_num_tool_changes) .comment_material(m_material[m_current_tool]) .append(";--------------------\n") .speed_override(100); xy initial_position = ((m_current_shape == SHAPE_NORMAL) ? cleaning_box.ld : cleaning_box.lu) + xy(m_perimeter_width, ((m_current_shape == SHAPE_NORMAL) ? 1.f : -1.f) * m_perimeter_width); if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { // Scaffold leaks terribly, reduce leaking by a full retract when going to the wipe tower. float initial_retract = ((m_material[m_current_tool] == SCAFF) ? 1.f : 0.5f) * m_retract; writer // Lift for a Z hop. .z_hop(m_zhop, 7200) // Additional retract on move to tower. .retract(initial_retract, 3600) // Move to a starting position, one perimeter width inside the cleaning box. .travel(initial_position, 7200) // Unlift for a Z hop. .z_hop_reset(7200) // Additional retract on move to tower. .load(initial_retract, 3600) .load(m_retract, 1500); } else { // Already at the initial position. writer.set_initial_position(initial_position); } if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { // Increase the extruder driver current to allow fast ramming. writer.set_extruder_trimpot(750); // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. if (tool != (unsigned int)-1) { toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], m_is_first_layer ? m_first_layer_temperature[tool] : m_temperature[tool]); // This is not the last change. // Change the tool, set a speed override for soluble and flex materials. toolchange_Change(writer, tool, m_material[tool]); toolchange_Load(writer, cleaning_box); // Wipe the newly loaded filament until the end of the assigned wipe area. toolchange_Wipe(writer, cleaning_box, false); // Draw a perimeter around cleaning_box and wipe. box_coordinates box = cleaning_box; if (m_current_shape == SHAPE_REVERSED) { std::swap(box.lu, box.ld); std::swap(box.ru, box.rd); } // Draw a perimeter around cleaning_box. writer.travel(box.lu, 7000) .extrude(box.ld, 3200).extrude(box.rd) .extrude(box.ru).extrude(box.lu); // Wipe the nozzle. //if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. writer.travel(box.ru, 7200) .travel(box.lu); } else toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], m_temperature[m_current_tool]); // Reset the extruder current to a normal value. writer.set_extruder_trimpot(550) .feedrate(6000) .flush_planner_queue() .reset_extruder() .append("; CP TOOLCHANGE END\n" ";------------------\n" "\n\n"); ++ m_num_tool_changes; m_current_wipe_start_y += wipe_area; } ToolChangeResult result; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); result.start_pos = writer.start_pos(); result.end_pos = writer.pos(); return result; } WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(Purpose purpose, bool sideOnly, float y_offset) { const box_coordinates wipeTower_box( m_wipe_tower_pos, m_wipe_tower_width, m_wipe_area * float(m_max_color_changes) - m_perimeter_width / 2); PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow * 1.1f) // Let the writer know the current Z position as a base for Z-hop. .set_z(m_z_pos) .set_layer_height(m_layer_height) .set_initial_tool(m_current_tool) .append( ";-------------------------------------\n" "; CP WIPE TOWER FIRST LAYER BRIM START\n"); xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0); if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) // Move with Z hop. writer.z_hop(m_zhop, 7200) .travel(initial_position, 6000) .z_hop_reset(7200); else writer.set_initial_position(initial_position); if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { // Prime the extruder 10*m_perimeter_width left along the vertical edge of the wipe tower. writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400); // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. // toolchange_Change(writer, int(tool), m_material[tool]); if (sideOnly) { float x_offset = m_perimeter_width; for (size_t i = 0; i < 4; ++ i, x_offset += m_perimeter_width) writer.travel (wipeTower_box.ld + xy(- x_offset, y_offset), 7000) .extrude(wipeTower_box.lu + xy(- x_offset, - y_offset), 2100); writer.travel(wipeTower_box.rd + xy(x_offset, y_offset), 7000); x_offset = m_perimeter_width; for (size_t i = 0; i < 4; ++ i, x_offset += m_perimeter_width) writer.travel (wipeTower_box.rd + xy(x_offset, y_offset), 7000) .extrude(wipeTower_box.ru + xy(x_offset, - y_offset), 2100); } else { // Extrude 4 rounds of a brim around the future wipe tower. box_coordinates box(wipeTower_box); //FIXME why is the box shifted in +Y by 0.5f * m_perimeter_width? box.translate(0.f, 0.5f * m_perimeter_width); box.expand(0.5f * m_perimeter_width); for (size_t i = 0; i < 4; ++ i) { writer.travel (box.ld, 7000) .extrude(box.lu, 2100).extrude(box.ru) .extrude(box.rd ).extrude(box.ld); box.expand(m_perimeter_width); } } if (m_initial_extra_wipe > m_perimeter_width * 1.9f) { box_coordinates cleaning_box( m_wipe_tower_pos + xy(0.f, 0.5f * m_perimeter_width), m_wipe_tower_width, m_initial_extra_wipe - m_perimeter_width); writer.travel(cleaning_box.ld + xy(m_perimeter_width, 0.5f * m_perimeter_width), 6000); // Wipe the newly loaded filament until the end of the assigned wipe area. toolchange_Wipe(writer, cleaning_box, true); // Draw a perimeter around cleaning_box. writer.travel(cleaning_box.lu, 7000) .extrude(cleaning_box.ld, 3200).extrude(cleaning_box.rd) .extrude(cleaning_box.ru).extrude(cleaning_box.lu); m_current_wipe_start_y = m_initial_extra_wipe; } // Move to the front left corner. writer.travel(wipeTower_box.ld, 7000); //if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) // Wipe along the front edge. // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. writer.travel(wipeTower_box.rd) .travel(wipeTower_box.ld); writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n" ";-----------------------------------\n"); // Mark the brim as extruded. m_idx_tool_change_in_layer = 0; } ToolChangeResult result; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); result.start_pos = writer.start_pos(); result.end_pos = writer.pos(); return result; } // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. void WipeTowerPrusaMM::toolchange_Unload( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box, const material_type current_material, const int new_temperature) { float xl = cleaning_box.ld.x + 0.5f * m_perimeter_width; float xr = cleaning_box.rd.x - 0.5f * m_perimeter_width; float y_step = ((m_current_shape == SHAPE_NORMAL) ? 1.f : -1.f) * m_perimeter_width; writer.append("; CP TOOLCHANGE UNLOAD\n"); // Ram the hot material out of the extruder melt zone. // Current extruder position is on the left, one perimeter inside the cleaning box in both X and Y. float e0 = m_perimeter_width * m_extrusion_flow; float e = (xr - xl) * m_extrusion_flow; switch (current_material) { case ABS: // ramming start end y increment amount feedrate writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 0.2f, 0, 1.2f * e, 4000) .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.2f, e0, 1.6f * e, 4600) .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 1.2f, e0, 1.8f * e, 5000) .ram(xr - m_perimeter_width * 2, xl + m_perimeter_width * 2, y_step * 1.2f, e0, 1.8f * e, 5000); break; case PVA: // Used for the PrimaSelect PVA writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 0.2f, 0, 1.75f * e, 4000) .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.5f, 0, 1.75f * e, 4500) .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 1.5f, 0, 1.75f * e, 4800) .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.5f, 0, 1.75f * e, 5000); break; case SCAFF: writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 2.f, 0, 1.75f * e, 4000) .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 3.f, 0, 2.34f * e, 4600) .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 3.f, 0, 2.63f * e, 5200); break; default: // PLA, PLA/PHA and others // Used for the Verbatim BVOH, PET, NGEN, co-polyesters writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 0.2f, 0, 1.60f * e, 4000) .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.2f, e0, 1.65f * e, 4600) .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 1.2f, e0, 1.74f * e, 5200); } // Pull the filament end into a cooling tube. writer.retract(15, 5000).retract(50, 5400).retract(15, 3000).retract(12, 2000); if (new_temperature != 0) // Set the extruder temperature, but don't wait. writer.set_extruder_temp(new_temperature, false); // In case the current print head position is closer to the left edge, reverse the direction. if (std::abs(writer.x() - xl) < std::abs(writer.x() - xr)) std::swap(xl, xr); // Horizontal cooling moves will be performed at the following Y coordinate: writer.travel(xr, writer.y() + y_step * 0.8f, 7200) .suppress_preview(); switch (current_material) { case PVA: writer.cool(xl, xr, 3, -5, 1600) .cool(xl, xr, 5, -5, 2000) .cool(xl, xr, 5, -5, 2200) .cool(xl, xr, 5, -5, 2400) .cool(xl, xr, 5, -5, 2400) .cool(xl, xr, 5, -3, 2400); break; case SCAFF: writer.cool(xl, xr, 3, -5, 1600) .cool(xl, xr, 5, -5, 2000) .cool(xl, xr, 5, -5, 2200) .cool(xl, xr, 5, -5, 2200) .cool(xl, xr, 5, -3, 2400); break; default: writer.cool(xl, xr, 3, -5, 1600) .cool(xl, xr, 5, -5, 2000) .cool(xl, xr, 5, -5, 2400) .cool(xl, xr, 5, -3, 2400); } writer.resume_preview() .flush_planner_queue(); } // Change the tool, set a speed override for solube and flex materials. void WipeTowerPrusaMM::toolchange_Change( PrusaMultiMaterial::Writer &writer, const unsigned int new_tool, material_type new_material) { // Speed override for the material. Go slow for flex and soluble materials. int speed_override; switch (new_material) { case PVA: speed_override = (m_z_pos < 0.80f) ? 60 : 80; break; case SCAFF: speed_override = 35; break; case FLEX: speed_override = 35; break; default: speed_override = 100; } writer.set_tool(new_tool) .speed_override(speed_override) .flush_planner_queue(); m_current_tool = new_tool; } void WipeTowerPrusaMM::toolchange_Load( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box) { float xl = cleaning_box.ld.x + m_perimeter_width; float xr = cleaning_box.rd.x - m_perimeter_width; //FIXME flipping left / right side, so that the following toolchange_Wipe will start // where toolchange_Load ends. std::swap(xl, xr); writer.append("; CP TOOLCHANGE LOAD\n") // Load the filament while moving left / right, // so the excess material will not create a blob at a single position. .suppress_preview() // Accelerate the filament loading .load_move_x(xr, 20, 1400) // Fast loading phase .load_move_x(xl, 40, 3000) // Slowing down .load_move_x(xr, 20, 1600) .load_move_x(xl, 10, 1000) .resume_preview(); // Extrude first five lines (just three lines if colorInit is set). writer.extrude(xr, writer.y(), 1600); bool colorInit = false; size_t pass = colorInit ? 1 : 2; float dy = ((m_current_shape == SHAPE_NORMAL) ? 1.f : -1.f) * m_perimeter_width * 0.85f; for (int i = 0; i < pass; ++ i) { writer.travel (xr, writer.y() + dy, 7200); writer.extrude(xl, writer.y(), 2200); writer.travel (xl, writer.y() + dy, 7200); writer.extrude(xr, writer.y(), 2200); } // Reset the extruder current to the normal value. writer.set_extruder_trimpot(550); } // Wipe the newly loaded filament until the end of the assigned wipe area. void WipeTowerPrusaMM::toolchange_Wipe( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box, bool skip_initial_y_move) { // Increase flow on first layer, slow down print. writer.set_extrusion_flow(m_extrusion_flow * (m_is_first_layer ? 1.18f : 1.f)) .append("; CP TOOLCHANGE WIPE\n"); float wipe_coeff = m_is_first_layer ? 0.5f : 1.f; float xl = cleaning_box.ld.x + 2.f * m_perimeter_width; float xr = cleaning_box.rd.x - 2.f * m_perimeter_width; // Wipe speed will increase up to 4800. float wipe_speed = 4200.f; float wipe_speed_inc = 50.f; float wipe_speed_max = 4800.f; // Y increment per wipe line. float dy = ((m_current_shape == SHAPE_NORMAL) ? 1.f : -1.f) * m_perimeter_width * 0.8f; for (bool p = true; // Next wipe line fits the cleaning box. ((m_current_shape == SHAPE_NORMAL) ? (writer.y() <= cleaning_box.lu.y - m_perimeter_width) : (writer.y() >= cleaning_box.ld.y + m_perimeter_width)); p = ! p) { wipe_speed = std::min(wipe_speed_max, wipe_speed + wipe_speed_inc); if (skip_initial_y_move) skip_initial_y_move = false; else writer.extrude(xl - (p ? m_perimeter_width / 2 : m_perimeter_width), writer.y() + dy, wipe_speed * wipe_coeff); writer.extrude(xr + (p ? m_perimeter_width : m_perimeter_width * 2), writer.y(), wipe_speed * wipe_coeff); // Next wipe line fits the cleaning box. if ((m_current_shape == SHAPE_NORMAL) ? (writer.y() > cleaning_box.lu.y - m_perimeter_width) : (writer.y() < cleaning_box.ld.y + m_perimeter_width)) break; wipe_speed = std::min(wipe_speed_max, wipe_speed + wipe_speed_inc); writer.extrude(xr + m_perimeter_width, writer.y() + dy, wipe_speed * wipe_coeff); writer.extrude(xl - m_perimeter_width, writer.y()); } // Reset the extrusion flow. writer.set_extrusion_flow(m_extrusion_flow); } WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer(Purpose purpose) { // This should only be called if the layer is not finished yet. // Otherwise the caller would likely travel to the wipe tower in vain. assert(! this->layer_finished()); PrusaMultiMaterial::Writer writer; writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_layer_height(m_layer_height) .set_initial_tool(m_current_tool) .append(";--------------------\n" "; CP EMPTY GRID START\n") // m_num_layer_changes is incremented by set_z, so it is 1 based. .comment_with_value(" layer #", m_num_layer_changes - 1); // Slow down on the 1st layer. float speed_factor = m_is_first_layer ? 0.5f : 1.f; box_coordinates fill_box(m_wipe_tower_pos + xy(0.f, m_current_wipe_start_y), m_wipe_tower_width, float(m_max_color_changes) * m_wipe_area - m_current_wipe_start_y); fill_box.expand(0.f, - 0.5f * m_perimeter_width); { float firstLayerOffset = 0.f; fill_box.ld.y += firstLayerOffset; fill_box.rd.y += firstLayerOffset; } if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { if (m_idx_tool_change_in_layer == 0) { // There were no tool changes at all in this layer. writer.retract(m_retract * 1.5f, 3600) // Jump with retract to fill_box.ld + a random shift in +x. .z_hop(m_zhop, 7200) .travel(fill_box.ld + xy(5.f + 15.f * float(rand()) / RAND_MAX, 0.f), 7000) .z_hop_reset(7200) // Prime the extruder. .load_move_x(fill_box.ld.x, m_retract * 1.5f, 3600); } else { // Otherwise the extruder is already over the wipe tower. } } else { // The print head is inside the wipe tower. Rather move to the start of the following extrusion. // writer.set_initial_position(fill_box.ld); writer.set_initial_position(fill_box.ld); } if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { // Extrude the first perimeter. box_coordinates box = fill_box; writer.extrude(box.lu, 2400 * speed_factor) .extrude(box.ru) .extrude(box.rd) .extrude(box.ld + xy(m_perimeter_width / 2, 0)); // Extrude second perimeter. box.expand(- m_perimeter_width / 2); writer.extrude(box.lu, 3200 * speed_factor) .extrude(box.ru) .extrude(box.rd) .extrude(box.ld + xy(m_perimeter_width / 2, 0)); if (m_is_first_layer) { // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower. box.expand(- m_perimeter_width / 2); box.ld.y -= 0.5f * m_perimeter_width; box.rd.y = box.ld.y; int nsteps = int(floor((box.lu.y - box.ld.y) / (2. * (1.0 * m_perimeter_width)))); float step = (box.lu.y - box.ld.y) / nsteps; for (size_t i = 0; i < nsteps; ++ i) { writer.extrude(box.ld.x, writer.y() + 0.5f * step); writer.extrude(box.rd.x, writer.y()); writer.extrude(box.rd.x, writer.y() + 0.5f * step); writer.extrude(box.ld.x, writer.y()); } } else { // Extrude a sparse infill to support the material to be printed above. // Extrude an inverse U at the left of the region. writer.extrude(box.ld + xy(m_perimeter_width / 2, m_perimeter_width / 2)) .extrude(fill_box.ld + xy(m_perimeter_width * 3, m_perimeter_width), 2900 * speed_factor) .extrude(fill_box.lu + xy(m_perimeter_width * 3, - m_perimeter_width)) .extrude(fill_box.lu + xy(m_perimeter_width * 6, - m_perimeter_width)) .extrude(fill_box.ld + xy(m_perimeter_width * 6, m_perimeter_width)); if (fill_box.lu.y - fill_box.ld.y > 4.f) { // Extrude three zig-zags. float step = (m_wipe_tower_width - m_perimeter_width * 12.f) / 12.f; for (size_t i = 0; i < 3; ++ i) { writer.extrude(writer.x() + step, fill_box.ld.y + m_perimeter_width * 8, 3200 * speed_factor); writer.extrude(writer.x() , fill_box.lu.y - m_perimeter_width * 8); writer.extrude(writer.x() + step, fill_box.lu.y - m_perimeter_width ); writer.extrude(writer.x() + step, fill_box.lu.y - m_perimeter_width * 8); writer.extrude(writer.x() , fill_box.ld.y + m_perimeter_width * 8); writer.extrude(writer.x() + step, fill_box.ld.y + m_perimeter_width ); } } // Extrude an inverse U at the left of the region. writer.extrude(fill_box.ru + xy(- m_perimeter_width * 6, - m_perimeter_width), 2900 * speed_factor) .extrude(fill_box.ru + xy(- m_perimeter_width * 3, - m_perimeter_width)) .extrude(fill_box.rd + xy(- m_perimeter_width * 3, m_perimeter_width)) .extrude(fill_box.rd + xy(- m_perimeter_width, m_perimeter_width)); } // if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) if (true) // Wipe along the front side of the current wiping box. // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. writer.travel(fill_box.ld + xy( m_perimeter_width, m_perimeter_width / 2), 7200) .travel(fill_box.rd + xy(- m_perimeter_width, m_perimeter_width / 2)); else writer.feedrate(7200); writer.append("; CP EMPTY GRID END\n" ";------------------\n\n\n\n\n\n\n"); // Indicate that this wipe tower layer is fully covered. m_idx_tool_change_in_layer = (unsigned int)m_max_color_changes; } ToolChangeResult result; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); result.start_pos = writer.start_pos(); result.end_pos = writer.pos(); return result; } }; // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp000066400000000000000000000214601324354444700245210ustar00rootroot00000000000000#ifndef WipeTowerPrusaMM_hpp_ #define WipeTowerPrusaMM_hpp_ #include #include #include #include #include "WipeTower.hpp" namespace Slic3r { namespace PrusaMultiMaterial { class Writer; }; class WipeTowerPrusaMM : public WipeTower { public: enum material_type { INVALID = -1, PLA = 0, // E:210C B:55C ABS = 1, // E:255C B:100C PET = 2, // E:240C B:90C HIPS = 3, // E:220C B:100C FLEX = 4, // E:245C B:80C SCAFF = 5, // E:215C B:55C EDGE = 6, // E:240C B:80C NGEN = 7, // E:230C B:80C PVA = 8 // E:210C B:80C }; // Parse material name into material_type. static material_type parse_material(const char *name); // x -- x coordinates of wipe tower in mm ( left bottom corner ) // y -- y coordinates of wipe tower in mm ( left bottom corner ) // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) // wipe_area -- space available for one toolchange in mm WipeTowerPrusaMM(float x, float y, float width, float wipe_area, unsigned int initial_tool) : m_wipe_tower_pos(x, y), m_wipe_tower_width(width), m_wipe_area(wipe_area), m_z_pos(0.f), m_current_tool(initial_tool) { for (size_t i = 0; i < 4; ++ i) { // Extruder specific parameters. m_material[i] = PLA; m_temperature[i] = 0; m_first_layer_temperature[i] = 0; } } virtual ~WipeTowerPrusaMM() {} // _retract - retract value in mm void set_retract(float retract) { m_retract = retract; } // _zHop - z hop value in mm void set_zhop(float zhop) { m_zhop = zhop; } // Set the extruder properties. void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp) { m_material[idx] = material; m_temperature[idx] = temp; m_first_layer_temperature[idx] = first_layer_temp; } // Switch to a next layer. virtual void set_layer( // Print height of this layer. float print_z, // Layer height, used to calculate extrusion the rate. float layer_height, // Maximum number of tool changes on this layer or the layers below. size_t max_tool_changes, // Is this the first layer of the print? In that case print the brim first. bool is_first_layer, // Is this the last layer of the waste tower? bool is_last_layer) { m_z_pos = print_z; m_layer_height = layer_height; m_max_color_changes = max_tool_changes; m_is_first_layer = is_first_layer; m_is_last_layer = is_last_layer; // Start counting the color changes from zero. Special case: -1 - extrude a brim first. m_idx_tool_change_in_layer = is_first_layer ? (unsigned int)(-1) : 0; m_current_wipe_start_y = 0.f; m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; ++ m_num_layer_changes; // Extrusion rate for an extrusion aka perimeter width 0.35mm. // Clamp the extrusion height to a 0.2mm layer height, independent of the nozzle diameter. // m_extrusion_flow = std::min(0.2f, layer_height) * 0.145f; // Use a strictly m_extrusion_flow = layer_height * 0.145f; } // Return the wipe tower position. virtual const xy& position() const { return m_wipe_tower_pos; } // Return the wipe tower width. virtual float width() const { return m_wipe_tower_width; } // The wipe tower is finished, there should be no more tool changes or wipe tower prints. virtual bool finished() const { return m_max_color_changes == 0; } // Returns gcode to prime the nozzles at the front edge of the print bed. virtual ToolChangeResult prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. const std::vector &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. bool last_wipe_inside_wipe_tower, // May be used by a stand alone post processor. Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE); // Returns gcode for a toolchange and a final print head position. // On the first layer, extrude a brim around the future wipe tower first. virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer, Purpose purpose); // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag. // Call this method only if layer_finished() is false. virtual ToolChangeResult finish_layer(Purpose purpose); // Is the current layer finished? A layer is finished if either the wipe tower is finished, or // the wipe tower has been completely covered by the tool change extrusions, // or the rest of the tower has been filled by a sparse infill with the finish_layer() method. virtual bool layer_finished() const { return m_idx_tool_change_in_layer == m_max_color_changes; } private: WipeTowerPrusaMM(); // A fill-in direction (positive Y, negative Y) alternates with each layer. enum wipe_shape { SHAPE_NORMAL = 1, SHAPE_REVERSED = -1 }; // Left front corner of the wipe tower in mm. xy m_wipe_tower_pos; // Width of the wipe tower. float m_wipe_tower_width; // Per color Y span. float m_wipe_area; // Current Z position. float m_z_pos = 0.f; // Current layer height. float m_layer_height = 0.f; // Maximum number of color changes per layer. size_t m_max_color_changes = 0; // Is this the 1st layer of the print? If so, print the brim around the waste tower. bool m_is_first_layer = false; // Is this the last layer of this waste tower? bool m_is_last_layer = false; // G-code generator parameters. float m_zhop = 0.5f; float m_retract = 4.f; // Width of an extrusion line, also a perimeter spacing for 100% infill. float m_perimeter_width = 0.5f; // Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. float m_extrusion_flow = 0.029f; // Extruder specific parameters. material_type m_material[4]; int m_temperature[4]; int m_first_layer_temperature[4]; // State of the wiper tower generator. // Layer change counter for the output statistics. unsigned int m_num_layer_changes = 0; // Tool change change counter for the output statistics. unsigned int m_num_tool_changes = 0; // Layer change counter in this layer. Counting up to m_max_color_changes. unsigned int m_idx_tool_change_in_layer = 0; // A fill-in direction (positive Y, negative Y) alternates with each layer. wipe_shape m_current_shape = SHAPE_NORMAL; unsigned int m_current_tool = 0; // Current y position at the wipe tower. float m_current_wipe_start_y = 0.f; // How much to wipe the 1st extruder over the wipe tower at the 1st layer // after the wipe tower brim has been extruded? float m_initial_extra_wipe = 0.f; struct box_coordinates { box_coordinates(float left, float bottom, float width, float height) : ld(left , bottom ), lu(left , bottom + height), rd(left + width, bottom ), ru(left + width, bottom + height) {} box_coordinates(const xy &pos, float width, float height) : box_coordinates(pos.x, pos.y, width, height) {} void translate(const xy &shift) { ld += shift; lu += shift; rd += shift; ru += shift; } void translate(const float dx, const float dy) { translate(xy(dx, dy)); } void expand(const float offset) { ld += xy(- offset, - offset); lu += xy(- offset, offset); rd += xy( offset, - offset); ru += xy( offset, offset); } void expand(const float offset_x, const float offset_y) { ld += xy(- offset_x, - offset_y); lu += xy(- offset_x, offset_y); rd += xy( offset_x, - offset_y); ru += xy( offset_x, offset_y); } xy ld; // left down xy lu; // left upper xy ru; // right upper xy rd; // right lower }; // Returns gcode for wipe tower brim // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower // offset -- set to 0 -- experimental, offset to replace brim in front / rear of wipe tower ToolChangeResult toolchange_Brim(Purpose purpose, bool sideOnly = false, float y_offset = 0.f); void toolchange_Unload( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box, const material_type current_material, const int new_temperature); void toolchange_Change( PrusaMultiMaterial::Writer &writer, const unsigned int new_tool, material_type new_material); void toolchange_Load( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box); void toolchange_Wipe( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box, bool skip_initial_y_move); void toolchange_Perimeter(); }; }; // namespace Slic3r #endif /* WipeTowerPrusaMM_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/GCodeReader.cpp000066400000000000000000000122461324354444700224470ustar00rootroot00000000000000#include "GCodeReader.hpp" #include #include #include #include #include namespace Slic3r { void GCodeReader::apply_config(const GCodeConfig &config) { m_config = config; m_extrusion_axis = m_config.get_extrusion_axis()[0]; } void GCodeReader::apply_config(const DynamicPrintConfig &config) { m_config.apply(config, true); m_extrusion_axis = m_config.get_extrusion_axis()[0]; } const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline, std::pair &command) { PROFILE_FUNC(); // command and args const char *c = ptr; { PROFILE_BLOCK(command_and_args); // Skip the whitespaces. command.first = skip_whitespaces(c); // Skip the command. c = command.second = skip_word(command.first); // Up to the end of line or comment. while (! is_end_of_gcode_line(*c)) { // Skip whitespaces. c = skip_whitespaces(c); if (is_end_of_gcode_line(*c)) break; // Check the name of the axis. Axis axis = NUM_AXES; switch (*c) { case 'X': axis = X; break; case 'Y': axis = Y; break; case 'Z': axis = Z; break; case 'F': axis = F; break; default: if (*c == m_extrusion_axis) axis = E; break; } if (axis != NUM_AXES) { // Try to parse the numeric value. char *pend = nullptr; double v = strtod(++ c, &pend); if (pend != nullptr && is_end_of_word(*pend)) { // The axis value has been parsed correctly. gline.m_axis[int(axis)] = float(v); gline.m_mask |= 1 << int(axis); c = pend; } else // Skip the rest of the word. c = skip_word(c); } else // Skip the rest of the word. c = skip_word(c); } } if (gline.has(E) && m_config.use_relative_e_distances) m_position[E] = 0; // Skip the rest of the line. for (; ! is_end_of_line(*c); ++ c); // Copy the raw string including the comment, without the trailing newlines. if (c > ptr) { PROFILE_BLOCK(copy_raw_string); gline.m_raw.assign(ptr, c); } // Skip the trailing newlines. if (*c == '\r') ++ c; if (*c == '\n') ++ c; if (m_verbose) std::cout << gline.m_raw << std::endl; return c; } void GCodeReader::update_coordinates(GCodeLine &gline, std::pair &command) { PROFILE_FUNC(); if (*command.first == 'G') { int cmd_len = int(command.second - command.first); if ((cmd_len == 2 && (command.first[1] == '0' || command.first[1] == '1')) || (cmd_len == 3 && command.first[1] == '9' && command.first[2] == '2')) { for (size_t i = 0; i < NUM_AXES; ++ i) if (gline.has(Axis(i))) this->m_position[i] = gline.value(Axis(i)); } } } void GCodeReader::parse_file(const std::string &file, callback_t callback) { std::ifstream f(file); std::string line; while (std::getline(f, line)) this->parse_line(line, callback); } bool GCodeReader::GCodeLine::has_value(char axis, float &value) const { const char *c = m_raw.c_str(); // Skip the whitespaces. c = skip_whitespaces(c); // Skip the command. c = skip_word(c); // Up to the end of line or comment. while (! is_end_of_gcode_line(*c)) { // Skip whitespaces. c = skip_whitespaces(c); if (is_end_of_gcode_line(*c)) break; // Check the name of the axis. if (*c == axis) { // Try to parse the numeric value. char *pend = nullptr; double v = strtod(++ c, &pend); if (pend != nullptr && is_end_of_word(*pend)) { // The axis value has been parsed correctly. value = float(v); return true; } } // Skip the rest of the word. c = skip_word(c); } return false; } void GCodeReader::GCodeLine::set(const GCodeReader &reader, const Axis axis, const float new_value, const int decimal_digits) { std::ostringstream ss; ss << std::fixed << std::setprecision(decimal_digits) << new_value; char match[3] = " X"; if (int(axis) < 3) match[1] += int(axis); else if (axis == F) match[1] = 'F'; else { assert(axis == E); match[1] = reader.extrusion_axis(); } if (this->has(axis)) { size_t pos = m_raw.find(match)+2; size_t end = m_raw.find(' ', pos+1); m_raw = m_raw.replace(pos, end-pos, ss.str()); } else { size_t pos = m_raw.find(' '); if (pos == std::string::npos) m_raw += std::string(match) + ss.str(); else m_raw = m_raw.replace(pos, 0, std::string(match) + ss.str()); } m_axis[axis] = new_value; m_mask |= 1 << int(axis); } } Slic3r-version_1.39.1/xs/src/libslic3r/GCodeReader.hpp000066400000000000000000000141231324354444700224500ustar00rootroot00000000000000#ifndef slic3r_GCodeReader_hpp_ #define slic3r_GCodeReader_hpp_ #include "libslic3r.h" #include #include #include #include #include "PrintConfig.hpp" namespace Slic3r { class GCodeReader { public: class GCodeLine { public: GCodeLine() { reset(); } void reset() { m_mask = 0; memset(m_axis, 0, sizeof(m_axis)); m_raw.clear(); } const std::string& raw() const { return m_raw; } const std::string cmd() const { const char *cmd = GCodeReader::skip_whitespaces(m_raw.c_str()); return std::string(cmd, GCodeReader::skip_word(cmd)); } const std::string comment() const { size_t pos = m_raw.find(';'); return (pos == std::string::npos) ? "" : m_raw.substr(pos + 1); } bool has(Axis axis) const { return (m_mask & (1 << int(axis))) != 0; } float value(Axis axis) const { return m_axis[axis]; } bool has_value(char axis, float &value) const; float new_Z(const GCodeReader &reader) const { return this->has(Z) ? this->z() : reader.z(); } float new_E(const GCodeReader &reader) const { return this->has(E) ? this->e() : reader.e(); } float new_F(const GCodeReader &reader) const { return this->has(F) ? this->f() : reader.f(); } float dist_X(const GCodeReader &reader) const { return this->has(X) ? (this->x() - reader.x()) : 0; } float dist_Y(const GCodeReader &reader) const { return this->has(Y) ? (this->y() - reader.y()) : 0; } float dist_Z(const GCodeReader &reader) const { return this->has(Z) ? (this->z() - reader.z()) : 0; } float dist_E(const GCodeReader &reader) const { return this->has(E) ? (this->e() - reader.e()) : 0; } float dist_XY(const GCodeReader &reader) const { float x = this->has(X) ? (this->x() - reader.x()) : 0; float y = this->has(Y) ? (this->y() - reader.y()) : 0; return sqrt(x*x + y*y); } bool cmd_is(const char *cmd_test) const { const char *cmd = GCodeReader::skip_whitespaces(m_raw.c_str()); int len = strlen(cmd_test); return strncmp(cmd, cmd_test, len) == 0 && GCodeReader::is_end_of_word(cmd[len]); } bool extruding(const GCodeReader &reader) const { return this->cmd_is("G1") && this->dist_E(reader) > 0; } bool retracting(const GCodeReader &reader) const { return this->cmd_is("G1") && this->dist_E(reader) < 0; } bool travel() const { return this->cmd_is("G1") && ! this->has(E); } void set(const GCodeReader &reader, const Axis axis, const float new_value, const int decimal_digits = 3); bool has_x() const { return this->has(X); } bool has_y() const { return this->has(Y); } bool has_z() const { return this->has(Z); } bool has_e() const { return this->has(E); } bool has_f() const { return this->has(F); } float x() const { return m_axis[X]; } float y() const { return m_axis[Y]; } float z() const { return m_axis[Z]; } float e() const { return m_axis[E]; } float f() const { return m_axis[F]; } private: std::string m_raw; float m_axis[NUM_AXES]; uint32_t m_mask; friend class GCodeReader; }; typedef std::function callback_t; GCodeReader() : m_verbose(false), m_extrusion_axis('E') { memset(m_position, 0, sizeof(m_position)); } void apply_config(const GCodeConfig &config); void apply_config(const DynamicPrintConfig &config); template void parse_buffer(const std::string &buffer, Callback callback) { const char *ptr = buffer.c_str(); GCodeLine gline; while (*ptr != 0) { gline.reset(); ptr = this->parse_line(ptr, gline, callback); } } void parse_buffer(const std::string &buffer) { this->parse_buffer(buffer, [](GCodeReader&, const GCodeReader::GCodeLine&){}); } template const char* parse_line(const char *ptr, GCodeLine &gline, Callback &callback) { std::pair cmd; const char *end = parse_line_internal(ptr, gline, cmd); callback(*this, gline); update_coordinates(gline, cmd); return end; } template void parse_line(const std::string &line, Callback callback) { GCodeLine gline; this->parse_line(line.c_str(), gline, callback); } void parse_file(const std::string &file, callback_t callback); float& x() { return m_position[X]; } float x() const { return m_position[X]; } float& y() { return m_position[Y]; } float y() const { return m_position[Y]; } float& z() { return m_position[Z]; } float z() const { return m_position[Z]; } float& e() { return m_position[E]; } float e() const { return m_position[E]; } float& f() { return m_position[F]; } float f() const { return m_position[F]; } char extrusion_axis() const { return m_extrusion_axis; } private: const char* parse_line_internal(const char *ptr, GCodeLine &gline, std::pair &command); void update_coordinates(GCodeLine &gline, std::pair &command); static bool is_whitespace(char c) { return c == ' ' || c == '\t'; } static bool is_end_of_line(char c) { return c == '\r' || c == '\n' || c == 0; } static bool is_end_of_gcode_line(char c) { return c == ';' || is_end_of_line(c); } static bool is_end_of_word(char c) { return is_whitespace(c) || is_end_of_gcode_line(c); } static const char* skip_whitespaces(const char *c) { for (; is_whitespace(*c); ++ c); return c; } static const char* skip_word(const char *c) { for (; ! is_end_of_word(*c); ++ c); return c; } GCodeConfig m_config; char m_extrusion_axis; float m_position[NUM_AXES]; bool m_verbose; }; } /* namespace Slic3r */ #endif /* slic3r_GCodeReader_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/GCodeSender.cpp000066400000000000000000000401011324354444700224540ustar00rootroot00000000000000#include "GCodeSender.hpp" #include #include #include #include #include #include #include #if defined(__APPLE__) || defined(__OpenBSD__) #include #endif #ifdef __APPLE__ #include #include #endif #ifdef __linux__ #include #include #include "/usr/include/asm-generic/ioctls.h" /* The following definitions are kindly borrowed from: /usr/include/asm-generic/termbits.h Unfortunately we cannot just include that one because it would redefine the "struct termios" already defined the already included by Boost.ASIO. */ #define K_NCCS 19 struct termios2 { tcflag_t c_iflag; tcflag_t c_oflag; tcflag_t c_cflag; tcflag_t c_lflag; cc_t c_line; cc_t c_cc[K_NCCS]; speed_t c_ispeed; speed_t c_ospeed; }; #define BOTHER CBAUDEX #endif //#define DEBUG_SERIAL #ifdef DEBUG_SERIAL #include std::fstream fs; #endif #define KEEP_SENT 20 namespace Slic3r { GCodeSender::GCodeSender() : io(), serial(io), can_send(false), sent(0), open(false), error(false), connected(false), queue_paused(false) {} GCodeSender::~GCodeSender() { this->disconnect(); } bool GCodeSender::connect(std::string devname, unsigned int baud_rate) { this->disconnect(); this->set_error_status(false); try { this->serial.open(devname); this->serial.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::odd)); this->serial.set_option(boost::asio::serial_port_base::character_size(boost::asio::serial_port_base::character_size(8))); this->serial.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none)); this->serial.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one)); this->set_baud_rate(baud_rate); this->serial.close(); this->serial.open(devname); this->serial.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none)); // set baud rate again because set_option overwrote it this->set_baud_rate(baud_rate); this->open = true; this->reset(); } catch (boost::system::system_error &) { this->set_error_status(true); return false; } // a reset firmware expect line numbers to start again from 1 this->sent = 0; this->last_sent.clear(); /* Initialize debugger */ #ifdef DEBUG_SERIAL fs.open("serial.txt", std::fstream::out | std::fstream::trunc); #endif // this gives some work to the io_service before it is started // (post() runs the supplied function in its thread) this->io.post(boost::bind(&GCodeSender::do_read, this)); // start reading in the background thread boost::thread t(boost::bind(&boost::asio::io_service::run, &this->io)); this->background_thread.swap(t); // always send a M105 to check for connection because firmware might be silent on connect //FIXME Vojtech: This is being sent too early, leading to line number synchronization issues, // from which the GCodeSender never recovers. // this->send("M105", true); return true; } void GCodeSender::set_baud_rate(unsigned int baud_rate) { try { // This does not support speeds > 115200 this->serial.set_option(boost::asio::serial_port_base::baud_rate(baud_rate)); } catch (boost::system::system_error &) { boost::asio::serial_port::native_handle_type handle = this->serial.native_handle(); #if __APPLE__ termios ios; ::tcgetattr(handle, &ios); ::cfsetspeed(&ios, baud_rate); speed_t newSpeed = baud_rate; ioctl(handle, IOSSIOSPEED, &newSpeed); ::tcsetattr(handle, TCSANOW, &ios); #elif __linux termios2 ios; if (ioctl(handle, TCGETS2, &ios)) printf("Error in TCGETS2: %s\n", strerror(errno)); ios.c_ispeed = ios.c_ospeed = baud_rate; ios.c_cflag &= ~CBAUD; ios.c_cflag |= BOTHER | CLOCAL | CREAD; ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read ios.c_cc[VTIME] = 1; if (ioctl(handle, TCSETS2, &ios)) printf("Error in TCSETS2: %s\n", strerror(errno)); #elif __OpenBSD__ struct termios ios; ::tcgetattr(handle, &ios); ::cfsetspeed(&ios, baud_rate); if (::tcsetattr(handle, TCSAFLUSH, &ios) != 0) printf("Failed to set baud rate: %s\n", strerror(errno)); #else //throw invalid_argument ("OS does not currently support custom bauds"); #endif } } void GCodeSender::disconnect() { if (!this->open) return; this->open = false; this->connected = false; this->io.post(boost::bind(&GCodeSender::do_close, this)); this->background_thread.join(); this->io.reset(); /* if (this->error_status()) { throw(boost::system::system_error(boost::system::error_code(), "Error while closing the device")); } */ #ifdef DEBUG_SERIAL fs << "DISCONNECTED" << std::endl << std::flush; fs.close(); #endif } bool GCodeSender::is_connected() const { return this->connected; } bool GCodeSender::wait_connected(unsigned int timeout) const { using namespace boost::posix_time; ptime t0 = second_clock::local_time() + seconds(timeout); while (!this->connected) { if (second_clock::local_time() > t0) return false; boost::this_thread::sleep(boost::posix_time::milliseconds(100)); } return true; } size_t GCodeSender::queue_size() const { boost::lock_guard l(this->queue_mutex); return this->queue.size(); } void GCodeSender::pause_queue() { boost::lock_guard l(this->queue_mutex); this->queue_paused = true; } void GCodeSender::resume_queue() { { boost::lock_guard l(this->queue_mutex); this->queue_paused = false; } this->send(); } void GCodeSender::purge_queue(bool priority) { boost::lock_guard l(this->queue_mutex); if (priority) { // clear priority queue std::list empty; std::swap(this->priqueue, empty); } else { // clear queue std::queue empty; std::swap(this->queue, empty); this->queue_paused = false; } } // purge log and return its contents std::vector GCodeSender::purge_log() { boost::lock_guard l(this->log_mutex); std::vector retval; retval.reserve(this->log.size()); while (!this->log.empty()) { retval.push_back(this->log.front()); this->log.pop(); } return retval; } std::string GCodeSender::getT() const { boost::lock_guard l(this->log_mutex); return this->T; } std::string GCodeSender::getB() const { boost::lock_guard l(this->log_mutex); return this->B; } void GCodeSender::do_close() { this->set_error_status(false); boost::system::error_code ec; this->serial.cancel(ec); if (ec) this->set_error_status(true); this->serial.close(ec); if (ec) this->set_error_status(true); } void GCodeSender::set_error_status(bool e) { boost::lock_guard l(this->error_mutex); this->error = e; } bool GCodeSender::error_status() const { boost::lock_guard l(this->error_mutex); return this->error; } void GCodeSender::do_read() { // read one line boost::asio::async_read_until( this->serial, this->read_buffer, '\n', boost::bind( &GCodeSender::on_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred ) ); } void GCodeSender::on_read(const boost::system::error_code& error, size_t bytes_transferred) { this->set_error_status(false); if (error) { #ifdef __APPLE__ if (error.value() == 45) { // OS X bug: http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html this->do_read(); return; } #endif // printf("ERROR: [%d] %s\n", error.value(), error.message().c_str()); // error can be true even because the serial port was closed. // In this case it is not a real error, so ignore. if (this->open) { this->do_close(); this->set_error_status(true); } return; } std::istream is(&this->read_buffer); std::string line; std::getline(is, line); if (!line.empty()) { #ifdef DEBUG_SERIAL fs << "<< " << line << std::endl << std::flush; #endif // note that line might contain \r at its end // parse incoming line if (!this->connected && (boost::starts_with(line, "start") || boost::starts_with(line, "Grbl ") || boost::starts_with(line, "ok") || boost::contains(line, "T:"))) { this->connected = true; { boost::lock_guard l(this->queue_mutex); this->can_send = true; } this->send(); } else if (boost::starts_with(line, "ok")) { { boost::lock_guard l(this->queue_mutex); this->can_send = true; } this->send(); } else if (boost::istarts_with(line, "resend") // Marlin uses "Resend: " || boost::istarts_with(line, "rs")) { // extract the first number from line boost::algorithm::trim_left_if(line, !boost::algorithm::is_digit()); size_t toresend = boost::lexical_cast(line.substr(0, line.find_first_not_of("0123456789"))); ++ toresend; // N is 0-based if (toresend >= this->sent - this->last_sent.size() && toresend < this->last_sent.size()) { { boost::lock_guard l(this->queue_mutex); // move the unsent lines to priqueue this->priqueue.insert( this->priqueue.begin(), // insert at the beginning this->last_sent.begin() + toresend - (this->sent - this->last_sent.size()) - 1, this->last_sent.end() ); // we can empty last_sent because it's not useful anymore this->last_sent.clear(); // start resending with the requested line number this->sent = toresend - 1; this->can_send = true; } this->send(); } else { printf("Cannot resend " PRINTF_ZU " (oldest we have is " PRINTF_ZU ")\n", toresend, this->sent - this->last_sent.size()); } } else if (boost::starts_with(line, "wait")) { // ignore } else { // push any other line into the log boost::lock_guard l(this->log_mutex); this->log.push(line); } // parse temperature info { size_t pos = line.find("T:"); if (pos != std::string::npos && line.size() > pos + 2) { // we got temperature info boost::lock_guard l(this->log_mutex); this->T = line.substr(pos+2, line.find_first_not_of("0123456789.", pos+2) - (pos+2)); pos = line.find("B:"); if (pos != std::string::npos && line.size() > pos + 2) { // we got bed temperature info this->B = line.substr(pos+2, line.find_first_not_of("0123456789.", pos+2) - (pos+2)); } } } } this->do_read(); } void GCodeSender::send(const std::vector &lines, bool priority) { // append lines to queue { boost::lock_guard l(this->queue_mutex); for (std::vector::const_iterator line = lines.begin(); line != lines.end(); ++line) { if (priority) { this->priqueue.push_back(*line); } else { this->queue.push(*line); } } } this->send(); } void GCodeSender::send(const std::string &line, bool priority) { // append line to queue { boost::lock_guard l(this->queue_mutex); if (priority) { this->priqueue.push_back(line); } else { this->queue.push(line); } } this->send(); } void GCodeSender::send() { this->io.post(boost::bind(&GCodeSender::do_send, this)); } void GCodeSender::do_send() { boost::lock_guard l(this->queue_mutex); // printer is not connected or we're still waiting for the previous ack if (!this->can_send) return; std::string line; while (!this->priqueue.empty() || (!this->queue.empty() && !this->queue_paused)) { if (!this->priqueue.empty()) { line = this->priqueue.front(); this->priqueue.pop_front(); } else { line = this->queue.front(); this->queue.pop(); } // strip comments size_t comment_pos = line.find_first_of(';'); if (comment_pos != std::string::npos) line.erase(comment_pos, std::string::npos); boost::algorithm::trim(line); // if line is not empty, send it if (!line.empty()) break; // if line is empty, process next item in queue } if (line.empty()) return; // compute full line std::string full_line = "N" + boost::lexical_cast(this->sent) + " " + line; ++ this->sent; // calculate checksum int cs = 0; for (std::string::const_iterator it = full_line.begin(); it != full_line.end(); ++it) cs = cs ^ *it; // write line to device full_line += "*"; full_line += boost::lexical_cast(cs); full_line += "\n"; #ifdef DEBUG_SERIAL fs << ">> " << full_line << std::flush; #endif this->last_sent.push_back(line); this->can_send = false; if (this->last_sent.size() > KEEP_SENT) this->last_sent.erase(this->last_sent.begin(), this->last_sent.end() - KEEP_SENT); // we can't supply boost::asio::buffer(full_line) to async_write() because full_line is on the // stack and the buffer would lose its underlying storage causing memory corruption std::ostream os(&this->write_buffer); os << full_line; boost::asio::async_write(this->serial, this->write_buffer, boost::bind(&GCodeSender::on_write, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } void GCodeSender::on_write(const boost::system::error_code& error, size_t bytes_transferred) { this->set_error_status(false); if (error) { if (this->open) { this->do_close(); this->set_error_status(true); } return; } this->do_send(); } void GCodeSender::set_DTR(bool on) { #if defined(_WIN32) && !defined(__SYMBIAN32__) boost::asio::serial_port_service::native_handle_type handle = this->serial.native_handle(); if (on) EscapeCommFunction(handle, SETDTR); else EscapeCommFunction(handle, CLRDTR); #else int fd = this->serial.native_handle(); int status; ioctl(fd, TIOCMGET, &status); if (on) status |= TIOCM_DTR; else status &= ~TIOCM_DTR; ioctl(fd, TIOCMSET, &status); #endif } void GCodeSender::reset() { this->set_DTR(false); boost::this_thread::sleep(boost::posix_time::milliseconds(200)); this->set_DTR(true); boost::this_thread::sleep(boost::posix_time::milliseconds(200)); this->set_DTR(false); boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); { boost::lock_guard l(this->queue_mutex); this->can_send = true; } } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/GCodeSender.hpp000066400000000000000000000041421324354444700224660ustar00rootroot00000000000000#ifndef slic3r_GCodeSender_hpp_ #define slic3r_GCodeSender_hpp_ #include "libslic3r.h" #include #include #include #include #include #include namespace Slic3r { namespace asio = boost::asio; class GCodeSender : private boost::noncopyable { public: GCodeSender(); ~GCodeSender(); bool connect(std::string devname, unsigned int baud_rate); void send(const std::vector &lines, bool priority = false); void send(const std::string &s, bool priority = false); void disconnect(); bool error_status() const; bool is_connected() const; bool wait_connected(unsigned int timeout = 3) const; size_t queue_size() const; void pause_queue(); void resume_queue(); void purge_queue(bool priority = false); std::vector purge_log(); std::string getT() const; std::string getB() const; void set_DTR(bool on); void reset(); private: asio::io_service io; asio::serial_port serial; boost::thread background_thread; boost::asio::streambuf read_buffer, write_buffer; bool open; // whether the serial socket is connected bool connected; // whether the printer is online bool error; mutable boost::mutex error_mutex; // this mutex guards queue, priqueue, can_send, queue_paused, sent, last_sent mutable boost::mutex queue_mutex; std::queue queue; std::list priqueue; bool can_send; bool queue_paused; size_t sent; std::vector last_sent; // this mutex guards log, T, B mutable boost::mutex log_mutex; std::queue log; std::string T, B; void set_baud_rate(unsigned int baud_rate); void set_error_status(bool e); void do_send(); void on_write(const boost::system::error_code& error, size_t bytes_transferred); void do_close(); void do_read(); void on_read(const boost::system::error_code& error, size_t bytes_transferred); void send(); }; } // namespace Slic3r #endif /* slic3r_GCodeSender_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/GCodeTimeEstimator.cpp000066400000000000000000001060441324354444700240330ustar00rootroot00000000000000#include "GCodeTimeEstimator.hpp" #include #include #include static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; static const float MILLISEC_TO_SEC = 0.001f; static const float INCHES_TO_MM = 25.4f; static const float DEFAULT_FEEDRATE = 1500.0f; // from Prusa Firmware (Marlin_main.cpp) static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 static const float DEFAULT_RETRACT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 static const float DEFAULT_AXIS_MAX_FEEDRATE[] = { 500.0f, 500.0f, 12.0f, 120.0f }; // Prusa Firmware 1_75mm_MK2 static const float DEFAULT_AXIS_MAX_ACCELERATION[] = { 9000.0f, 9000.0f, 500.0f, 10000.0f }; // Prusa Firmware 1_75mm_MK2 static const float DEFAULT_AXIS_MAX_JERK[] = { 10.0f, 10.0f, 0.2f, 2.5f }; // from Prusa Firmware (Configuration.h) static const float DEFAULT_MINIMUM_FEEDRATE = 0.0f; // from Prusa Firmware (Configuration_adv.h) static const float DEFAULT_MINIMUM_TRAVEL_FEEDRATE = 0.0f; // from Prusa Firmware (Configuration_adv.h) static const float DEFAULT_EXTRUDE_FACTOR_OVERRIDE_PERCENTAGE = 1.0f; // 100 percent static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f; namespace Slic3r { void GCodeTimeEstimator::Feedrates::reset() { feedrate = 0.0f; safe_feedrate = 0.0f; ::memset(axis_feedrate, 0, Num_Axis * sizeof(float)); ::memset(abs_axis_feedrate, 0, Num_Axis * sizeof(float)); } float GCodeTimeEstimator::Block::Trapezoid::acceleration_time(float acceleration) const { return acceleration_time_from_distance(feedrate.entry, accelerate_until, acceleration); } float GCodeTimeEstimator::Block::Trapezoid::cruise_time() const { return (feedrate.cruise != 0.0f) ? cruise_distance() / feedrate.cruise : 0.0f; } float GCodeTimeEstimator::Block::Trapezoid::deceleration_time(float acceleration) const { return acceleration_time_from_distance(feedrate.cruise, (distance - decelerate_after), -acceleration); } float GCodeTimeEstimator::Block::Trapezoid::cruise_distance() const { return decelerate_after - accelerate_until; } float GCodeTimeEstimator::Block::Trapezoid::acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration) { return (acceleration != 0.0f) ? (speed_from_distance(initial_feedrate, distance, acceleration) - initial_feedrate) / acceleration : 0.0f; } float GCodeTimeEstimator::Block::Trapezoid::speed_from_distance(float initial_feedrate, float distance, float acceleration) { // to avoid invalid negative numbers due to numerical imprecision float value = std::max(0.0f, sqr(initial_feedrate) + 2.0f * acceleration * distance); return ::sqrt(value); } float GCodeTimeEstimator::Block::move_length() const { float length = ::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); return (length > 0.0f) ? length : std::abs(delta_pos[E]); } float GCodeTimeEstimator::Block::is_extruder_only_move() const { return (delta_pos[X] == 0.0f) && (delta_pos[Y] == 0.0f) && (delta_pos[Z] == 0.0f) && (delta_pos[E] != 0.0f); } float GCodeTimeEstimator::Block::is_travel_move() const { return delta_pos[E] == 0.0f; } float GCodeTimeEstimator::Block::acceleration_time() const { return trapezoid.acceleration_time(acceleration); } float GCodeTimeEstimator::Block::cruise_time() const { return trapezoid.cruise_time(); } float GCodeTimeEstimator::Block::deceleration_time() const { return trapezoid.deceleration_time(acceleration); } float GCodeTimeEstimator::Block::cruise_distance() const { return trapezoid.cruise_distance(); } void GCodeTimeEstimator::Block::calculate_trapezoid() { float distance = move_length(); trapezoid.distance = distance; trapezoid.feedrate = feedrate; float accelerate_distance = estimate_acceleration_distance(feedrate.entry, feedrate.cruise, acceleration); float decelerate_distance = estimate_acceleration_distance(feedrate.cruise, feedrate.exit, -acceleration); float cruise_distance = distance - accelerate_distance - decelerate_distance; // Not enough space to reach the nominal feedrate. // This means no cruising, and we'll have to use intersection_distance() to calculate when to abort acceleration // and start braking in order to reach the exit_feedrate exactly at the end of this block. if (cruise_distance < 0.0f) { accelerate_distance = clamp(0.0f, distance, intersection_distance(feedrate.entry, feedrate.exit, acceleration, distance)); cruise_distance = 0.0f; trapezoid.feedrate.cruise = Trapezoid::speed_from_distance(feedrate.entry, accelerate_distance, acceleration); } trapezoid.accelerate_until = accelerate_distance; trapezoid.decelerate_after = accelerate_distance + cruise_distance; } float GCodeTimeEstimator::Block::max_allowable_speed(float acceleration, float target_velocity, float distance) { // to avoid invalid negative numbers due to numerical imprecision float value = std::max(0.0f, sqr(target_velocity) - 2.0f * acceleration * distance); return ::sqrt(value); } float GCodeTimeEstimator::Block::estimate_acceleration_distance(float initial_rate, float target_rate, float acceleration) { return (acceleration == 0.0f) ? 0.0f : (sqr(target_rate) - sqr(initial_rate)) / (2.0f * acceleration); } float GCodeTimeEstimator::Block::intersection_distance(float initial_rate, float final_rate, float acceleration, float distance) { return (acceleration == 0.0f) ? 0.0f : (2.0f * acceleration * distance - sqr(initial_rate) + sqr(final_rate)) / (4.0f * acceleration); } GCodeTimeEstimator::GCodeTimeEstimator() { reset(); set_default(); } void GCodeTimeEstimator::calculate_time_from_text(const std::string& gcode) { reset(); _parser.parse_buffer(gcode, [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }); _calculate_time(); _reset_blocks(); _reset(); } void GCodeTimeEstimator::calculate_time_from_file(const std::string& file) { reset(); _parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); _calculate_time(); _reset_blocks(); _reset(); } void GCodeTimeEstimator::calculate_time_from_lines(const std::vector& gcode_lines) { reset(); auto action = [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }; for (const std::string& line : gcode_lines) _parser.parse_line(line, action); _calculate_time(); _reset_blocks(); _reset(); } void GCodeTimeEstimator::add_gcode_line(const std::string& gcode_line) { PROFILE_FUNC(); _parser.parse_line(gcode_line, [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }); } void GCodeTimeEstimator::add_gcode_block(const char *ptr) { PROFILE_FUNC(); GCodeReader::GCodeLine gline; auto action = [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }; for (; *ptr != 0;) { gline.reset(); ptr = _parser.parse_line(ptr, gline, action); } } void GCodeTimeEstimator::calculate_time() { PROFILE_FUNC(); _calculate_time(); _reset_blocks(); _reset(); } void GCodeTimeEstimator::set_axis_position(EAxis axis, float position) { _state.axis[axis].position = position; } void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec) { _state.axis[axis].max_feedrate = feedrate_mm_sec; } void GCodeTimeEstimator::set_axis_max_acceleration(EAxis axis, float acceleration) { _state.axis[axis].max_acceleration = acceleration; } void GCodeTimeEstimator::set_axis_max_jerk(EAxis axis, float jerk) { _state.axis[axis].max_jerk = jerk; } float GCodeTimeEstimator::get_axis_position(EAxis axis) const { return _state.axis[axis].position; } float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const { return _state.axis[axis].max_feedrate; } float GCodeTimeEstimator::get_axis_max_acceleration(EAxis axis) const { return _state.axis[axis].max_acceleration; } float GCodeTimeEstimator::get_axis_max_jerk(EAxis axis) const { return _state.axis[axis].max_jerk; } void GCodeTimeEstimator::set_feedrate(float feedrate_mm_sec) { _state.feedrate = feedrate_mm_sec; } float GCodeTimeEstimator::get_feedrate() const { return _state.feedrate; } void GCodeTimeEstimator::set_acceleration(float acceleration_mm_sec2) { _state.acceleration = acceleration_mm_sec2; } float GCodeTimeEstimator::get_acceleration() const { return _state.acceleration; } void GCodeTimeEstimator::set_retract_acceleration(float acceleration_mm_sec2) { _state.retract_acceleration = acceleration_mm_sec2; } float GCodeTimeEstimator::get_retract_acceleration() const { return _state.retract_acceleration; } void GCodeTimeEstimator::set_minimum_feedrate(float feedrate_mm_sec) { _state.minimum_feedrate = feedrate_mm_sec; } float GCodeTimeEstimator::get_minimum_feedrate() const { return _state.minimum_feedrate; } void GCodeTimeEstimator::set_minimum_travel_feedrate(float feedrate_mm_sec) { _state.minimum_travel_feedrate = feedrate_mm_sec; } float GCodeTimeEstimator::get_minimum_travel_feedrate() const { return _state.minimum_travel_feedrate; } void GCodeTimeEstimator::set_extrude_factor_override_percentage(float percentage) { _state.extrude_factor_override_percentage = percentage; } float GCodeTimeEstimator::get_extrude_factor_override_percentage() const { return _state.extrude_factor_override_percentage; } void GCodeTimeEstimator::set_dialect(GCodeFlavor dialect) { _state.dialect = dialect; } GCodeFlavor GCodeTimeEstimator::get_dialect() const { return _state.dialect; } void GCodeTimeEstimator::set_units(GCodeTimeEstimator::EUnits units) { _state.units = units; } GCodeTimeEstimator::EUnits GCodeTimeEstimator::get_units() const { return _state.units; } void GCodeTimeEstimator::set_positioning_xyz_type(GCodeTimeEstimator::EPositioningType type) { _state.positioning_xyz_type = type; } GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_positioning_xyz_type() const { return _state.positioning_xyz_type; } void GCodeTimeEstimator::set_positioning_e_type(GCodeTimeEstimator::EPositioningType type) { _state.positioning_e_type = type; } GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_positioning_e_type() const { return _state.positioning_e_type; } void GCodeTimeEstimator::add_additional_time(float timeSec) { _state.additional_time += timeSec; } void GCodeTimeEstimator::set_additional_time(float timeSec) { _state.additional_time = timeSec; } float GCodeTimeEstimator::get_additional_time() const { return _state.additional_time; } void GCodeTimeEstimator::set_default() { set_units(Millimeters); set_dialect(gcfRepRap); set_positioning_xyz_type(Absolute); set_positioning_e_type(Relative); set_feedrate(DEFAULT_FEEDRATE); set_acceleration(DEFAULT_ACCELERATION); set_retract_acceleration(DEFAULT_RETRACT_ACCELERATION); set_minimum_feedrate(DEFAULT_MINIMUM_FEEDRATE); set_minimum_travel_feedrate(DEFAULT_MINIMUM_TRAVEL_FEEDRATE); set_extrude_factor_override_percentage(DEFAULT_EXTRUDE_FACTOR_OVERRIDE_PERCENTAGE); for (unsigned char a = X; a < Num_Axis; ++a) { EAxis axis = (EAxis)a; set_axis_max_feedrate(axis, DEFAULT_AXIS_MAX_FEEDRATE[a]); set_axis_max_acceleration(axis, DEFAULT_AXIS_MAX_ACCELERATION[a]); set_axis_max_jerk(axis, DEFAULT_AXIS_MAX_JERK[a]); } } void GCodeTimeEstimator::reset() { _time = 0.0f; _reset_blocks(); _reset(); } float GCodeTimeEstimator::get_time() const { return _time; } std::string GCodeTimeEstimator::get_time_hms() const { float timeinsecs = get_time(); int hours = (int)(timeinsecs / 3600.0f); timeinsecs -= (float)hours * 3600.0f; int minutes = (int)(timeinsecs / 60.0f); timeinsecs -= (float)minutes * 60.0f; char buffer[64]; if (hours > 0) ::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)timeinsecs); else if (minutes > 0) ::sprintf(buffer, "%dm %ds", minutes, (int)timeinsecs); else ::sprintf(buffer, "%ds", (int)timeinsecs); return buffer; } void GCodeTimeEstimator::_reset() { _curr.reset(); _prev.reset(); set_axis_position(X, 0.0f); set_axis_position(Y, 0.0f); set_axis_position(Z, 0.0f); set_additional_time(0.0f); } void GCodeTimeEstimator::_reset_blocks() { _blocks.clear(); } void GCodeTimeEstimator::_calculate_time() { _forward_pass(); _reverse_pass(); _recalculate_trapezoids(); _time += get_additional_time(); for (const Block& block : _blocks) { _time += block.acceleration_time(); _time += block.cruise_time(); _time += block.deceleration_time(); } } void GCodeTimeEstimator::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line) { PROFILE_FUNC(); std::string cmd = line.cmd(); if (cmd.length() > 1) { switch (::toupper(cmd[0])) { case 'G': { switch (::atoi(&cmd[1])) { case 1: // Move { _processG1(line); break; } case 4: // Dwell { _processG4(line); break; } case 20: // Set Units to Inches { _processG20(line); break; } case 21: // Set Units to Millimeters { _processG21(line); break; } case 28: // Move to Origin (Home) { _processG28(line); break; } case 90: // Set to Absolute Positioning { _processG90(line); break; } case 91: // Set to Relative Positioning { _processG91(line); break; } case 92: // Set Position { _processG92(line); break; } } break; } case 'M': { switch (::atoi(&cmd[1])) { case 1: // Sleep or Conditional stop { _processM1(line); break; } case 82: // Set extruder to absolute mode { _processM82(line); break; } case 83: // Set extruder to relative mode { _processM83(line); break; } case 109: // Set Extruder Temperature and Wait { _processM109(line); break; } case 201: // Set max printing acceleration { _processM201(line); break; } case 203: // Set maximum feedrate { _processM203(line); break; } case 204: // Set default acceleration { _processM204(line); break; } case 205: // Advanced settings { _processM205(line); break; } case 221: // Set extrude factor override percentage { _processM221(line); break; } case 566: // Set allowable instantaneous speed change { _processM566(line); break; } } break; } } } } // Returns the new absolute position on the given axis in dependence of the given parameters float axis_absolute_position_from_G1_line(GCodeTimeEstimator::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeTimeEstimator::EUnits units, GCodeTimeEstimator::EPositioningType type, float current_absolute_position) { float lengthsScaleFactor = (units == GCodeTimeEstimator::Inches) ? INCHES_TO_MM : 1.0f; if (lineG1.has(Slic3r::Axis(axis))) { float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; return (type == GCodeTimeEstimator::Absolute) ? ret : current_absolute_position + ret; } else return current_absolute_position; } void GCodeTimeEstimator::_processG1(const GCodeReader::GCodeLine& line) { // updates axes positions from line EUnits units = get_units(); float new_pos[Num_Axis]; for (unsigned char a = X; a < Num_Axis; ++a) { new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, (a == E) ? get_positioning_e_type() : get_positioning_xyz_type(), get_axis_position((EAxis)a)); } // updates feedrate from line, if present if (line.has_f()) set_feedrate(std::max(line.f() * MMMIN_TO_MMSEC, get_minimum_feedrate())); // fills block data Block block; // calculates block movement deltas float max_abs_delta = 0.0f; for (unsigned char a = X; a < Num_Axis; ++a) { block.delta_pos[a] = new_pos[a] - get_axis_position((EAxis)a); max_abs_delta = std::max(max_abs_delta, std::abs(block.delta_pos[a])); } // is it a move ? if (max_abs_delta == 0.0f) return; // calculates block feedrate _curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate()); float distance = block.move_length(); float invDistance = 1.0f / distance; float min_feedrate_factor = 1.0f; for (unsigned char a = X; a < Num_Axis; ++a) { _curr.axis_feedrate[a] = _curr.feedrate * block.delta_pos[a] * invDistance; if (a == E) _curr.axis_feedrate[a] *= get_extrude_factor_override_percentage(); _curr.abs_axis_feedrate[a] = std::abs(_curr.axis_feedrate[a]); if (_curr.abs_axis_feedrate[a] > 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / _curr.abs_axis_feedrate[a]); } block.feedrate.cruise = min_feedrate_factor * _curr.feedrate; for (unsigned char a = X; a < Num_Axis; ++a) { _curr.axis_feedrate[a] *= min_feedrate_factor; _curr.abs_axis_feedrate[a] *= min_feedrate_factor; } // calculates block acceleration float acceleration = block.is_extruder_only_move() ? get_retract_acceleration() : get_acceleration(); for (unsigned char a = X; a < Num_Axis; ++a) { float axis_max_acceleration = get_axis_max_acceleration((EAxis)a); if (acceleration * std::abs(block.delta_pos[a]) * invDistance > axis_max_acceleration) acceleration = axis_max_acceleration; } block.acceleration = acceleration; // calculates block exit feedrate _curr.safe_feedrate = block.feedrate.cruise; for (unsigned char a = X; a < Num_Axis; ++a) { float axis_max_jerk = get_axis_max_jerk((EAxis)a); if (_curr.abs_axis_feedrate[a] > axis_max_jerk) _curr.safe_feedrate = std::min(_curr.safe_feedrate, axis_max_jerk); } block.feedrate.exit = _curr.safe_feedrate; // calculates block entry feedrate float vmax_junction = _curr.safe_feedrate; if (!_blocks.empty() && (_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) { bool prev_speed_larger = _prev.feedrate > block.feedrate.cruise; float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / _prev.feedrate) : (_prev.feedrate / block.feedrate.cruise); // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting. vmax_junction = prev_speed_larger ? block.feedrate.cruise : _prev.feedrate; float v_factor = 1.0f; bool limited = false; for (unsigned char a = X; a < Num_Axis; ++a) { // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop. float v_exit = _prev.axis_feedrate[a]; float v_entry = _curr.axis_feedrate[a]; if (prev_speed_larger) v_exit *= smaller_speed_factor; if (limited) { v_exit *= v_factor; v_entry *= v_factor; } // Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction. float jerk = (v_exit > v_entry) ? (((v_entry > 0.0f) || (v_exit < 0.0f)) ? // coasting (v_exit - v_entry) : // axis reversal std::max(v_exit, -v_entry)) : // v_exit <= v_entry (((v_entry < 0.0f) || (v_exit > 0.0f)) ? // coasting (v_entry - v_exit) : // axis reversal std::max(-v_exit, v_entry)); float axis_max_jerk = get_axis_max_jerk((EAxis)a); if (jerk > axis_max_jerk) { v_factor *= axis_max_jerk / jerk; limited = true; } } if (limited) vmax_junction *= v_factor; // Now the transition velocity is known, which maximizes the shared exit / entry velocity while // respecting the jerk factors, it may be possible, that applying separate safe exit / entry velocities will achieve faster prints. float vmax_junction_threshold = vmax_junction * 0.99f; // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start. if ((_prev.safe_feedrate > vmax_junction_threshold) && (_curr.safe_feedrate > vmax_junction_threshold)) vmax_junction = _curr.safe_feedrate; } float v_allowable = Block::max_allowable_speed(-acceleration, _curr.safe_feedrate, distance); block.feedrate.entry = std::min(vmax_junction, v_allowable); block.max_entry_speed = vmax_junction; block.flags.nominal_length = (block.feedrate.cruise <= v_allowable); block.flags.recalculate = true; block.safe_feedrate = _curr.safe_feedrate; // calculates block trapezoid block.calculate_trapezoid(); // updates previous _prev = _curr; // updates axis positions for (unsigned char a = X; a < Num_Axis; ++a) { set_axis_position((EAxis)a, new_pos[a]); } // adds block to blocks list _blocks.emplace_back(block); } void GCodeTimeEstimator::_processG4(const GCodeReader::GCodeLine& line) { GCodeFlavor dialect = get_dialect(); float value; if (line.has_value('P', value)) add_additional_time(value * MILLISEC_TO_SEC); // see: http://reprap.org/wiki/G-code#G4:_Dwell if ((dialect == gcfRepetier) || (dialect == gcfMarlin) || (dialect == gcfSmoothie) || (dialect == gcfRepRap)) { if (line.has_value('S', value)) add_additional_time(value); } _simulate_st_synchronize(); } void GCodeTimeEstimator::_processG20(const GCodeReader::GCodeLine& line) { set_units(Inches); } void GCodeTimeEstimator::_processG21(const GCodeReader::GCodeLine& line) { set_units(Millimeters); } void GCodeTimeEstimator::_processG28(const GCodeReader::GCodeLine& line) { // TODO } void GCodeTimeEstimator::_processG90(const GCodeReader::GCodeLine& line) { set_positioning_xyz_type(Absolute); } void GCodeTimeEstimator::_processG91(const GCodeReader::GCodeLine& line) { // TODO: THERE ARE DIALECT VARIANTS set_positioning_xyz_type(Relative); } void GCodeTimeEstimator::_processG92(const GCodeReader::GCodeLine& line) { float lengthsScaleFactor = (get_units() == Inches) ? INCHES_TO_MM : 1.0f; bool anyFound = false; if (line.has_x()) { set_axis_position(X, line.x() * lengthsScaleFactor); anyFound = true; } if (line.has_y()) { set_axis_position(Y, line.y() * lengthsScaleFactor); anyFound = true; } if (line.has_z()) { set_axis_position(Z, line.z() * lengthsScaleFactor); anyFound = true; } if (line.has_e()) { set_axis_position(E, line.e() * lengthsScaleFactor); anyFound = true; } else _simulate_st_synchronize(); if (!anyFound) { for (unsigned char a = X; a < Num_Axis; ++a) { set_axis_position((EAxis)a, 0.0f); } } } void GCodeTimeEstimator::_processM1(const GCodeReader::GCodeLine& line) { _simulate_st_synchronize(); } void GCodeTimeEstimator::_processM82(const GCodeReader::GCodeLine& line) { set_positioning_e_type(Absolute); } void GCodeTimeEstimator::_processM83(const GCodeReader::GCodeLine& line) { set_positioning_e_type(Relative); } void GCodeTimeEstimator::_processM109(const GCodeReader::GCodeLine& line) { // TODO } void GCodeTimeEstimator::_processM201(const GCodeReader::GCodeLine& line) { GCodeFlavor dialect = get_dialect(); // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration float factor = ((dialect != gcfRepRap) && (get_units() == GCodeTimeEstimator::Inches)) ? INCHES_TO_MM : 1.0f; if (line.has_x()) set_axis_max_acceleration(X, line.x() * factor); if (line.has_y()) set_axis_max_acceleration(Y, line.y() * factor); if (line.has_z()) set_axis_max_acceleration(Z, line.z() * factor); if (line.has_e()) set_axis_max_acceleration(E, line.e() * factor); } void GCodeTimeEstimator::_processM203(const GCodeReader::GCodeLine& line) { GCodeFlavor dialect = get_dialect(); // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate if (dialect == gcfRepetier) return; // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate float factor = (dialect == gcfMarlin) ? 1.0f : MMMIN_TO_MMSEC; if (line.has_x()) set_axis_max_feedrate(X, line.x() * factor); if (line.has_y()) set_axis_max_feedrate(Y, line.y() * factor); if (line.has_z()) set_axis_max_feedrate(Z, line.z() * factor); if (line.has_e()) set_axis_max_feedrate(E, line.e() * factor); } void GCodeTimeEstimator::_processM204(const GCodeReader::GCodeLine& line) { float value; if (line.has_value('S', value)) set_acceleration(value); if (line.has_value('T', value)) set_retract_acceleration(value); } void GCodeTimeEstimator::_processM205(const GCodeReader::GCodeLine& line) { if (line.has_x()) { float max_jerk = line.x(); set_axis_max_jerk(X, max_jerk); set_axis_max_jerk(Y, max_jerk); } if (line.has_y()) set_axis_max_jerk(Y, line.y()); if (line.has_z()) set_axis_max_jerk(Z, line.z()); if (line.has_e()) set_axis_max_jerk(E, line.e()); float value; if (line.has_value('S', value)) set_minimum_feedrate(value); if (line.has_value('T', value)) set_minimum_travel_feedrate(value); } void GCodeTimeEstimator::_processM221(const GCodeReader::GCodeLine& line) { float value_s; float value_t; if (line.has_value('S', value_s) && !line.has_value('T', value_t)) set_extrude_factor_override_percentage(value_s * 0.01f); } void GCodeTimeEstimator::_processM566(const GCodeReader::GCodeLine& line) { if (line.has_x()) set_axis_max_jerk(X, line.x() * MMMIN_TO_MMSEC); if (line.has_y()) set_axis_max_jerk(Y, line.y() * MMMIN_TO_MMSEC); if (line.has_z()) set_axis_max_jerk(Z, line.z() * MMMIN_TO_MMSEC); if (line.has_e()) set_axis_max_jerk(E, line.e() * MMMIN_TO_MMSEC); } void GCodeTimeEstimator::_simulate_st_synchronize() { _calculate_time(); _reset_blocks(); } void GCodeTimeEstimator::_forward_pass() { if (_blocks.size() > 1) { for (unsigned int i = 0; i < (unsigned int)_blocks.size() - 1; ++i) { _planner_forward_pass_kernel(_blocks[i], _blocks[i + 1]); } } } void GCodeTimeEstimator::_reverse_pass() { if (_blocks.size() > 1) { for (int i = (int)_blocks.size() - 1; i >= 1; --i) { _planner_reverse_pass_kernel(_blocks[i - 1], _blocks[i]); } } } void GCodeTimeEstimator::_planner_forward_pass_kernel(Block& prev, Block& curr) { // If the previous block is an acceleration block, but it is not long enough to complete the // full speed change within the block, we need to adjust the entry speed accordingly. Entry // speeds have already been reset, maximized, and reverse planned by reverse planner. // If nominal length is true, max junction speed is guaranteed to be reached. No need to recheck. if (!prev.flags.nominal_length) { if (prev.feedrate.entry < curr.feedrate.entry) { float entry_speed = std::min(curr.feedrate.entry, Block::max_allowable_speed(-prev.acceleration, prev.feedrate.entry, prev.move_length())); // Check for junction speed change if (curr.feedrate.entry != entry_speed) { curr.feedrate.entry = entry_speed; curr.flags.recalculate = true; } } } } void GCodeTimeEstimator::_planner_reverse_pass_kernel(Block& curr, Block& next) { // If entry speed is already at the maximum entry speed, no need to recheck. Block is cruising. // If not, block in state of acceleration or deceleration. Reset entry speed to maximum and // check for maximum allowable speed reductions to ensure maximum possible planned speed. if (curr.feedrate.entry != curr.max_entry_speed) { // If nominal length true, max junction speed is guaranteed to be reached. Only compute // for max allowable speed if block is decelerating and nominal length is false. if (!curr.flags.nominal_length && (curr.max_entry_speed > next.feedrate.entry)) curr.feedrate.entry = std::min(curr.max_entry_speed, Block::max_allowable_speed(-curr.acceleration, next.feedrate.entry, curr.move_length())); else curr.feedrate.entry = curr.max_entry_speed; curr.flags.recalculate = true; } } void GCodeTimeEstimator::_recalculate_trapezoids() { Block* curr = nullptr; Block* next = nullptr; for (Block& b : _blocks) { curr = next; next = &b; if (curr != nullptr) { // Recalculate if current block entry or exit junction speed has changed. if (curr->flags.recalculate || next->flags.recalculate) { // NOTE: Entry and exit factors always > 0 by all previous logic operations. Block block = *curr; block.feedrate.exit = next->feedrate.entry; block.calculate_trapezoid(); curr->trapezoid = block.trapezoid; curr->flags.recalculate = false; // Reset current only to ensure next trapezoid is computed } } } // Last/newest block in buffer. Always recalculated. if (next != nullptr) { Block block = *next; block.feedrate.exit = next->safe_feedrate; block.calculate_trapezoid(); next->trapezoid = block.trapezoid; next->flags.recalculate = false; } } } Slic3r-version_1.39.1/xs/src/libslic3r/GCodeTimeEstimator.hpp000066400000000000000000000261421324354444700240400ustar00rootroot00000000000000#ifndef slic3r_GCodeTimeEstimator_hpp_ #define slic3r_GCodeTimeEstimator_hpp_ #include "libslic3r.h" #include "PrintConfig.hpp" #include "GCodeReader.hpp" namespace Slic3r { // // Some of the algorithms used by class GCodeTimeEstimator were inpired by // Cura Engine's class TimeEstimateCalculator // https://github.com/Ultimaker/CuraEngine/blob/master/src/timeEstimate.h // class GCodeTimeEstimator { public: enum EUnits : unsigned char { Millimeters, Inches }; enum EAxis : unsigned char { X, Y, Z, E, Num_Axis }; enum EPositioningType : unsigned char { Absolute, Relative }; private: struct Axis { float position; // mm float max_feedrate; // mm/s float max_acceleration; // mm/s^2 float max_jerk; // mm/s }; struct Feedrates { float feedrate; // mm/s float axis_feedrate[Num_Axis]; // mm/s float abs_axis_feedrate[Num_Axis]; // mm/s float safe_feedrate; // mm/s void reset(); }; struct State { GCodeFlavor dialect; EUnits units; EPositioningType positioning_xyz_type; EPositioningType positioning_e_type; Axis axis[Num_Axis]; float feedrate; // mm/s float acceleration; // mm/s^2 float retract_acceleration; // mm/s^2 float additional_time; // s float minimum_feedrate; // mm/s float minimum_travel_feedrate; // mm/s float extrude_factor_override_percentage; }; public: struct Block { struct FeedrateProfile { float entry; // mm/s float cruise; // mm/s float exit; // mm/s }; struct Trapezoid { float distance; // mm float accelerate_until; // mm float decelerate_after; // mm FeedrateProfile feedrate; float acceleration_time(float acceleration) const; float cruise_time() const; float deceleration_time(float acceleration) const; float cruise_distance() const; // This function gives the time needed to accelerate from an initial speed to reach a final distance. static float acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration); // This function gives the final speed while accelerating at the given constant acceleration from the given initial speed along the given distance. static float speed_from_distance(float initial_feedrate, float distance, float acceleration); }; struct Flags { bool recalculate; bool nominal_length; }; Flags flags; float delta_pos[Num_Axis]; // mm float acceleration; // mm/s^2 float max_entry_speed; // mm/s float safe_feedrate; // mm/s FeedrateProfile feedrate; Trapezoid trapezoid; // Returns the length of the move covered by this block, in mm float move_length() const; // Returns true if this block is a retract/unretract move only float is_extruder_only_move() const; // Returns true if this block is a move with no extrusion float is_travel_move() const; // Returns the time spent accelerating toward cruise speed, in seconds float acceleration_time() const; // Returns the time spent at cruise speed, in seconds float cruise_time() const; // Returns the time spent decelerating from cruise speed, in seconds float deceleration_time() const; // Returns the distance covered at cruise speed, in mm float cruise_distance() const; // Calculates this block's trapezoid void calculate_trapezoid(); // Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the // acceleration within the allotted distance. static float max_allowable_speed(float acceleration, float target_velocity, float distance); // Calculates the distance (not time) it takes to accelerate from initial_rate to target_rate using the given acceleration: static float estimate_acceleration_distance(float initial_rate, float target_rate, float acceleration); // This function gives you the point at which you must start braking (at the rate of -acceleration) if // you started at speed initial_rate and accelerated until this point and want to end at the final_rate after // a total travel of distance. This can be used to compute the intersection point between acceleration and // deceleration in the cases where the trapezoid has no plateau (i.e. never reaches maximum speed) static float intersection_distance(float initial_rate, float final_rate, float acceleration, float distance); }; typedef std::vector BlocksList; private: GCodeReader _parser; State _state; Feedrates _curr; Feedrates _prev; BlocksList _blocks; float _time; // s public: GCodeTimeEstimator(); // Calculates the time estimate from the given gcode in string format void calculate_time_from_text(const std::string& gcode); // Calculates the time estimate from the gcode contained in the file with the given filename void calculate_time_from_file(const std::string& file); // Calculates the time estimate from the gcode contained in given list of gcode lines void calculate_time_from_lines(const std::vector& gcode_lines); // Adds the given gcode line void add_gcode_line(const std::string& gcode_line); void add_gcode_block(const char *ptr); void add_gcode_block(const std::string &str) { this->add_gcode_block(str.c_str()); } // Calculates the time estimate from the gcode lines added using add_gcode_line() void calculate_time(); // Set current position on the given axis with the given value void set_axis_position(EAxis axis, float position); void set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec); void set_axis_max_acceleration(EAxis axis, float acceleration); void set_axis_max_jerk(EAxis axis, float jerk); // Returns current position on the given axis float get_axis_position(EAxis axis) const; float get_axis_max_feedrate(EAxis axis) const; float get_axis_max_acceleration(EAxis axis) const; float get_axis_max_jerk(EAxis axis) const; void set_feedrate(float feedrate_mm_sec); float get_feedrate() const; void set_acceleration(float acceleration_mm_sec2); float get_acceleration() const; void set_retract_acceleration(float acceleration_mm_sec2); float get_retract_acceleration() const; void set_minimum_feedrate(float feedrate_mm_sec); float get_minimum_feedrate() const; void set_minimum_travel_feedrate(float feedrate_mm_sec); float get_minimum_travel_feedrate() const; void set_extrude_factor_override_percentage(float percentage); float get_extrude_factor_override_percentage() const; void set_dialect(GCodeFlavor dialect); GCodeFlavor get_dialect() const; void set_units(EUnits units); EUnits get_units() const; void set_positioning_xyz_type(EPositioningType type); EPositioningType get_positioning_xyz_type() const; void set_positioning_e_type(EPositioningType type); EPositioningType get_positioning_e_type() const; void add_additional_time(float timeSec); void set_additional_time(float timeSec); float get_additional_time() const; void set_default(); // Call this method before to start adding lines using add_gcode_line() when reusing an instance of GCodeTimeEstimator void reset(); // Returns the estimated time, in seconds float get_time() const; // Returns the estimated time, in format HHh MMm SSs std::string get_time_hms() const; private: void _reset(); void _reset_blocks(); // Calculates the time estimate void _calculate_time(); // Processes the given gcode line void _process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line); // Move void _processG1(const GCodeReader::GCodeLine& line); // Dwell void _processG4(const GCodeReader::GCodeLine& line); // Set Units to Inches void _processG20(const GCodeReader::GCodeLine& line); // Set Units to Millimeters void _processG21(const GCodeReader::GCodeLine& line); // Move to Origin (Home) void _processG28(const GCodeReader::GCodeLine& line); // Set to Absolute Positioning void _processG90(const GCodeReader::GCodeLine& line); // Set to Relative Positioning void _processG91(const GCodeReader::GCodeLine& line); // Set Position void _processG92(const GCodeReader::GCodeLine& line); // Sleep or Conditional stop void _processM1(const GCodeReader::GCodeLine& line); // Set extruder to absolute mode void _processM82(const GCodeReader::GCodeLine& line); // Set extruder to relative mode void _processM83(const GCodeReader::GCodeLine& line); // Set Extruder Temperature and Wait void _processM109(const GCodeReader::GCodeLine& line); // Set max printing acceleration void _processM201(const GCodeReader::GCodeLine& line); // Set maximum feedrate void _processM203(const GCodeReader::GCodeLine& line); // Set default acceleration void _processM204(const GCodeReader::GCodeLine& line); // Advanced settings void _processM205(const GCodeReader::GCodeLine& line); // Set extrude factor override percentage void _processM221(const GCodeReader::GCodeLine& line); // Set allowable instantaneous speed change void _processM566(const GCodeReader::GCodeLine& line); // Simulates firmware st_synchronize() call void _simulate_st_synchronize(); void _forward_pass(); void _reverse_pass(); void _planner_forward_pass_kernel(Block& prev, Block& curr); void _planner_reverse_pass_kernel(Block& curr, Block& next); void _recalculate_trapezoids(); }; } /* namespace Slic3r */ #endif /* slic3r_GCodeTimeEstimator_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/GCodeWriter.cpp000066400000000000000000000372641324354444700225300ustar00rootroot00000000000000#include "GCodeWriter.hpp" #include #include #include #include #include #define FLAVOR_IS(val) this->config.gcode_flavor == val #define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val #define COMMENT(comment) if (this->config.gcode_comments && !comment.empty()) gcode << " ; " << comment; #define PRECISION(val, precision) std::fixed << std::setprecision(precision) << val #define XYZF_NUM(val) PRECISION(val, 3) #define E_NUM(val) PRECISION(val, 5) namespace Slic3r { void GCodeWriter::apply_print_config(const PrintConfig &print_config) { this->config.apply(print_config, true); m_extrusion_axis = this->config.get_extrusion_axis(); this->m_single_extruder_multi_material = print_config.single_extruder_multi_material.value; } void GCodeWriter::set_extruders(const std::vector &extruder_ids) { m_extruders.clear(); m_extruders.reserve(extruder_ids.size()); for (unsigned int extruder_id : extruder_ids) m_extruders.emplace_back(Extruder(extruder_id, &this->config)); /* we enable support for multiple extruder if any extruder greater than 0 is used (even if prints only uses that one) since we need to output Tx commands first extruder has index 0 */ this->multiple_extruders = (*std::max_element(extruder_ids.begin(), extruder_ids.end())) > 0; } std::string GCodeWriter::preamble() { std::ostringstream gcode; if (FLAVOR_IS_NOT(gcfMakerWare)) { gcode << "G21 ; set units to millimeters\n"; gcode << "G90 ; use absolute coordinates\n"; } if (FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfMarlin) || FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepetier) || FLAVOR_IS(gcfSmoothie)) { if (this->config.use_relative_e_distances) { gcode << "M83 ; use relative distances for extrusion\n"; } else { gcode << "M82 ; use absolute distances for extrusion\n"; } gcode << this->reset_e(true); } return gcode.str(); } std::string GCodeWriter::postamble() const { std::ostringstream gcode; if (FLAVOR_IS(gcfMachinekit)) gcode << "M2 ; end of program\n"; return gcode.str(); } std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool) const { if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish))) return ""; std::string code, comment; if (wait && FLAVOR_IS_NOT(gcfTeacup)) { code = "M109"; comment = "set temperature and wait for it to be reached"; } else { code = "M104"; comment = "set temperature"; } std::ostringstream gcode; gcode << code << " "; if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { gcode << "P"; } else { gcode << "S"; } gcode << temperature; if (tool != -1 && ( (this->multiple_extruders && ! this->m_single_extruder_multi_material) || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) ) { gcode << " T" << tool; } gcode << " ; " << comment << "\n"; if (FLAVOR_IS(gcfTeacup) && wait) gcode << "M116 ; wait for temperature to be reached\n"; return gcode.str(); } std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait) { if (temperature == m_last_bed_temperature && (! wait || m_last_bed_temperature_reached)) return std::string(); m_last_bed_temperature = temperature; m_last_bed_temperature_reached = wait; std::string code, comment; if (wait && FLAVOR_IS_NOT(gcfTeacup)) { if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { code = "M109"; } else { code = "M190"; } comment = "set bed temperature and wait for it to be reached"; } else { code = "M140"; comment = "set bed temperature"; } std::ostringstream gcode; gcode << code << " "; if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { gcode << "P"; } else { gcode << "S"; } gcode << temperature << " ; " << comment << "\n"; if (FLAVOR_IS(gcfTeacup) && wait) gcode << "M116 ; wait for bed temperature to be reached\n"; return gcode.str(); } std::string GCodeWriter::set_fan(unsigned int speed, bool dont_save) { std::ostringstream gcode; if (m_last_fan_speed != speed || dont_save) { if (!dont_save) m_last_fan_speed = speed; if (speed == 0) { if (FLAVOR_IS(gcfTeacup)) { gcode << "M106 S0"; } else if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { gcode << "M127"; } else { gcode << "M107"; } if (this->config.gcode_comments) gcode << " ; disable fan"; gcode << "\n"; } else { if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { gcode << "M126"; } else { gcode << "M106 "; if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { gcode << "P"; } else { gcode << "S"; } gcode << (255.0 * speed / 100.0); } if (this->config.gcode_comments) gcode << " ; enable fan"; gcode << "\n"; } } return gcode.str(); } std::string GCodeWriter::set_acceleration(unsigned int acceleration) { if (acceleration == 0 || acceleration == m_last_acceleration) return std::string(); m_last_acceleration = acceleration; std::ostringstream gcode; if (FLAVOR_IS(gcfRepetier)) { // M201: Set max printing acceleration gcode << "M201 X" << acceleration << " Y" << acceleration; if (this->config.gcode_comments) gcode << " ; adjust acceleration"; gcode << "\n"; // M202: Set max travel acceleration gcode << "M202 X" << acceleration << " Y" << acceleration; } else { // M204: Set default acceleration gcode << "M204 S" << acceleration; } if (this->config.gcode_comments) gcode << " ; adjust acceleration"; gcode << "\n"; return gcode.str(); } std::string GCodeWriter::reset_e(bool force) { if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) return ""; if (m_extruder != nullptr) { if (m_extruder->E() == 0. && ! force) return ""; m_extruder->reset_E(); } if (! m_extrusion_axis.empty() && ! this->config.use_relative_e_distances) { std::ostringstream gcode; gcode << "G92 " << m_extrusion_axis << "0"; if (this->config.gcode_comments) gcode << " ; reset extrusion distance"; gcode << "\n"; return gcode.str(); } else { return ""; } } std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, bool allow_100) const { if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish)) return ""; unsigned int percent = (unsigned int)floor(100.0 * num / tot + 0.5); if (!allow_100) percent = std::min(percent, (unsigned int)99); std::ostringstream gcode; gcode << "M73 P" << percent; if (this->config.gcode_comments) gcode << " ; update progress"; gcode << "\n"; return gcode.str(); } std::string GCodeWriter::toolchange_prefix() const { return FLAVOR_IS(gcfMakerWare) ? "M135 T" : FLAVOR_IS(gcfSailfish) ? "M108 T" : "T"; } std::string GCodeWriter::toolchange(unsigned int extruder_id) { // set the new extruder auto it_extruder = std::lower_bound(m_extruders.begin(), m_extruders.end(), Extruder::key(extruder_id)); assert(it_extruder != m_extruders.end()); m_extruder = const_cast(&*it_extruder); // return the toolchange command // if we are running a single-extruder setup, just set the extruder and return nothing std::ostringstream gcode; if (this->multiple_extruders) { gcode << this->toolchange_prefix() << extruder_id; if (this->config.gcode_comments) gcode << " ; change extruder"; gcode << "\n"; gcode << this->reset_e(true); } return gcode.str(); } std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker) const { assert(F > 0.); assert(F < 100000.); std::ostringstream gcode; gcode << "G1 F" << F; COMMENT(comment); gcode << cooling_marker; gcode << "\n"; return gcode.str(); } std::string GCodeWriter::travel_to_xy(const Pointf &point, const std::string &comment) { m_pos.x = point.x; m_pos.y = point.y; std::ostringstream gcode; gcode << "G1 X" << XYZF_NUM(point.x) << " Y" << XYZF_NUM(point.y) << " F" << XYZF_NUM(this->config.travel_speed.value * 60.0); COMMENT(comment); gcode << "\n"; return gcode.str(); } std::string GCodeWriter::travel_to_xyz(const Pointf3 &point, const std::string &comment) { /* If target Z is lower than current Z but higher than nominal Z we don't perform the Z move but we only move in the XY plane and adjust the nominal Z by reducing the lift amount that will be used for unlift. */ if (!this->will_move_z(point.z)) { double nominal_z = m_pos.z - m_lifted; m_lifted = m_lifted - (point.z - nominal_z); return this->travel_to_xy(point); } /* In all the other cases, we perform an actual XYZ move and cancel the lift. */ m_lifted = 0; m_pos = point; std::ostringstream gcode; gcode << "G1 X" << XYZF_NUM(point.x) << " Y" << XYZF_NUM(point.y) << " Z" << XYZF_NUM(point.z) << " F" << XYZF_NUM(this->config.travel_speed.value * 60.0); COMMENT(comment); gcode << "\n"; return gcode.str(); } std::string GCodeWriter::travel_to_z(double z, const std::string &comment) { /* If target Z is lower than current Z but higher than nominal Z we don't perform the move but we only adjust the nominal Z by reducing the lift amount that will be used for unlift. */ if (!this->will_move_z(z)) { double nominal_z = m_pos.z - m_lifted; m_lifted = m_lifted - (z - nominal_z); return ""; } /* In all the other cases, we perform an actual Z move and cancel the lift. */ m_lifted = 0; return this->_travel_to_z(z, comment); } std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) { m_pos.z = z; std::ostringstream gcode; gcode << "G1 Z" << XYZF_NUM(z) << " F" << XYZF_NUM(this->config.travel_speed.value * 60.0); COMMENT(comment); gcode << "\n"; return gcode.str(); } bool GCodeWriter::will_move_z(double z) const { /* If target Z is lower than current Z but higher than nominal Z we don't perform an actual Z move. */ if (m_lifted > 0) { double nominal_z = m_pos.z - m_lifted; if (z >= nominal_z && z <= m_pos.z) return false; } return true; } std::string GCodeWriter::extrude_to_xy(const Pointf &point, double dE, const std::string &comment) { m_pos.x = point.x; m_pos.y = point.y; m_extruder->extrude(dE); std::ostringstream gcode; gcode << "G1 X" << XYZF_NUM(point.x) << " Y" << XYZF_NUM(point.y) << " " << m_extrusion_axis << E_NUM(m_extruder->E()); COMMENT(comment); gcode << "\n"; return gcode.str(); } std::string GCodeWriter::extrude_to_xyz(const Pointf3 &point, double dE, const std::string &comment) { m_pos = point; m_lifted = 0; m_extruder->extrude(dE); std::ostringstream gcode; gcode << "G1 X" << XYZF_NUM(point.x) << " Y" << XYZF_NUM(point.y) << " Z" << XYZF_NUM(point.z) << " " << m_extrusion_axis << E_NUM(m_extruder->E()); COMMENT(comment); gcode << "\n"; return gcode.str(); } std::string GCodeWriter::retract(bool before_wipe) { double factor = before_wipe ? m_extruder->retract_before_wipe() : 1.; assert(factor >= 0. && factor <= 1. + EPSILON); return this->_retract( factor * m_extruder->retract_length(), factor * m_extruder->retract_restart_extra(), "retract" ); } std::string GCodeWriter::retract_for_toolchange(bool before_wipe) { double factor = before_wipe ? m_extruder->retract_before_wipe() : 1.; assert(factor >= 0. && factor <= 1. + EPSILON); return this->_retract( factor * m_extruder->retract_length_toolchange(), factor * m_extruder->retract_restart_extra_toolchange(), "retract for toolchange" ); } std::string GCodeWriter::_retract(double length, double restart_extra, const std::string &comment) { std::ostringstream gcode; /* If firmware retraction is enabled, we use a fake value of 1 since we ignore the actual configured retract_length which might be 0, in which case the retraction logic gets skipped. */ if (this->config.use_firmware_retraction) length = 1; // If we use volumetric E values we turn lengths into volumes */ if (this->config.use_volumetric_e) { double d = m_extruder->filament_diameter(); double area = d * d * PI/4; length = length * area; restart_extra = restart_extra * area; } double dE = m_extruder->retract(length, restart_extra); if (dE != 0) { if (this->config.use_firmware_retraction) { if (FLAVOR_IS(gcfMachinekit)) gcode << "G22 ; retract\n"; else gcode << "G10 ; retract\n"; } else { gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E()) << " F" << float(m_extruder->retract_speed() * 60.); COMMENT(comment); gcode << "\n"; } } if (FLAVOR_IS(gcfMakerWare)) gcode << "M103 ; extruder off\n"; return gcode.str(); } std::string GCodeWriter::unretract() { std::ostringstream gcode; if (FLAVOR_IS(gcfMakerWare)) gcode << "M101 ; extruder on\n"; double dE = m_extruder->unretract(); if (dE != 0) { if (this->config.use_firmware_retraction) { if (FLAVOR_IS(gcfMachinekit)) gcode << "G23 ; unretract\n"; else gcode << "G11 ; unretract\n"; gcode << this->reset_e(); } else { // use G1 instead of G0 because G0 will blend the restart with the previous travel move gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E()) << " F" << float(m_extruder->deretract_speed() * 60.); if (this->config.gcode_comments) gcode << " ; unretract"; gcode << "\n"; } } return gcode.str(); } /* If this method is called more than once before calling unlift(), it will not perform subsequent lifts, even if Z was raised manually (i.e. with travel_to_z()) and thus _lifted was reduced. */ std::string GCodeWriter::lift() { // check whether the above/below conditions are met double target_lift = 0; { double above = this->config.retract_lift_above.get_at(m_extruder->id()); double below = this->config.retract_lift_below.get_at(m_extruder->id()); if (m_pos.z >= above && (below == 0 || m_pos.z <= below)) target_lift = this->config.retract_lift.get_at(m_extruder->id()); } if (m_lifted == 0 && target_lift > 0) { m_lifted = target_lift; return this->_travel_to_z(m_pos.z + target_lift, "lift Z"); } return ""; } std::string GCodeWriter::unlift() { std::string gcode; if (m_lifted > 0) { gcode += this->_travel_to_z(m_pos.z - m_lifted, "restore layer Z"); m_lifted = 0; } return gcode; } } Slic3r-version_1.39.1/xs/src/libslic3r/GCodeWriter.hpp000066400000000000000000000077511324354444700225330ustar00rootroot00000000000000#ifndef slic3r_GCodeWriter_hpp_ #define slic3r_GCodeWriter_hpp_ #include "libslic3r.h" #include #include "Extruder.hpp" #include "Point.hpp" #include "PrintConfig.hpp" #include "GCode/CoolingBuffer.hpp" namespace Slic3r { class GCodeWriter { public: GCodeConfig config; bool multiple_extruders; GCodeWriter() : multiple_extruders(false), m_extrusion_axis("E"), m_extruder(nullptr), m_single_extruder_multi_material(false), m_last_acceleration(0), m_last_fan_speed(0), m_last_bed_temperature(0), m_last_bed_temperature_reached(true), m_lifted(0) {} Extruder* extruder() { return m_extruder; } const Extruder* extruder() const { return m_extruder; } std::string extrusion_axis() const { return m_extrusion_axis; } void apply_print_config(const PrintConfig &print_config); // Extruders are expected to be sorted in an increasing order. void set_extruders(const std::vector &extruder_ids); const std::vector& extruders() const { return m_extruders; } std::vector extruder_ids() const { std::vector out; out.reserve(m_extruders.size()); for (const Extruder &e : m_extruders) out.push_back(e.id()); return out; } std::string preamble(); std::string postamble() const; std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1) const; std::string set_bed_temperature(unsigned int temperature, bool wait = false); std::string set_fan(unsigned int speed, bool dont_save = false); std::string set_acceleration(unsigned int acceleration); std::string reset_e(bool force = false); std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const; // return false if this extruder was already selected bool need_toolchange(unsigned int extruder_id) const { return m_extruder == nullptr || m_extruder->id() != extruder_id; } std::string set_extruder(unsigned int extruder_id) { return this->need_toolchange(extruder_id) ? this->toolchange(extruder_id) : ""; } // Prefix of the toolchange G-code line, to be used by the CoolingBuffer to separate sections of the G-code // printed with the same extruder. std::string toolchange_prefix() const; std::string toolchange(unsigned int extruder_id); std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const; std::string travel_to_xy(const Pointf &point, const std::string &comment = std::string()); std::string travel_to_xyz(const Pointf3 &point, const std::string &comment = std::string()); std::string travel_to_z(double z, const std::string &comment = std::string()); bool will_move_z(double z) const; std::string extrude_to_xy(const Pointf &point, double dE, const std::string &comment = std::string()); std::string extrude_to_xyz(const Pointf3 &point, double dE, const std::string &comment = std::string()); std::string retract(bool before_wipe = false); std::string retract_for_toolchange(bool before_wipe = false); std::string unretract(); std::string lift(); std::string unlift(); Pointf3 get_position() const { return m_pos; } private: std::vector m_extruders; std::string m_extrusion_axis; bool m_single_extruder_multi_material; Extruder* m_extruder; unsigned int m_last_acceleration; unsigned int m_last_fan_speed; unsigned int m_last_bed_temperature; bool m_last_bed_temperature_reached; double m_lifted; Pointf3 m_pos; std::string _travel_to_z(double z, const std::string &comment); std::string _retract(double length, double restart_extra, const std::string &comment); }; } /* namespace Slic3r */ #endif /* slic3r_GCodeWriter_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/Geometry.cpp000066400000000000000000001254371324354444700221450ustar00rootroot00000000000000#include "Geometry.hpp" #include "ClipperUtils.hpp" #include "ExPolygon.hpp" #include "Line.hpp" #include "PolylineCollection.hpp" #include "clipper.hpp" #include #include #include #include #include #include #include #include #include #ifdef SLIC3R_DEBUG #include "SVG.hpp" #endif #ifdef SLIC3R_DEBUG namespace boost { namespace polygon { // The following code for the visualization of the boost Voronoi diagram is based on: // // Boost.Polygon library voronoi_graphic_utils.hpp header file // Copyright Andrii Sydorchuk 2010-2012. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) template class voronoi_visual_utils { public: // Discretize parabolic Voronoi edge. // Parabolic Voronoi edges are always formed by one point and one segment // from the initial input set. // // Args: // point: input point. // segment: input segment. // max_dist: maximum discretization distance. // discretization: point discretization of the given Voronoi edge. // // Template arguments: // InCT: coordinate type of the input geometries (usually integer). // Point: point type, should model point concept. // Segment: segment type, should model segment concept. // // Important: // discretization should contain both edge endpoints initially. template class Point, template class Segment> static typename enable_if< typename gtl_and< typename gtl_if< typename is_point_concept< typename geometry_concept< Point >::type >::type >::type, typename gtl_if< typename is_segment_concept< typename geometry_concept< Segment >::type >::type >::type >::type, void >::type discretize( const Point& point, const Segment& segment, const CT max_dist, std::vector< Point >* discretization) { // Apply the linear transformation to move start point of the segment to // the point with coordinates (0, 0) and the direction of the segment to // coincide the positive direction of the x-axis. CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment))); CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment))); CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y; // Compute x-coordinates of the endpoints of the edge // in the transformed space. CT projection_start = sqr_segment_length * get_point_projection((*discretization)[0], segment); CT projection_end = sqr_segment_length * get_point_projection((*discretization)[1], segment); // Compute parabola parameters in the transformed space. // Parabola has next representation: // f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y). CT point_vec_x = cast(x(point)) - cast(x(low(segment))); CT point_vec_y = cast(y(point)) - cast(y(low(segment))); CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y; CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x; // Save the last point. Point last_point = (*discretization)[1]; discretization->pop_back(); // Use stack to avoid recursion. std::stack point_stack; point_stack.push(projection_end); CT cur_x = projection_start; CT cur_y = parabola_y(cur_x, rot_x, rot_y); // Adjust max_dist parameter in the transformed space. const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length; while (!point_stack.empty()) { CT new_x = point_stack.top(); CT new_y = parabola_y(new_x, rot_x, rot_y); // Compute coordinates of the point of the parabola that is // furthest from the current line segment. CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x; CT mid_y = parabola_y(mid_x, rot_x, rot_y); // Compute maximum distance between the given parabolic arc // and line segment that discretize it. CT dist = (new_y - cur_y) * (mid_x - cur_x) - (new_x - cur_x) * (mid_y - cur_y); dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) + (new_x - cur_x) * (new_x - cur_x)); if (dist <= max_dist_transformed) { // Distance between parabola and line segment is less than max_dist. point_stack.pop(); CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) / sqr_segment_length + cast(x(low(segment))); CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) / sqr_segment_length + cast(y(low(segment))); discretization->push_back(Point(inter_x, inter_y)); cur_x = new_x; cur_y = new_y; } else { point_stack.push(mid_x); } } // Update last point. discretization->back() = last_point; } private: // Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b). static CT parabola_y(CT x, CT a, CT b) { return ((x - a) * (x - a) + b * b) / (b + b); } // Get normalized length of the distance between: // 1) point projection onto the segment // 2) start point of the segment // Return this length divided by the segment length. This is made to avoid // sqrt computation during transformation from the initial space to the // transformed one and vice versa. The assumption is made that projection of // the point lies between the start-point and endpoint of the segment. template class Point, template class Segment> static typename enable_if< typename gtl_and< typename gtl_if< typename is_point_concept< typename geometry_concept< Point >::type >::type >::type, typename gtl_if< typename is_segment_concept< typename geometry_concept< Segment >::type >::type >::type >::type, CT >::type get_point_projection( const Point& point, const Segment& segment) { CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment))); CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment))); CT point_vec_x = x(point) - cast(x(low(segment))); CT point_vec_y = y(point) - cast(y(low(segment))); CT sqr_segment_length = segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y; CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y; return vec_dot / sqr_segment_length; } template static CT cast(const InCT& value) { return static_cast(value); } }; } } // namespace boost::polygon #endif using namespace boost::polygon; // provides also high() and low() namespace Slic3r { namespace Geometry { static bool sort_points (Point a, Point b) { return (a.x < b.x) || (a.x == b.x && a.y < b.y); } /* This implementation is based on Andrew's monotone chain 2D convex hull algorithm */ Polygon convex_hull(Points points) { assert(points.size() >= 3); // sort input points std::sort(points.begin(), points.end(), sort_points); int n = points.size(), k = 0; Polygon hull; if (n >= 3) { hull.points.resize(2*n); // Build lower hull for (int i = 0; i < n; i++) { while (k >= 2 && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--; hull.points[k++] = points[i]; } // Build upper hull for (int i = n-2, t = k+1; i >= 0; i--) { while (k >= t && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--; hull.points[k++] = points[i]; } hull.points.resize(k); assert( hull.points.front().coincides_with(hull.points.back()) ); hull.points.pop_back(); } return hull; } Polygon convex_hull(const Polygons &polygons) { Points pp; for (Polygons::const_iterator p = polygons.begin(); p != polygons.end(); ++p) { pp.insert(pp.end(), p->points.begin(), p->points.end()); } return convex_hull(pp); } /* accepts an arrayref of points and returns a list of indices according to a nearest-neighbor walk */ void chained_path(const Points &points, std::vector &retval, Point start_near) { PointConstPtrs my_points; std::map indices; my_points.reserve(points.size()); for (Points::const_iterator it = points.begin(); it != points.end(); ++it) { my_points.push_back(&*it); indices[&*it] = it - points.begin(); } retval.reserve(points.size()); while (!my_points.empty()) { Points::size_type idx = start_near.nearest_point_index(my_points); start_near = *my_points[idx]; retval.push_back(indices[ my_points[idx] ]); my_points.erase(my_points.begin() + idx); } } void chained_path(const Points &points, std::vector &retval) { if (points.empty()) return; // can't call front() on empty vector chained_path(points, retval, points.front()); } /* retval and items must be different containers */ template void chained_path_items(Points &points, T &items, T &retval) { std::vector indices; chained_path(points, indices); for (std::vector::const_iterator it = indices.begin(); it != indices.end(); ++it) retval.push_back(items[*it]); } template void chained_path_items(Points &points, ClipperLib::PolyNodes &items, ClipperLib::PolyNodes &retval); bool directions_parallel(double angle1, double angle2, double max_diff) { double diff = fabs(angle1 - angle2); max_diff += EPSILON; return diff < max_diff || fabs(diff - PI) < max_diff; } template bool contains(const std::vector &vector, const Point &point) { for (typename std::vector::const_iterator it = vector.begin(); it != vector.end(); ++it) { if (it->contains(point)) return true; } return false; } template bool contains(const ExPolygons &vector, const Point &point); double rad2deg(double angle) { return angle / PI * 180.0; } double rad2deg_dir(double angle) { angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0); if (angle < 0) angle += PI; return rad2deg(angle); } void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval) { Polygons pp; for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) { Polygon p = *it; p.points.push_back(p.points.front()); p.points = MultiPoint::_douglas_peucker(p.points, tolerance); p.points.pop_back(); pp.push_back(p); } *retval = Slic3r::simplify_polygons(pp); } double linint(double value, double oldmin, double oldmax, double newmin, double newmax) { return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin; } #if 0 // Point with a weight, by which the points are sorted. // If the points have the same weight, sort them lexicographically by their positions. struct ArrangeItem { ArrangeItem() {} Pointf pos; coordf_t weight; bool operator<(const ArrangeItem &other) const { return weight < other.weight || ((weight == other.weight) && (pos.y < other.pos.y || (pos.y == other.pos.y && pos.x < other.pos.x))); } }; Pointfs arrange(size_t num_parts, const Pointf &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box) { // Use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm. const Pointf cell_size(part_size.x + gap, part_size.y + gap); const BoundingBoxf bed_bbox = (bed_bounding_box != NULL && bed_bounding_box->defined) ? *bed_bounding_box : // Bogus bed size, large enough not to trigger the unsufficient bed size error. BoundingBoxf( Pointf(0, 0), Pointf(cell_size.x * num_parts, cell_size.y * num_parts)); // This is how many cells we have available into which to put parts. size_t cellw = size_t(floor((bed_bbox.size().x + gap) / cell_size.x)); size_t cellh = size_t(floor((bed_bbox.size().y + gap) / cell_size.y)); if (num_parts > cellw * cellh) CONFESS(PRINTF_ZU " parts won't fit in your print area!\n", num_parts); // Get a bounding box of cellw x cellh cells, centered at the center of the bed. Pointf cells_size(cellw * cell_size.x - gap, cellh * cell_size.y - gap); Pointf cells_offset(bed_bbox.center() - 0.5 * cells_size); BoundingBoxf cells_bb(cells_offset, cells_size + cells_offset); // List of cells, sorted by distance from center. std::vector cellsorder(cellw * cellh, ArrangeItem()); for (size_t j = 0; j < cellh; ++ j) { // Center of the jth row on the bed. coordf_t cy = linint(j + 0.5, 0., double(cellh), cells_bb.min.y, cells_bb.max.y); // Offset from the bed center. coordf_t yd = cells_bb.center().y - cy; for (size_t i = 0; i < cellw; ++ i) { // Center of the ith column on the bed. coordf_t cx = linint(i + 0.5, 0., double(cellw), cells_bb.min.x, cells_bb.max.x); // Offset from the bed center. coordf_t xd = cells_bb.center().x - cx; // Cell with a distance from the bed center. ArrangeItem &ci = cellsorder[j * cellw + i]; // Cell center ci.pos.x = cx; ci.pos.y = cy; // Square distance of the cell center to the bed center. ci.weight = xd * xd + yd * yd; } } // Sort the cells lexicographically by their distances to the bed center and left to right / bttom to top. std::sort(cellsorder.begin(), cellsorder.end()); cellsorder.erase(cellsorder.begin() + num_parts, cellsorder.end()); // Return the (left,top) corners of the cells. Pointfs positions; positions.reserve(num_parts); for (std::vector::const_iterator it = cellsorder.begin(); it != cellsorder.end(); ++ it) positions.push_back(Pointf(it->pos.x - 0.5 * part_size.x, it->pos.y - 0.5 * part_size.y)); return positions; } #else class ArrangeItem { public: Pointf pos; size_t index_x, index_y; coordf_t dist; }; class ArrangeItemIndex { public: coordf_t index; ArrangeItem item; ArrangeItemIndex(coordf_t _index, ArrangeItem _item) : index(_index), item(_item) {}; }; bool arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const BoundingBoxf* bb, Pointfs &positions) { positions.clear(); Pointf part = part_size; // use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm part.x += dist; part.y += dist; Pointf area; if (bb != NULL && bb->defined) { area = bb->size(); } else { // bogus area size, large enough not to trigger the error below area.x = part.x * total_parts; area.y = part.y * total_parts; } // this is how many cells we have available into which to put parts size_t cellw = floor((area.x + dist) / part.x); size_t cellh = floor((area.y + dist) / part.y); if (total_parts > (cellw * cellh)) return false; // total space used by cells Pointf cells(cellw * part.x, cellh * part.y); // bounding box of total space used by cells BoundingBoxf cells_bb; cells_bb.merge(Pointf(0,0)); // min cells_bb.merge(cells); // max // center bounding box to area cells_bb.translate( (area.x - cells.x) / 2, (area.y - cells.y) / 2 ); // list of cells, sorted by distance from center std::vector cellsorder; // work out distance for all cells, sort into list for (size_t i = 0; i <= cellw-1; ++i) { for (size_t j = 0; j <= cellh-1; ++j) { coordf_t cx = linint(i + 0.5, 0, cellw, cells_bb.min.x, cells_bb.max.x); coordf_t cy = linint(j + 0.5, 0, cellh, cells_bb.min.y, cells_bb.max.y); coordf_t xd = fabs((area.x / 2) - cx); coordf_t yd = fabs((area.y / 2) - cy); ArrangeItem c; c.pos.x = cx; c.pos.y = cy; c.index_x = i; c.index_y = j; c.dist = xd * xd + yd * yd - fabs((cellw / 2) - (i + 0.5)); // binary insertion sort { coordf_t index = c.dist; size_t low = 0; size_t high = cellsorder.size(); while (low < high) { size_t mid = (low + ((high - low) / 2)) | 0; coordf_t midval = cellsorder[mid].index; if (midval < index) { low = mid + 1; } else if (midval > index) { high = mid; } else { cellsorder.insert(cellsorder.begin() + mid, ArrangeItemIndex(index, c)); goto ENDSORT; } } cellsorder.insert(cellsorder.begin() + low, ArrangeItemIndex(index, c)); } ENDSORT: ; } } // the extents of cells actually used by objects coordf_t lx = 0; coordf_t ty = 0; coordf_t rx = 0; coordf_t by = 0; // now find cells actually used by objects, map out the extents so we can position correctly for (size_t i = 1; i <= total_parts; ++i) { ArrangeItemIndex c = cellsorder[i - 1]; coordf_t cx = c.item.index_x; coordf_t cy = c.item.index_y; if (i == 1) { lx = rx = cx; ty = by = cy; } else { if (cx > rx) rx = cx; if (cx < lx) lx = cx; if (cy > by) by = cy; if (cy < ty) ty = cy; } } // now we actually place objects into cells, positioned such that the left and bottom borders are at 0 for (size_t i = 1; i <= total_parts; ++i) { ArrangeItemIndex c = cellsorder.front(); cellsorder.erase(cellsorder.begin()); coordf_t cx = c.item.index_x - lx; coordf_t cy = c.item.index_y - ty; positions.push_back(Pointf(cx * part.x, cy * part.y)); } if (bb != NULL && bb->defined) { for (Pointfs::iterator p = positions.begin(); p != positions.end(); ++p) { p->x += bb->min.x; p->y += bb->min.y; } } return true; } #endif #ifdef SLIC3R_DEBUG // The following code for the visualization of the boost Voronoi diagram is based on: // // Boost.Polygon library voronoi_visualizer.cpp file // Copyright Andrii Sydorchuk 2010-2012. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) namespace Voronoi { namespace Internal { typedef double coordinate_type; typedef boost::polygon::point_data point_type; typedef boost::polygon::segment_data segment_type; typedef boost::polygon::rectangle_data rect_type; // typedef voronoi_builder VB; typedef boost::polygon::voronoi_diagram VD; typedef VD::cell_type cell_type; typedef VD::cell_type::source_index_type source_index_type; typedef VD::cell_type::source_category_type source_category_type; typedef VD::edge_type edge_type; typedef VD::cell_container_type cell_container_type; typedef VD::cell_container_type vertex_container_type; typedef VD::edge_container_type edge_container_type; typedef VD::const_cell_iterator const_cell_iterator; typedef VD::const_vertex_iterator const_vertex_iterator; typedef VD::const_edge_iterator const_edge_iterator; static const std::size_t EXTERNAL_COLOR = 1; inline void color_exterior(const VD::edge_type* edge) { if (edge->color() == EXTERNAL_COLOR) return; edge->color(EXTERNAL_COLOR); edge->twin()->color(EXTERNAL_COLOR); const VD::vertex_type* v = edge->vertex1(); if (v == NULL || !edge->is_primary()) return; v->color(EXTERNAL_COLOR); const VD::edge_type* e = v->incident_edge(); do { color_exterior(e); e = e->rot_next(); } while (e != v->incident_edge()); } inline point_type retrieve_point(const std::vector &segments, const cell_type& cell) { assert(cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == SOURCE_CATEGORY_SEGMENT_END_POINT); return (cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) ? low(segments[cell.source_index()]) : high(segments[cell.source_index()]); } inline void clip_infinite_edge(const std::vector &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector* clipped_edge) { const cell_type& cell1 = *edge.cell(); const cell_type& cell2 = *edge.twin()->cell(); point_type origin, direction; // Infinite edges could not be created by two segment sites. if (cell1.contains_point() && cell2.contains_point()) { point_type p1 = retrieve_point(segments, cell1); point_type p2 = retrieve_point(segments, cell2); origin.x((p1.x() + p2.x()) * 0.5); origin.y((p1.y() + p2.y()) * 0.5); direction.x(p1.y() - p2.y()); direction.y(p2.x() - p1.x()); } else { origin = cell1.contains_segment() ? retrieve_point(segments, cell2) : retrieve_point(segments, cell1); segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()]; coordinate_type dx = high(segment).x() - low(segment).x(); coordinate_type dy = high(segment).y() - low(segment).y(); if ((low(segment) == origin) ^ cell1.contains_point()) { direction.x(dy); direction.y(-dx); } else { direction.x(-dy); direction.y(dx); } } coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y())); if (edge.vertex0() == NULL) { clipped_edge->push_back(point_type( origin.x() - direction.x() * koef, origin.y() - direction.y() * koef)); } else { clipped_edge->push_back( point_type(edge.vertex0()->x(), edge.vertex0()->y())); } if (edge.vertex1() == NULL) { clipped_edge->push_back(point_type( origin.x() + direction.x() * koef, origin.y() + direction.y() * koef)); } else { clipped_edge->push_back( point_type(edge.vertex1()->x(), edge.vertex1()->y())); } } inline void sample_curved_edge(const std::vector &segments, const edge_type& edge, std::vector &sampled_edge, coordinate_type max_dist) { point_type point = edge.cell()->contains_point() ? retrieve_point(segments, *edge.cell()) : retrieve_point(segments, *edge.twin()->cell()); segment_type segment = edge.cell()->contains_point() ? segments[edge.twin()->cell()->source_index()] : segments[edge.cell()->source_index()]; ::boost::polygon::voronoi_visual_utils::discretize(point, segment, max_dist, &sampled_edge); } } /* namespace Internal */ } // namespace Voronoi static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_diagram &vd, const ThickPolylines *polylines, const char *path) { const double scale = 0.2; const std::string inputSegmentPointColor = "lightseagreen"; const coord_t inputSegmentPointRadius = coord_t(0.09 * scale / SCALING_FACTOR); const std::string inputSegmentColor = "lightseagreen"; const coord_t inputSegmentLineWidth = coord_t(0.03 * scale / SCALING_FACTOR); const std::string voronoiPointColor = "black"; const coord_t voronoiPointRadius = coord_t(0.06 * scale / SCALING_FACTOR); const std::string voronoiLineColorPrimary = "black"; const std::string voronoiLineColorSecondary = "green"; const std::string voronoiArcColor = "red"; const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR); const bool internalEdgesOnly = false; const bool primaryEdgesOnly = false; BoundingBox bbox = BoundingBox(lines); bbox.min.x -= coord_t(1. / SCALING_FACTOR); bbox.min.y -= coord_t(1. / SCALING_FACTOR); bbox.max.x += coord_t(1. / SCALING_FACTOR); bbox.max.y += coord_t(1. / SCALING_FACTOR); ::Slic3r::SVG svg(path, bbox); if (polylines != NULL) svg.draw(*polylines, "lime", "lime", voronoiLineWidth); // bbox.scale(1.2); // For clipping of half-lines to some reasonable value. // The line will then be clipped by the SVG viewer anyway. const double bbox_dim_max = double(bbox.max.x - bbox.min.x) + double(bbox.max.y - bbox.min.y); // For the discretization of the Voronoi parabolic segments. const double discretization_step = 0.0005 * bbox_dim_max; // Make a copy of the input segments with the double type. std::vector segments; for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it) segments.push_back(Voronoi::Internal::segment_type( Voronoi::Internal::point_type(double(it->a.x), double(it->a.y)), Voronoi::Internal::point_type(double(it->b.x), double(it->b.y)))); // Color exterior edges. for (voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) if (!it->is_finite()) Voronoi::Internal::color_exterior(&(*it)); // Draw the end points of the input polygon. for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { svg.draw(it->a, inputSegmentPointColor, inputSegmentPointRadius); svg.draw(it->b, inputSegmentPointColor, inputSegmentPointRadius); } // Draw the input polygon. for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) svg.draw(Line(Point(coord_t(it->a.x), coord_t(it->a.y)), Point(coord_t(it->b.x), coord_t(it->b.y))), inputSegmentColor, inputSegmentLineWidth); #if 1 // Draw voronoi vertices. for (voronoi_diagram::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it) if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR) svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius); for (voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) { if (primaryEdgesOnly && !it->is_primary()) continue; if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR)) continue; std::vector samples; std::string color = voronoiLineColorPrimary; if (!it->is_finite()) { Voronoi::Internal::clip_infinite_edge(segments, *it, bbox_dim_max, &samples); if (! it->is_primary()) color = voronoiLineColorSecondary; } else { // Store both points of the segment into samples. sample_curved_edge will split the initial line // until the discretization_step is reached. samples.push_back(Voronoi::Internal::point_type(it->vertex0()->x(), it->vertex0()->y())); samples.push_back(Voronoi::Internal::point_type(it->vertex1()->x(), it->vertex1()->y())); if (it->is_curved()) { Voronoi::Internal::sample_curved_edge(segments, *it, samples, discretization_step); color = voronoiArcColor; } else if (! it->is_primary()) color = voronoiLineColorSecondary; } for (std::size_t i = 0; i + 1 < samples.size(); ++i) svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth); } #endif if (polylines != NULL) svg.draw(*polylines, "blue", voronoiLineWidth); svg.Close(); } #endif /* SLIC3R_DEBUG */ // Euclidian distance of two boost::polygon points. template T dist(const boost::polygon::point_data &p1,const boost::polygon::point_data &p2) { T dx = p2.x() - p1.x(); T dy = p2.y() - p1.y(); return sqrt(dx*dx+dy*dy); } // Find a foot point of "px" on a segment "seg". template inline point_type project_point_to_segment(segment_type &seg, point_type &px) { typedef typename point_type::coordinate_type T; const point_type &p0 = low(seg); const point_type &p1 = high(seg); const point_type dir(p1.x()-p0.x(), p1.y()-p0.y()); const point_type dproj(px.x()-p0.x(), px.y()-p0.y()); const T t = (dir.x()*dproj.x() + dir.y()*dproj.y()) / (dir.x()*dir.x() + dir.y()*dir.y()); assert(t >= T(-1e-6) && t <= T(1. + 1e-6)); return point_type(p0.x() + t*dir.x(), p0.y() + t*dir.y()); } template inline const typename VD::point_type retrieve_cell_point(const typename VD::cell_type& cell, const SEGMENTS &segments) { assert(cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == SOURCE_CATEGORY_SEGMENT_END_POINT); return (cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) ? low(segments[cell.source_index()]) : high(segments[cell.source_index()]); } template inline std::pair measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments) { typedef typename VD::coord_type T; const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y()); const typename VD::point_type pb(edge.vertex1()->x(), edge.vertex1()->y()); const typename VD::cell_type &cell1 = *edge.cell(); const typename VD::cell_type &cell2 = *edge.twin()->cell(); if (cell1.contains_segment()) { if (cell2.contains_segment()) { // Both cells contain a linear segment, the left / right cells are symmetric. // Project pa, pb to the left segment. const typename VD::segment_type segment1 = segments[cell1.source_index()]; const typename VD::point_type p1a = project_point_to_segment(segment1, pa); const typename VD::point_type p1b = project_point_to_segment(segment1, pb); return std::pair(T(2.)*dist(pa, p1a), T(2.)*dist(pb, p1b)); } else { // 1st cell contains a linear segment, 2nd cell contains a point. // The medial axis between the cells is a parabolic arc. // Project pa, pb to the left segment. const typename VD::point_type p2 = retrieve_cell_point(cell2, segments); return std::pair(T(2.)*dist(pa, p2), T(2.)*dist(pb, p2)); } } else if (cell2.contains_segment()) { // 1st cell contains a point, 2nd cell contains a linear segment. // The medial axis between the cells is a parabolic arc. const typename VD::point_type p1 = retrieve_cell_point(cell1, segments); return std::pair(T(2.)*dist(pa, p1), T(2.)*dist(pb, p1)); } else { // Both cells contain a point. The left / right regions are triangular and symmetric. const typename VD::point_type p1 = retrieve_cell_point(cell1, segments); return std::pair(T(2.)*dist(pa, p1), T(2.)*dist(pb, p1)); } } // Converts the Line instances of Lines vector to VD::segment_type. template class Lines2VDSegments { public: Lines2VDSegments(const Lines &alines) : lines(alines) {} typename VD::segment_type operator[](size_t idx) const { return typename VD::segment_type( typename VD::point_type(typename VD::coord_type(lines[idx].a.x), typename VD::coord_type(lines[idx].a.y)), typename VD::point_type(typename VD::coord_type(lines[idx].b.x), typename VD::coord_type(lines[idx].b.y))); } private: const Lines &lines; }; void MedialAxis::build(ThickPolylines* polylines) { construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd); /* // DEBUG: dump all Voronoi edges { for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { if (edge->is_infinite()) continue; ThickPolyline polyline; polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); polylines->push_back(polyline); } return; } */ typedef const VD::vertex_type vert_t; typedef const VD::edge_type edge_t; // collect valid edges (i.e. prune those not belonging to MAT) // note: this keeps twins, so it inserts twice the number of the valid edges this->valid_edges.clear(); { std::set seen_edges; for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) { // if we only process segments representing closed loops, none if the // infinite edges (if any) would be part of our MAT anyway if (edge->is_secondary() || edge->is_infinite()) continue; // don't re-validate twins if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed? seen_edges.insert(&*edge); seen_edges.insert(edge->twin()); if (!this->validate_edge(&*edge)) continue; this->valid_edges.insert(&*edge); this->valid_edges.insert(edge->twin()); } } this->edges = this->valid_edges; // iterate through the valid edges to build polylines while (!this->edges.empty()) { const edge_t* edge = *this->edges.begin(); // start a polyline ThickPolyline polyline; polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() )); polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() )); polyline.width.push_back(this->thickness[edge].first); polyline.width.push_back(this->thickness[edge].second); // remove this edge and its twin from the available edges (void)this->edges.erase(edge); (void)this->edges.erase(edge->twin()); // get next points this->process_edge_neighbors(edge, &polyline); // get previous points { ThickPolyline rpolyline; this->process_edge_neighbors(edge->twin(), &rpolyline); polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend()); polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend()); polyline.endpoints.first = rpolyline.endpoints.second; } assert(polyline.width.size() == polyline.points.size()*2 - 2); // prevent loop endpoints from being extended if (polyline.first_point().coincides_with(polyline.last_point())) { polyline.endpoints.first = false; polyline.endpoints.second = false; } // append polyline to result polylines->push_back(polyline); } #ifdef SLIC3R_DEBUG { static int iRun = 0; dump_voronoi_to_svg(this->lines, this->vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str()); printf("Thick lines: "); for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) { ThickLines lines = it->thicklines(); for (ThickLines::const_iterator it2 = lines.begin(); it2 != lines.end(); ++ it2) { printf("%f,%f ", it2->a_width, it2->b_width); } } printf("\n"); } #endif /* SLIC3R_DEBUG */ } void MedialAxis::build(Polylines* polylines) { ThickPolylines tp; this->build(&tp); polylines->insert(polylines->end(), tp.begin(), tp.end()); } void MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline) { while (true) { // Since rot_next() works on the edge starting point but we want // to find neighbors on the ending point, we just swap edge with // its twin. const VD::edge_type* twin = edge->twin(); // count neighbors for this edge std::vector neighbors; for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin; neighbor = neighbor->rot_next()) { if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor); } // if we have a single neighbor then we can continue recursively if (neighbors.size() == 1) { const VD::edge_type* neighbor = neighbors.front(); // break if this is a closed loop if (this->edges.count(neighbor) == 0) return; Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y()); polyline->points.push_back(new_point); polyline->width.push_back(this->thickness[neighbor].first); polyline->width.push_back(this->thickness[neighbor].second); (void)this->edges.erase(neighbor); (void)this->edges.erase(neighbor->twin()); edge = neighbor; } else if (neighbors.size() == 0) { polyline->endpoints.second = true; return; } else { // T-shaped or star-shaped joint return; } } } bool MedialAxis::validate_edge(const VD::edge_type* edge) { // prevent overflows and detect almost-infinite edges if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || std::abs(edge->vertex0()->y()) > double(CLIPPER_MAX_COORD_UNSCALED) || std::abs(edge->vertex1()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) || std::abs(edge->vertex1()->y()) > double(CLIPPER_MAX_COORD_UNSCALED)) return false; // construct the line representing this edge of the Voronoi diagram const Line line( Point( edge->vertex0()->x(), edge->vertex0()->y() ), Point( edge->vertex1()->x(), edge->vertex1()->y() ) ); // discard edge if it lies outside the supplied shape // this could maybe be optimized (checking inclusion of the endpoints // might give false positives as they might belong to the contour itself) if (this->expolygon != NULL) { if (line.a.coincides_with(line.b)) { // in this case, contains(line) returns a false positive if (!this->expolygon->contains(line.a)) return false; } else { if (!this->expolygon->contains(line)) return false; } } // retrieve the original line segments which generated the edge we're checking const VD::cell_type* cell_l = edge->cell(); const VD::cell_type* cell_r = edge->twin()->cell(); const Line &segment_l = this->retrieve_segment(cell_l); const Line &segment_r = this->retrieve_segment(cell_r); /* SVG svg("edge.svg"); svg.draw(*this->expolygon); svg.draw(line); svg.draw(segment_l, "red"); svg.draw(segment_r, "blue"); svg.Close(); */ /* Calculate thickness of the cross-section at both the endpoints of this edge. Our Voronoi edge is part of a CCW sequence going around its Voronoi cell located on the left side. (segment_l). This edge's twin goes around segment_r. Thus, segment_r is oriented in the same direction as our main edge, and segment_l is oriented in the same direction as our twin edge. We used to only consider the (half-)distances to segment_r, and that works whenever segment_l and segment_r are almost specular and facing. However, at curves they are staggered and they only face for a very little length (our very short edge represents such visibility). Both w0 and w1 can be calculated either towards cell_l or cell_r with equal results by Voronoi definition. When cell_l or cell_r don't refer to the segment but only to an endpoint, we calculate the distance to that endpoint instead. */ coordf_t w0 = cell_r->contains_segment() ? line.a.distance_to(segment_r)*2 : line.a.distance_to(this->retrieve_endpoint(cell_r))*2; coordf_t w1 = cell_l->contains_segment() ? line.b.distance_to(segment_l)*2 : line.b.distance_to(this->retrieve_endpoint(cell_l))*2; if (cell_l->contains_segment() && cell_r->contains_segment()) { // calculate the relative angle between the two boundary segments double angle = fabs(segment_r.orientation() - segment_l.orientation()); if (angle > PI) angle = 2*PI - angle; assert(angle >= 0 && angle <= PI); // fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction) // we're interested only in segments close to the second case (facing segments) // so we allow some tolerance. // this filter ensures that we're dealing with a narrow/oriented area (longer than thick) // we don't run it on edges not generated by two segments (thus generated by one segment // and the endpoint of another segment), since their orientation would not be meaningful if (PI - angle > PI/8) { // angle is not narrow enough // only apply this filter to segments that are not too short otherwise their // angle could possibly be not meaningful if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width) return false; } } else { if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON) return false; } if (w0 < this->min_width && w1 < this->min_width) return false; if (w0 > this->max_width && w1 > this->max_width) return false; this->thickness[edge] = std::make_pair(w0, w1); this->thickness[edge->twin()] = std::make_pair(w1, w0); return true; } const Line& MedialAxis::retrieve_segment(const VD::cell_type* cell) const { return this->lines[cell->source_index()]; } const Point& MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const { const Line& line = this->retrieve_segment(cell); if (cell->source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) { return line.a; } else { return line.b; } } } } Slic3r-version_1.39.1/xs/src/libslic3r/Geometry.hpp000066400000000000000000000141611324354444700221410ustar00rootroot00000000000000#ifndef slic3r_Geometry_hpp_ #define slic3r_Geometry_hpp_ #include "libslic3r.h" #include "BoundingBox.hpp" #include "ExPolygon.hpp" #include "Polygon.hpp" #include "Polyline.hpp" #include "boost/polygon/voronoi.hpp" using boost::polygon::voronoi_builder; using boost::polygon::voronoi_diagram; namespace Slic3r { namespace Geometry { // Generic result of an orientation predicate. enum Orientation { ORIENTATION_CCW = 1, ORIENTATION_CW = -1, ORIENTATION_COLINEAR = 0 }; // Return orientation of the three points (clockwise, counter-clockwise, colinear) // The predicate is exact for the coord_t type, using 64bit signed integers for the temporaries. // which means, the coord_t types must not have some of the topmost bits utilized. // As the points are limited to 30 bits + signum, // the temporaries u, v, w are limited to 61 bits + signum, // and d is limited to 63 bits + signum and we are good. static inline Orientation orient(const Point &a, const Point &b, const Point &c) { // BOOST_STATIC_ASSERT(sizeof(coord_t) * 2 == sizeof(int64_t)); int64_t u = int64_t(b.x) * int64_t(c.y) - int64_t(b.y) * int64_t(c.x); int64_t v = int64_t(a.x) * int64_t(c.y) - int64_t(a.y) * int64_t(c.x); int64_t w = int64_t(a.x) * int64_t(b.y) - int64_t(a.y) * int64_t(b.x); int64_t d = u - v + w; return (d > 0) ? ORIENTATION_CCW : ((d == 0) ? ORIENTATION_COLINEAR : ORIENTATION_CW); } // Return orientation of the polygon by checking orientation of the left bottom corner of the polygon // using exact arithmetics. The input polygon must not contain duplicate points // (or at least the left bottom corner point must not have duplicates). static inline bool is_ccw(const Polygon &poly) { // The polygon shall be at least a triangle. assert(poly.points.size() >= 3); if (poly.points.size() < 3) return true; // 1) Find the lowest lexicographical point. unsigned int imin = 0; for (unsigned int i = 1; i < poly.points.size(); ++ i) { const Point &pmin = poly.points[imin]; const Point &p = poly.points[i]; if (p.x < pmin.x || (p.x == pmin.x && p.y < pmin.y)) imin = i; } // 2) Detect the orientation of the corner imin. size_t iPrev = ((imin == 0) ? poly.points.size() : imin) - 1; size_t iNext = ((imin + 1 == poly.points.size()) ? 0 : imin + 1); Orientation o = orient(poly.points[iPrev], poly.points[imin], poly.points[iNext]); // The lowest bottom point must not be collinear if the polygon does not contain duplicate points // or overlapping segments. assert(o != ORIENTATION_COLINEAR); return o == ORIENTATION_CCW; } inline bool ray_ray_intersection(const Pointf &p1, const Vectorf &v1, const Pointf &p2, const Vectorf &v2, Pointf &res) { double denom = v1.x * v2.y - v2.x * v1.y; if (std::abs(denom) < EPSILON) return false; double t = (v2.x * (p1.y - p2.y) - v2.y * (p1.x - p2.x)) / denom; res.x = p1.x + t * v1.x; res.y = p1.y + t * v1.y; return true; } inline bool segment_segment_intersection(const Pointf &p1, const Vectorf &v1, const Pointf &p2, const Vectorf &v2, Pointf &res) { double denom = v1.x * v2.y - v2.x * v1.y; if (std::abs(denom) < EPSILON) // Lines are collinear. return false; double s12_x = p1.x - p2.x; double s12_y = p1.y - p2.y; double s_numer = v1.x * s12_y - v1.y * s12_x; bool denom_is_positive = false; if (denom < 0.) { denom_is_positive = true; denom = - denom; s_numer = - s_numer; } if (s_numer < 0.) // Intersection outside of the 1st segment. return false; double t_numer = v2.x * s12_y - v2.y * s12_x; if (! denom_is_positive) t_numer = - t_numer; if (t_numer < 0. || s_numer > denom || t_numer > denom) // Intersection outside of the 1st or 2nd segment. return false; // Intersection inside both of the segments. double t = t_numer / denom; res.x = p1.x + t * v1.x; res.y = p1.y + t * v1.y; return true; } Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); void chained_path(const Points &points, std::vector &retval, Point start_near); void chained_path(const Points &points, std::vector &retval); template void chained_path_items(Points &points, T &items, T &retval); bool directions_parallel(double angle1, double angle2, double max_diff = 0); template bool contains(const std::vector &vector, const Point &point); double rad2deg(double angle); double rad2deg_dir(double angle); template T deg2rad(T angle) { return T(PI) * angle / T(180.0); } void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval); double linint(double value, double oldmin, double oldmax, double newmin, double newmax); bool arrange( // input size_t num_parts, const Pointf &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box, // output Pointfs &positions); class MedialAxis { public: Lines lines; const ExPolygon* expolygon; double max_width; double min_width; MedialAxis(double _max_width, double _min_width, const ExPolygon* _expolygon = NULL) : expolygon(_expolygon), max_width(_max_width), min_width(_min_width) {}; void build(ThickPolylines* polylines); void build(Polylines* polylines); private: class VD : public voronoi_diagram { public: typedef double coord_type; typedef boost::polygon::point_data point_type; typedef boost::polygon::segment_data segment_type; typedef boost::polygon::rectangle_data rect_type; }; VD vd; std::set edges, valid_edges; std::map > thickness; void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline); bool validate_edge(const VD::edge_type* edge); const Line& retrieve_segment(const VD::cell_type* cell) const; const Point& retrieve_endpoint(const VD::cell_type* cell) const; }; } } #endif Slic3r-version_1.39.1/xs/src/libslic3r/Int128.hpp000066400000000000000000000277621324354444700213460ustar00rootroot00000000000000// This is an excerpt of from the Clipper library by Angus Johnson, see the license below, // implementing a 64 x 64 -> 128bit multiply, and 128bit addition, subtraction and compare // operations, to be used with exact geometric predicates. // The code has been extended by Vojtech Bubnik to use 128 bit intrinsic types // and/or 64x64->128 intrinsic functions where possible. /******************************************************************************* * * * Author : Angus Johnson * * Version : 6.2.9 * * Date : 16 February 2015 * * Website : http://www.angusj.com * * Copyright : Angus Johnson 2010-2015 * * * * License: * * Use, modification & distribution is subject to Boost Software License Ver 1. * * http://www.boost.org/LICENSE_1_0.txt * * * * Attributions: * * The code in this library is an extension of Bala Vatti's clipping algorithm: * * "A generic solution to polygon clipping" * * Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * * http://portal.acm.org/citation.cfm?id=129906 * * * * Computer graphics and geometric modeling: implementation and algorithms * * By Max K. Agoston * * Springer; 1 edition (January 4, 2005) * * http://books.google.com/books?q=vatti+clipping+agoston * * * * See also: * * "Polygon Offsetting by Computing Winding Numbers" * * Paper no. DETC2005-85513 pp. 565-575 * * ASME 2005 International Design Engineering Technical Conferences * * and Computers and Information in Engineering Conference (IDETC/CIE2005) * * September 24-28, 2005 , Long Beach, California, USA * * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * * * *******************************************************************************/ // #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG #ifdef SLIC3R_DEBUG #undef NDEBUG #define DEBUG #define _DEBUG #undef assert #endif #include #include "Point.hpp" #if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__) #define HAS_INTRINSIC_128_TYPE #endif //------------------------------------------------------------------------------ // Int128 class (enables safe math on signed 64bit integers) // eg Int128 val1((int64_t)9223372036854775807); //ie 2^63 -1 // Int128 val2((int64_t)9223372036854775807); // Int128 val3 = val1 * val2; //------------------------------------------------------------------------------ class Int128 { #ifdef HAS_INTRINSIC_128_TYPE /******************************************** Using the intrinsic 128bit x 128bit multiply ************************************************/ public: __int128 value; Int128(int64_t lo = 0) : value(lo) {} Int128(const Int128 &v) : value(v.value) {} Int128& operator=(const int64_t &rhs) { value = rhs; return *this; } uint64_t lo() const { return uint64_t(value); } int64_t hi() const { return int64_t(value >> 64); } int sign() const { return (value > 0) - (value < 0); } bool operator==(const Int128 &rhs) const { return value == rhs.value; } bool operator!=(const Int128 &rhs) const { return value != rhs.value; } bool operator> (const Int128 &rhs) const { return value > rhs.value; } bool operator< (const Int128 &rhs) const { return value < rhs.value; } bool operator>=(const Int128 &rhs) const { return value >= rhs.value; } bool operator<=(const Int128 &rhs) const { return value <= rhs.value; } Int128& operator+=(const Int128 &rhs) { value += rhs.value; return *this; } Int128 operator+ (const Int128 &rhs) const { return Int128(value + rhs.value); } Int128& operator-=(const Int128 &rhs) { value -= rhs.value; return *this; } Int128 operator -(const Int128 &rhs) const { return Int128(value - rhs.value); } Int128 operator -() const { return Int128(- value); } operator double() const { return double(value); } static inline Int128 multiply(int64_t lhs, int64_t rhs) { return Int128(__int128(lhs) * __int128(rhs)); } // Evaluate signum of a 2x2 determinant. static int sign_determinant_2x2(int64_t a11, int64_t a12, int64_t a21, int64_t a22) { __int128 det = __int128(a11) * __int128(a22) - __int128(a12) * __int128(a21); return (det > 0) - (det < 0); } // Compare two rational numbers. static int compare_rationals(int64_t p1, int64_t q1, int64_t p2, int64_t q2) { int invert = ((q1 < 0) == (q2 < 0)) ? 1 : -1; __int128 det = __int128(p1) * __int128(q2) - __int128(p2) * __int128(q1); return ((det > 0) - (det < 0)) * invert; } #else /* HAS_INTRINSIC_128_TYPE */ /******************************************** Splitting the 128bit number into two 64bit words *********************************************/ Int128(int64_t lo = 0) : m_lo((uint64_t)lo), m_hi((lo < 0) ? -1 : 0) {} Int128(const Int128 &val) : m_lo(val.m_lo), m_hi(val.m_hi) {} Int128(const int64_t& hi, const uint64_t& lo) : m_lo(lo), m_hi(hi) {} Int128& operator = (const int64_t &val) { m_lo = (uint64_t)val; m_hi = (val < 0) ? -1 : 0; return *this; } uint64_t lo() const { return m_lo; } int64_t hi() const { return m_hi; } int sign() const { return (m_hi == 0) ? (m_lo > 0) : (m_hi > 0) - (m_hi < 0); } bool operator == (const Int128 &val) const { return m_hi == val.m_hi && m_lo == val.m_lo; } bool operator != (const Int128 &val) const { return ! (*this == val); } bool operator > (const Int128 &val) const { return (m_hi == val.m_hi) ? m_lo > val.m_lo : m_hi > val.m_hi; } bool operator < (const Int128 &val) const { return (m_hi == val.m_hi) ? m_lo < val.m_lo : m_hi < val.m_hi; } bool operator >= (const Int128 &val) const { return ! (*this < val); } bool operator <= (const Int128 &val) const { return ! (*this > val); } Int128& operator += (const Int128 &rhs) { m_hi += rhs.m_hi; m_lo += rhs.m_lo; if (m_lo < rhs.m_lo) m_hi++; return *this; } Int128 operator + (const Int128 &rhs) const { Int128 result(*this); result+= rhs; return result; } Int128& operator -= (const Int128 &rhs) { *this += -rhs; return *this; } Int128 operator - (const Int128 &rhs) const { Int128 result(*this); result -= rhs; return result; } Int128 operator-() const { return (m_lo == 0) ? Int128(-m_hi, 0) : Int128(~m_hi, ~m_lo + 1); } operator double() const { const double shift64 = 18446744073709551616.0; //2^64 return (m_hi < 0) ? ((m_lo == 0) ? (double)m_hi * shift64 : -(double)(~m_lo + ~m_hi * shift64)) : (double)(m_lo + m_hi * shift64); } static inline Int128 multiply(int64_t lhs, int64_t rhs) { #if defined(_MSC_VER) && defined(_WIN64) // On Visual Studio 64bit, use the _mul128() intrinsic function. Int128 result; result.m_lo = (uint64_t)_mul128(lhs, rhs, &result.m_hi); return result; #else // This branch should only be executed in case there is neither __int16 type nor _mul128 intrinsic // function available. This is mostly on 32bit operating systems. // Use a pure C implementation of _mul128(). int negate = (lhs < 0) != (rhs < 0); if (lhs < 0) lhs = -lhs; uint64_t int1Hi = uint64_t(lhs) >> 32; uint64_t int1Lo = uint64_t(lhs & 0xFFFFFFFF); if (rhs < 0) rhs = -rhs; uint64_t int2Hi = uint64_t(rhs) >> 32; uint64_t int2Lo = uint64_t(rhs & 0xFFFFFFFF); //because the high (sign) bits in both int1Hi & int2Hi have been zeroed, //there's no risk of 64 bit overflow in the following assignment //(ie: $7FFFFFFF*$FFFFFFFF + $7FFFFFFF*$FFFFFFFF < 64bits) uint64_t a = int1Hi * int2Hi; uint64_t b = int1Lo * int2Lo; //Result = A shl 64 + C shl 32 + B ... uint64_t c = int1Hi * int2Lo + int1Lo * int2Hi; Int128 tmp; tmp.m_hi = int64_t(a + (c >> 32)); tmp.m_lo = int64_t(c << 32); tmp.m_lo += int64_t(b); if (tmp.m_lo < b) ++ tmp.m_hi; if (negate) tmp = - tmp; return tmp; #endif } // Evaluate signum of a 2x2 determinant. static int sign_determinant_2x2(int64_t a11, int64_t a12, int64_t a21, int64_t a22) { return (Int128::multiply(a11, a22) - Int128::multiply(a12, a21)).sign(); } // Compare two rational numbers. static int compare_rationals(int64_t p1, int64_t q1, int64_t p2, int64_t q2) { int invert = ((q1 < 0) == (q2 < 0)) ? 1 : -1; Int128 det = Int128::multiply(p1, q2) - Int128::multiply(p2, q1); return det.sign() * invert; } private: uint64_t m_lo; int64_t m_hi; #endif /* HAS_INTRINSIC_128_TYPE */ /******************************************** Common methods ************************************************/ public: // Evaluate signum of a 2x2 determinant, use a numeric filter to avoid 128 bit multiply if possible. static int sign_determinant_2x2_filtered(int64_t a11, int64_t a12, int64_t a21, int64_t a22) { // First try to calculate the determinant over the upper 31 bits. // Round p1, p2, q1, q2 to 31 bits. int64_t a11s = (a11 + (1 << 31)) >> 32; int64_t a12s = (a12 + (1 << 31)) >> 32; int64_t a21s = (a21 + (1 << 31)) >> 32; int64_t a22s = (a22 + (1 << 31)) >> 32; // Result fits 63 bits, it is an approximate of the determinant divided by 2^64. int64_t det = a11s * a22s - a12s * a21s; // Maximum absolute of the remainder of the exact determinant, divided by 2^64. int64_t err = ((std::abs(a11s) + std::abs(a12s) + std::abs(a21s) + std::abs(a22s)) << 1) + 1; assert(std::abs(det) <= err || ((det > 0) ? 1 : -1) == sign_determinant_2x2(a11, a12, a21, a22)); return (std::abs(det) > err) ? ((det > 0) ? 1 : -1) : sign_determinant_2x2(a11, a12, a21, a22); } // Compare two rational numbers, use a numeric filter to avoid 128 bit multiply if possible. static int compare_rationals_filtered(int64_t p1, int64_t q1, int64_t p2, int64_t q2) { // First try to calculate the determinant over the upper 31 bits. // Round p1, p2, q1, q2 to 31 bits. int invert = ((q1 < 0) == (q2 < 0)) ? 1 : -1; int64_t q1s = (q1 + (1 << 31)) >> 32; int64_t q2s = (q2 + (1 << 31)) >> 32; if (q1s != 0 && q2s != 0) { int64_t p1s = (p1 + (1 << 31)) >> 32; int64_t p2s = (p2 + (1 << 31)) >> 32; // Result fits 63 bits, it is an approximate of the determinant divided by 2^64. int64_t det = p1s * q2s - p2s * q1s; // Maximum absolute of the remainder of the exact determinant, divided by 2^64. int64_t err = ((std::abs(p1s) + std::abs(q1s) + std::abs(p2s) + std::abs(q2s)) << 1) + 1; assert(std::abs(det) <= err || ((det > 0) ? 1 : -1) * invert == compare_rationals(p1, q1, p2, q2)); if (std::abs(det) > err) return ((det > 0) ? 1 : -1) * invert; } return sign_determinant_2x2(p1, q1, p2, q2) * invert; } // Exact orientation predicate, // returns +1: CCW, 0: collinear, -1: CW. static int orient(const Slic3r::Point &p1, const Slic3r::Point &p2, const Slic3r::Point &p3) { Slic3r::Vector v1(p2 - p1); Slic3r::Vector v2(p3 - p1); return sign_determinant_2x2_filtered(v1.x, v1.y, v2.x, v2.y); } // Exact orientation predicate, // returns +1: CCW, 0: collinear, -1: CW. static int cross(const Slic3r::Point &v1, const Slic3r::Point &v2) { return sign_determinant_2x2_filtered(v1.x, v1.y, v2.x, v2.y); } }; Slic3r-version_1.39.1/xs/src/libslic3r/Layer.cpp000066400000000000000000000227141324354444700214200ustar00rootroot00000000000000#include "Layer.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "Print.hpp" #include "Fill/Fill.hpp" #include "SVG.hpp" #include namespace Slic3r { Layer::~Layer() { this->lower_layer = this->upper_layer = nullptr; for (LayerRegion *region : this->regions) delete region; this->regions.clear(); } LayerRegion* Layer::add_region(PrintRegion* print_region) { this->regions.emplace_back(new LayerRegion(this, print_region)); return this->regions.back(); } // merge all regions' slices to get islands void Layer::make_slices() { ExPolygons slices; if (this->regions.size() == 1) { // optimization: if we only have one region, take its slices slices = this->regions.front()->slices; } else { Polygons slices_p; FOREACH_LAYERREGION(this, layerm) { polygons_append(slices_p, to_polygons((*layerm)->slices)); } slices = union_ex(slices_p); } this->slices.expolygons.clear(); this->slices.expolygons.reserve(slices.size()); // prepare ordering points Points ordering_points; ordering_points.reserve(slices.size()); for (const ExPolygon &ex : slices) ordering_points.push_back(ex.contour.first_point()); // sort slices std::vector order; Slic3r::Geometry::chained_path(ordering_points, order); // populate slices vector for (size_t i : order) this->slices.expolygons.push_back(STDMOVE(slices[i])); } void Layer::merge_slices() { if (this->regions.size() == 1) { // Optimization, also more robust. Don't merge classified pieces of layerm->slices, // but use the non-split islands of a layer. For a single region print, these shall be equal. this->regions.front()->slices.set(this->slices.expolygons, stInternal); } else { FOREACH_LAYERREGION(this, layerm) { // without safety offset, artifacts are generated (GH #2494) (*layerm)->slices.set(union_ex(to_polygons(STDMOVE((*layerm)->slices.surfaces)), true), stInternal); } } } // Here the perimeters are created cummulatively for all layer regions sharing the same parameters influencing the perimeters. // The perimeter paths and the thin fills (ExtrusionEntityCollection) are assigned to the first compatible layer region. // The resulting fill surface is split back among the originating regions. void Layer::make_perimeters() { BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id(); // keep track of regions whose perimeters we have already generated std::set done; FOREACH_LAYERREGION(this, layerm) { size_t region_id = layerm - this->regions.begin(); if (done.find(region_id) != done.end()) continue; BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; done.insert(region_id); const PrintRegionConfig &config = (*layerm)->region()->config; // find compatible regions LayerRegionPtrs layerms; layerms.push_back(*layerm); for (LayerRegionPtrs::const_iterator it = layerm + 1; it != this->regions.end(); ++it) { LayerRegion* other_layerm = *it; const PrintRegionConfig &other_config = other_layerm->region()->config; if (config.perimeter_extruder == other_config.perimeter_extruder && config.perimeters == other_config.perimeters && config.perimeter_speed == other_config.perimeter_speed && config.external_perimeter_speed == other_config.external_perimeter_speed && config.gap_fill_speed == other_config.gap_fill_speed && config.overhangs == other_config.overhangs && config.serialize("perimeter_extrusion_width").compare(other_config.serialize("perimeter_extrusion_width")) == 0 && config.thin_walls == other_config.thin_walls && config.external_perimeters_first == other_config.external_perimeters_first) { layerms.push_back(other_layerm); done.insert(it - this->regions.begin()); } } if (layerms.size() == 1) { // optimization (*layerm)->fill_surfaces.surfaces.clear(); (*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces); (*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces); } else { SurfaceCollection new_slices; { // group slices (surfaces) according to number of extra perimeters std::map slices; // extra_perimeters => [ surface, surface... ] for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) { for (Surfaces::iterator s = (*l)->slices.surfaces.begin(); s != (*l)->slices.surfaces.end(); ++s) { slices[s->extra_perimeters].push_back(*s); } } // merge the surfaces assigned to each group for (std::map::const_iterator it = slices.begin(); it != slices.end(); ++it) new_slices.append(union_ex(it->second, true), it->second.front()); } // make perimeters SurfaceCollection fill_surfaces; (*layerm)->make_perimeters(new_slices, &fill_surfaces); // assign fill_surfaces to each layer if (!fill_surfaces.surfaces.empty()) { for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) { // Separate the fill surfaces. ExPolygons expp = intersection_ex(to_polygons(fill_surfaces), (*l)->slices); (*l)->fill_expolygons = expp; (*l)->fill_surfaces.set(STDMOVE(expp), fill_surfaces.surfaces.front()); } } } } BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done"; } void Layer::make_fills() { #ifdef SLIC3R_DEBUG printf("Making fills for layer " PRINTF_ZU "\n", this->id()); #endif for (LayerRegionPtrs::iterator it_layerm = regions.begin(); it_layerm != regions.end(); ++ it_layerm) { LayerRegion &layerm = *(*it_layerm); layerm.fills.clear(); make_fill(layerm, layerm.fills); #ifndef NDEBUG for (size_t i = 0; i < layerm.fills.entities.size(); ++ i) assert(dynamic_cast(layerm.fills.entities[i]) != NULL); #endif } } void Layer::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; for (LayerRegionPtrs::const_iterator region = this->regions.begin(); region != this->regions.end(); ++region) for (Surfaces::const_iterator surface = (*region)->slices.surfaces.begin(); surface != (*region)->slices.surfaces.end(); ++surface) bbox.merge(get_extents(surface->expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min.x, bbox.max.y); bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); SVG svg(path, bbox); const float transparency = 0.5f; for (LayerRegionPtrs::const_iterator region = this->regions.begin(); region != this->regions.end(); ++region) for (Surfaces::const_iterator surface = (*region)->slices.surfaces.begin(); surface != (*region)->slices.surfaces.end(); ++surface) svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency); export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. void Layer::export_region_slices_to_svg_debug(const char *name) const { static size_t idx = 0; this->export_region_slices_to_svg(debug_out_path("Layer-slices-%s-%d.svg", name, idx ++).c_str()); } void Layer::export_region_fill_surfaces_to_svg(const char *path) const { BoundingBox bbox; for (LayerRegionPtrs::const_iterator region = this->regions.begin(); region != this->regions.end(); ++region) for (Surfaces::const_iterator surface = (*region)->fill_surfaces.surfaces.begin(); surface != (*region)->fill_surfaces.surfaces.end(); ++surface) bbox.merge(get_extents(surface->expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min.x, bbox.max.y); bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); SVG svg(path, bbox); const float transparency = 0.5f; for (LayerRegionPtrs::const_iterator region = this->regions.begin(); region != this->regions.end(); ++region) for (Surfaces::const_iterator surface = (*region)->fill_surfaces.surfaces.begin(); surface != (*region)->fill_surfaces.surfaces.end(); ++surface) svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency); export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. void Layer::export_region_fill_surfaces_to_svg_debug(const char *name) const { static size_t idx = 0; this->export_region_fill_surfaces_to_svg(debug_out_path("Layer-fill_surfaces-%s-%d.svg", name, idx ++).c_str()); } } Slic3r-version_1.39.1/xs/src/libslic3r/Layer.hpp000066400000000000000000000153711324354444700214260ustar00rootroot00000000000000#ifndef slic3r_Layer_hpp_ #define slic3r_Layer_hpp_ #include "libslic3r.h" #include "Flow.hpp" #include "SurfaceCollection.hpp" #include "ExtrusionEntityCollection.hpp" #include "ExPolygonCollection.hpp" #include "PolylineCollection.hpp" namespace Slic3r { class Layer; class PrintRegion; class PrintObject; // TODO: make stuff private class LayerRegion { friend class Layer; public: Layer* layer() { return this->_layer; } const Layer* layer() const { return this->_layer; } PrintRegion* region() { return this->_region; } const PrintRegion* region() const { return this->_region; } // collection of surfaces generated by slicing the original geometry // divided by type top/bottom/internal SurfaceCollection slices; // collection of extrusion paths/loops filling gaps // These fills are generated by the perimeter generator. // They are not printed on their own, but they are copied to this->fills during infill generation. ExtrusionEntityCollection thin_fills; // Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature") // and for re-starting of infills. ExPolygons fill_expolygons; // collection of surfaces for infill generation SurfaceCollection fill_surfaces; // Collection of perimeter surfaces. This is a cached result of diff(slices, fill_surfaces). // While not necessary, the memory consumption is meager and it speeds up calculation. // The perimeter_surfaces keep the IDs of the slices (top/bottom/) SurfaceCollection perimeter_surfaces; // collection of expolygons representing the bridged areas (thus not // needing support material) Polygons bridged; // collection of polylines representing the unsupported bridge edges PolylineCollection unsupported_bridge_edges; // ordered collection of extrusion paths/loops to build all perimeters // (this collection contains only ExtrusionEntityCollection objects) ExtrusionEntityCollection perimeters; // ordered collection of extrusion paths to fill surfaces // (this collection contains only ExtrusionEntityCollection objects) ExtrusionEntityCollection fills; Flow flow(FlowRole role, bool bridge = false, double width = -1) const; void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces); void process_external_surfaces(const Layer* lower_layer); double infill_area_threshold() const; void export_region_slices_to_svg(const char *path) const; void export_region_fill_surfaces_to_svg(const char *path) const; // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. void export_region_slices_to_svg_debug(const char *name) const; void export_region_fill_surfaces_to_svg_debug(const char *name) const; // Is there any valid extrusion assigned to this LayerRegion? bool has_extrusions() const { return ! this->perimeters.entities.empty() || ! this->fills.entities.empty(); } private: Layer *_layer; PrintRegion *_region; LayerRegion(Layer *layer, PrintRegion *region) : _layer(layer), _region(region) {} ~LayerRegion() {} }; typedef std::vector LayerRegionPtrs; class Layer { friend class PrintObject; public: size_t id() const { return this->_id; } void set_id(size_t id) { this->_id = id; } PrintObject* object() { return this->_object; } const PrintObject* object() const { return this->_object; } Layer *upper_layer; Layer *lower_layer; LayerRegionPtrs regions; bool slicing_errors; coordf_t slice_z; // Z used for slicing in unscaled coordinates coordf_t print_z; // Z used for printing in unscaled coordinates coordf_t height; // layer height in unscaled coordinates // collection of expolygons generated by slicing the original geometry; // also known as 'islands' (all regions and surface types are merged here) // The slices are chained by the shortest traverse distance and this traversal // order will be recovered by the G-code generator. ExPolygonCollection slices; size_t region_count() const { return this->regions.size(); } const LayerRegion* get_region(int idx) const { return this->regions.at(idx); } LayerRegion* get_region(int idx) { return this->regions.at(idx); } LayerRegion* add_region(PrintRegion* print_region); void make_slices(); void merge_slices(); template bool any_internal_region_slice_contains(const T &item) const { for (const LayerRegion *layerm : this->regions) if (layerm->slices.any_internal_contains(item)) return true; return false; } template bool any_bottom_region_slice_contains(const T &item) const { for (const LayerRegion *layerm : this->regions) if (layerm->slices.any_bottom_contains(item)) return true; return false; } void make_perimeters(); void make_fills(); void export_region_slices_to_svg(const char *path) const; void export_region_fill_surfaces_to_svg(const char *path) const; // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. void export_region_slices_to_svg_debug(const char *name) const; void export_region_fill_surfaces_to_svg_debug(const char *name) const; // Is there any valid extrusion assigned to this LayerRegion? virtual bool has_extrusions() const { for (auto layerm : this->regions) if (layerm->has_extrusions()) return true; return false; } protected: size_t _id; // sequential number of layer, 0-based PrintObject *_object; Layer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) : upper_layer(nullptr), lower_layer(nullptr), slicing_errors(false), slice_z(slice_z), print_z(print_z), height(height), _id(id), _object(object) {} virtual ~Layer(); }; class SupportLayer : public Layer { friend class PrintObject; public: // Polygons covered by the supports: base, interface and contact areas. ExPolygonCollection support_islands; // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; // Is there any valid extrusion assigned to this LayerRegion? virtual bool has_extrusions() const { return ! support_fills.empty(); } //protected: // The constructor has been made public to be able to insert additional support layers for the skirt or a wipe tower // between the raft and the object first layer. SupportLayer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) : Layer(id, object, height, print_z, slice_z) {} virtual ~SupportLayer() {} }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/LayerRegion.cpp000066400000000000000000000472701324354444700225700ustar00rootroot00000000000000#include "Layer.hpp" #include "BridgeDetector.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "PerimeterGenerator.hpp" #include "Print.hpp" #include "Surface.hpp" #include "BoundingBox.hpp" #include "SVG.hpp" #include #include #include namespace Slic3r { Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const { return this->_region->flow( role, this->_layer->height, bridge, this->_layer->id() == 0, width, *this->_layer->object() ); } // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. void LayerRegion::slices_to_fill_surfaces_clipped() { // Note: this method should be idempotent, but fill_surfaces gets modified // in place. However we're now only using its boundaries (which are invariant) // so we're safe. This guarantees idempotence of prepare_infill() also in case // that combine_infill() turns some fill_surface into VOID surfaces. // Polygons fill_boundaries = to_polygons(STDMOVE(this->fill_surfaces)); Polygons fill_boundaries = to_polygons(this->fill_expolygons); // Collect polygons per surface type. std::vector polygons_by_surface; polygons_by_surface.assign(size_t(stCount), Polygons()); for (Surface &surface : this->slices.surfaces) polygons_append(polygons_by_surface[(size_t)surface.surface_type], surface.expolygon); // Trim surfaces by the fill_boundaries. this->fill_surfaces.surfaces.clear(); for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) { const Polygons &polygons = polygons_by_surface[surface_type]; if (! polygons.empty()) this->fill_surfaces.append(intersection_ex(polygons, fill_boundaries), SurfaceType(surface_type)); } } void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces) { this->perimeters.clear(); this->thin_fills.clear(); PerimeterGenerator g( // input: &slices, this->layer()->height, this->flow(frPerimeter), &this->region()->config, &this->layer()->object()->config, &this->layer()->object()->print()->config, // output: &this->perimeters, &this->thin_fills, fill_surfaces ); if (this->layer()->lower_layer != NULL) // Cummulative sum of polygons over all the regions. g.lower_slices = &this->layer()->lower_layer->slices; g.layer_id = this->layer()->id(); g.ext_perimeter_flow = this->flow(frExternalPerimeter); g.overhang_flow = this->region()->flow(frPerimeter, -1, true, false, -1, *this->layer()->object()); g.solid_infill_flow = this->flow(frSolidInfill); g.process(); } //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 #define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. void LayerRegion::process_external_surfaces(const Layer* lower_layer) { const Surfaces &surfaces = this->fill_surfaces.surfaces; const double margin = scale_(EXTERNAL_INFILL_MARGIN); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset // for better anchoring. // Bottom surfaces, grown. Surfaces bottom; // Bridge surfaces, initialy not grown. Surfaces bridges; // Top surfaces, grown. Surfaces top; // Internal surfaces, not grown. Surfaces internal; // Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed. //FIXME if non zero infill, then fill_boundaries could be cheaply initialized from layerm->fill_expolygons. Polygons fill_boundaries; // Collect top surfaces and internal surfaces. // Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill. // This loop destroys the surfaces (aliasing this->fill_surfaces.surfaces) by moving into top/internal/fill_boundaries! { // bottom_polygons are used to trim inflated top surfaces. fill_boundaries.reserve(number_polygons(surfaces)); bool has_infill = this->region()->config.fill_density.value > 0.; for (const Surface &surface : this->fill_surfaces.surfaces) { if (surface.surface_type == stTop) { // Collect the top surfaces, inflate them and trim them by the bottom surfaces. // This gives the priority to bottom surfaces. surfaces_append(top, offset_ex(surface.expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); } else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == NULL)) { // Grown by 3mm. surfaces_append(bottom, offset_ex(surface.expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); } else if (surface.surface_type == stBottomBridge) { if (! surface.empty()) bridges.push_back(surface); } bool internal_surface = surface.surface_type != stTop && ! surface.is_bottom(); if (has_infill || surface.surface_type != stInternal) { if (internal_surface) // Make a copy as the following line uses the move semantics. internal.push_back(surface); polygons_append(fill_boundaries, STDMOVE(surface.expolygon)); } else if (internal_surface) internal.push_back(STDMOVE(surface)); } } #if 0 { static int iRun = 0; bridges.export_to_svg(debug_out_path("bridges-before-grouping-%d.svg", iRun ++), true); } #endif if (bridges.empty()) { fill_boundaries = union_(fill_boundaries, true); } else { // 1) Calculate the inflated bridge regions, each constrained to its island. ExPolygons fill_boundaries_ex = union_ex(fill_boundaries, true); std::vector bridges_grown; std::vector bridge_bboxes; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { static int iRun = 0; SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex)); svg.draw(fill_boundaries_ex); svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05)); svg.Close(); } // export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ { // Bridge expolygons, grown, to be tested for intersection with other bridge regions. std::vector fill_boundaries_ex_bboxes = get_extents_vector(fill_boundaries_ex); bridges_grown.reserve(bridges.size()); bridge_bboxes.reserve(bridges.size()); for (size_t i = 0; i < bridges.size(); ++ i) { // Find the island of this bridge. const Point pt = bridges[i].expolygon.contour.points.front(); int idx_island = -1; for (int j = 0; j < int(fill_boundaries_ex.size()); ++ j) if (fill_boundaries_ex_bboxes[j].contains(pt) && fill_boundaries_ex[j].contains(pt)) { idx_island = j; break; } // Grown by 3mm. Polygons polys = offset(to_polygons(bridges[i].expolygon), float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS); if (idx_island == -1) { printf("Bridge did not fall into the source region!\r\n"); } else { // Found an island, to which this bridge region belongs. Trim it, polys = intersection(polys, to_polygons(fill_boundaries_ex[idx_island])); } bridge_bboxes.push_back(get_extents(polys)); bridges_grown.push_back(STDMOVE(polys)); } } // 2) Group the bridge surfaces by overlaps. std::vector bridge_group(bridges.size(), (size_t)-1); size_t n_groups = 0; for (size_t i = 0; i < bridges.size(); ++ i) { // A grup id for this bridge. size_t group_id = (bridge_group[i] == -1) ? (n_groups ++) : bridge_group[i]; bridge_group[i] = group_id; // For all possibly overlaping bridges: for (size_t j = i + 1; j < bridges.size(); ++ j) { if (! bridge_bboxes[i].overlap(bridge_bboxes[j])) continue; if (intersection(bridges_grown[i], bridges_grown[j], false).empty()) continue; // The two bridge regions intersect. Give them the same group id. if (bridge_group[j] != -1) { // The j'th bridge has been merged with some other bridge before. size_t group_id_new = bridge_group[j]; for (size_t k = 0; k < j; ++ k) if (bridge_group[k] == group_id) bridge_group[k] = group_id_new; group_id = group_id_new; } bridge_group[j] = group_id; } } // 3) Merge the groups with the same group id, detect bridges. { BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z << ", bridge groups: " << n_groups; for (size_t group_id = 0; group_id < n_groups; ++ group_id) { size_t n_bridges_merged = 0; size_t idx_last = (size_t)-1; for (size_t i = 0; i < bridges.size(); ++ i) { if (bridge_group[i] == group_id) { ++ n_bridges_merged; idx_last = i; } } if (n_bridges_merged == 0) // This group has no regions assigned as these were moved into another group. continue; // Collect the initial ungrown regions and the grown polygons. ExPolygons initial; Polygons grown; for (size_t i = 0; i < bridges.size(); ++ i) { if (bridge_group[i] != group_id) continue; initial.push_back(STDMOVE(bridges[i].expolygon)); polygons_append(grown, bridges_grown[i]); } // detect bridge direction before merging grown surfaces otherwise adjacent bridges // would get merged into a single one while they need different directions // also, supply the original expolygon instead of the grown one, because in case // of very thin (but still working) anchors, the grown expolygon would go beyond them BridgeDetector bd( initial, lower_layer->slices, this->flow(frInfill, true).scaled_width() ); #ifdef SLIC3R_DEBUG printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id()); #endif if (bd.detect_angle(Geometry::deg2rad(this->region()->config.bridge_angle.value))) { bridges[idx_last].bridge_angle = bd.angle; if (this->layer()->object()->config.support_material) { polygons_append(this->bridged, bd.coverage()); this->unsupported_bridge_edges.append(bd.unsupported_edges()); } } // without safety offset, artifacts are generated (GH #2494) surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]); } fill_boundaries = STDMOVE(to_polygons(fill_boundaries_ex)); BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; } #if 0 { static int iRun = 0; bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun ++), true); } #endif } Surfaces new_surfaces; { // Merge top and bottom in a single collection. surfaces_append(top, STDMOVE(bottom)); // Intersect the grown surfaces with the actual fill boundaries. Polygons bottom_polygons = to_polygons(bottom); for (size_t i = 0; i < top.size(); ++ i) { Surface &s1 = top[i]; if (s1.empty()) continue; Polygons polys; polygons_append(polys, STDMOVE(s1)); for (size_t j = i + 1; j < top.size(); ++ j) { Surface &s2 = top[j]; if (! s2.empty() && surfaces_could_merge(s1, s2)) { polygons_append(polys, STDMOVE(s2)); s2.clear(); } } if (s1.surface_type == stTop) // Trim the top surfaces by the bottom surfaces. This gives the priority to the bottom surfaces. polys = diff(polys, bottom_polygons); surfaces_append( new_surfaces, // Don't use a safety offset as fill_boundaries were already united using the safety offset. STDMOVE(intersection_ex(polys, fill_boundaries, false)), s1); } } // Subtract the new top surfaces from the other non-top surfaces and re-add them. Polygons new_polygons = to_polygons(new_surfaces); for (size_t i = 0; i < internal.size(); ++ i) { Surface &s1 = internal[i]; if (s1.empty()) continue; Polygons polys; polygons_append(polys, STDMOVE(s1)); for (size_t j = i + 1; j < internal.size(); ++ j) { Surface &s2 = internal[j]; if (! s2.empty() && surfaces_could_merge(s1, s2)) { polygons_append(polys, STDMOVE(s2)); s2.clear(); } } ExPolygons new_expolys = diff_ex(polys, new_polygons); polygons_append(new_polygons, to_polygons(new_expolys)); surfaces_append(new_surfaces, STDMOVE(new_expolys), s1); } this->fill_surfaces.surfaces = STDMOVE(new_surfaces); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } void LayerRegion::prepare_fill_surfaces() { #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial"); export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ /* Note: in order to make the psPrepareInfill step idempotent, we should never alter fill_surfaces boundaries on which our idempotency relies since that's the only meaningful information returned by psPerimeters. */ // if no solid layers are requested, turn top/bottom surfaces to internal if (this->region()->config.top_solid_layers == 0) { for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) if (surface->surface_type == stTop) surface->surface_type = (this->layer()->object()->config.infill_only_where_needed) ? stInternalVoid : stInternal; } if (this->region()->config.bottom_solid_layers == 0) { for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { if (surface->surface_type == stBottom || surface->surface_type == stBottomBridge) surface->surface_type = stInternal; } } // turn too small internal regions into solid regions according to the user setting if (this->region()->config.fill_density.value > 0) { // scaling an area requires two calls! double min_area = scale_(scale_(this->region()->config.solid_infill_below_area.value)); for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { if (surface->surface_type == stInternal && surface->area() <= min_area) surface->surface_type = stInternalSolid; } } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING export_region_slices_to_svg_debug("2_prepare_fill_surfaces-final"); export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } double LayerRegion::infill_area_threshold() const { double ss = this->flow(frSolidInfill).scaled_spacing(); return ss*ss; } void LayerRegion::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) bbox.merge(get_extents(surface->expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min.x, bbox.max.y); bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); SVG svg(path, bbox); const float transparency = 0.5f; for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface) svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency); for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) svg.draw(surface->expolygon.lines(), surface_type_to_color_name(surface->surface_type)); export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. void LayerRegion::export_region_slices_to_svg_debug(const char *name) const { static std::map idx_map; size_t &idx = idx_map[name]; this->export_region_slices_to_svg(debug_out_path("LayerRegion-slices-%s-%d.svg", name, idx ++).c_str()); } void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const { BoundingBox bbox; for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) bbox.merge(get_extents(surface->expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min.x, bbox.max.y); bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); SVG svg(path, bbox); const float transparency = 0.5f; for (const Surface &surface : this->fill_surfaces.surfaces) { svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency); svg.draw_outline(surface.expolygon, "black", "blue", scale_(0.05)); } export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } // Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export. void LayerRegion::export_region_fill_surfaces_to_svg_debug(const char *name) const { static std::map idx_map; size_t &idx = idx_map[name]; this->export_region_fill_surfaces_to_svg(debug_out_path("LayerRegion-fill_surfaces-%s-%d.svg", name, idx ++).c_str()); } } Slic3r-version_1.39.1/xs/src/libslic3r/Line.cpp000066400000000000000000000115611324354444700212310ustar00rootroot00000000000000#include "Geometry.hpp" #include "Line.hpp" #include "Polyline.hpp" #include #include #include namespace Slic3r { std::string Line::wkt() const { std::ostringstream ss; ss << "LINESTRING(" << this->a.x << " " << this->a.y << "," << this->b.x << " " << this->b.y << ")"; return ss.str(); } Line::operator Lines() const { Lines lines; lines.push_back(*this); return lines; } Line::operator Polyline() const { Polyline pl; pl.points.push_back(this->a); pl.points.push_back(this->b); return pl; } void Line::scale(double factor) { this->a.scale(factor); this->b.scale(factor); } void Line::translate(double x, double y) { this->a.translate(x, y); this->b.translate(x, y); } void Line::rotate(double angle, const Point ¢er) { this->a.rotate(angle, center); this->b.rotate(angle, center); } void Line::reverse() { std::swap(this->a, this->b); } double Line::length() const { return this->a.distance_to(this->b); } Point Line::midpoint() const { return Point((this->a.x + this->b.x) / 2.0, (this->a.y + this->b.y) / 2.0); } void Line::point_at(double distance, Point* point) const { double len = this->length(); *point = this->a; if (this->a.x != this->b.x) point->x = this->a.x + (this->b.x - this->a.x) * distance / len; if (this->a.y != this->b.y) point->y = this->a.y + (this->b.y - this->a.y) * distance / len; } Point Line::point_at(double distance) const { Point p; this->point_at(distance, &p); return p; } bool Line::intersection_infinite(const Line &other, Point* point) const { Vector x = this->a.vector_to(other.a); Vector d1 = this->vector(); Vector d2 = other.vector(); double cross = d1.x * d2.y - d1.y * d2.x; if (std::fabs(cross) < EPSILON) return false; double t1 = (x.x * d2.y - x.y * d2.x)/cross; point->x = this->a.x + d1.x * t1; point->y = this->a.y + d1.y * t1; return true; } bool Line::coincides_with(const Line &line) const { return this->a.coincides_with(line.a) && this->b.coincides_with(line.b); } double Line::distance_to(const Point &point) const { return point.distance_to(*this); } double Line::atan2_() const { return atan2(this->b.y - this->a.y, this->b.x - this->a.x); } double Line::orientation() const { double angle = this->atan2_(); if (angle < 0) angle = 2*PI + angle; return angle; } double Line::direction() const { double atan2 = this->atan2_(); return (fabs(atan2 - PI) < EPSILON) ? 0 : (atan2 < 0) ? (atan2 + PI) : atan2; } bool Line::parallel_to(double angle) const { return Slic3r::Geometry::directions_parallel(this->direction(), angle); } bool Line::parallel_to(const Line &line) const { return this->parallel_to(line.direction()); } Vector Line::vector() const { return Vector(this->b.x - this->a.x, this->b.y - this->a.y); } Vector Line::normal() const { return Vector((this->b.y - this->a.y), -(this->b.x - this->a.x)); } void Line::extend_end(double distance) { // relocate last point by extending the segment by the specified length Line line = *this; line.reverse(); this->b = line.point_at(-distance); } void Line::extend_start(double distance) { // relocate first point by extending the first segment by the specified length this->a = this->point_at(-distance); } bool Line::intersection(const Line& line, Point* intersection) const { double denom = ((double)(line.b.y - line.a.y)*(this->b.x - this->a.x)) - ((double)(line.b.x - line.a.x)*(this->b.y - this->a.y)); double nume_a = ((double)(line.b.x - line.a.x)*(this->a.y - line.a.y)) - ((double)(line.b.y - line.a.y)*(this->a.x - line.a.x)); double nume_b = ((double)(this->b.x - this->a.x)*(this->a.y - line.a.y)) - ((double)(this->b.y - this->a.y)*(this->a.x - line.a.x)); if (fabs(denom) < EPSILON) { if (fabs(nume_a) < EPSILON && fabs(nume_b) < EPSILON) { return false; // coincident } return false; // parallel } double ua = nume_a / denom; double ub = nume_b / denom; if (ua >= 0 && ua <= 1.0f && ub >= 0 && ub <= 1.0f) { // Get the intersection point. intersection->x = this->a.x + ua*(this->b.x - this->a.x); intersection->y = this->a.y + ua*(this->b.y - this->a.y); return true; } return false; // not intersecting } double Line::ccw(const Point& point) const { return point.ccw(*this); } Pointf3 Linef3::intersect_plane(double z) const { return Pointf3( this->a.x + (this->b.x - this->a.x) * (z - this->a.z) / (this->b.z - this->a.z), this->a.y + (this->b.y - this->a.y) * (z - this->a.z) / (this->b.z - this->a.z), z ); } void Linef3::scale(double factor) { this->a.scale(factor); this->b.scale(factor); } } Slic3r-version_1.39.1/xs/src/libslic3r/Line.hpp000066400000000000000000000046001324354444700212320ustar00rootroot00000000000000#ifndef slic3r_Line_hpp_ #define slic3r_Line_hpp_ #include "libslic3r.h" #include "Point.hpp" namespace Slic3r { class Line; class Linef3; class Polyline; class ThickLine; typedef std::vector Lines; typedef std::vector ThickLines; class Line { public: Point a; Point b; Line() {}; explicit Line(Point _a, Point _b): a(_a), b(_b) {}; std::string wkt() const; operator Lines() const; operator Polyline() const; void scale(double factor); void translate(double x, double y); void rotate(double angle, const Point ¢er); void reverse(); double length() const; Point midpoint() const; void point_at(double distance, Point* point) const; Point point_at(double distance) const; bool intersection_infinite(const Line &other, Point* point) const; bool coincides_with(const Line &line) const; double distance_to(const Point &point) const; bool parallel_to(double angle) const; bool parallel_to(const Line &line) const; double atan2_() const; double orientation() const; double direction() const; Vector vector() const; Vector normal() const; void extend_end(double distance); void extend_start(double distance); bool intersection(const Line& line, Point* intersection) const; double ccw(const Point& point) const; }; class ThickLine : public Line { public: coordf_t a_width, b_width; ThickLine() : a_width(0), b_width(0) {}; ThickLine(Point _a, Point _b) : Line(_a, _b), a_width(0), b_width(0) {}; }; class Linef { public: Pointf a; Pointf b; Linef() {}; explicit Linef(Pointf _a, Pointf _b): a(_a), b(_b) {}; }; class Linef3 { public: Pointf3 a; Pointf3 b; Linef3() {}; explicit Linef3(Pointf3 _a, Pointf3 _b): a(_a), b(_b) {}; Pointf3 intersect_plane(double z) const; void scale(double factor); }; } // namespace Slic3r // start Boost #include namespace boost { namespace polygon { template <> struct geometry_concept { typedef segment_concept type; }; template <> struct segment_traits { typedef coord_t coordinate_type; typedef Slic3r::Point point_type; static inline point_type get(const Slic3r::Line& line, direction_1d dir) { return dir.to_int() ? line.b : line.a; } }; } } // end Boost #endif Slic3r-version_1.39.1/xs/src/libslic3r/Model.cpp000066400000000000000000000740601324354444700214050ustar00rootroot00000000000000#include "Model.hpp" #include "Geometry.hpp" #include "Format/AMF.hpp" #include "Format/OBJ.hpp" #include "Format/PRUS.hpp" #include "Format/STL.hpp" #include #include #include #include namespace Slic3r { Model::Model(const Model &other) { // copy materials for (const auto &m : other.materials) this->add_material(m.first, *m.second); // copy objects this->objects.reserve(other.objects.size()); for (const ModelObject *o : other.objects) this->add_object(*o, true); } Model& Model::operator=(Model other) { this->swap(other); return *this; } void Model::swap(Model &other) { std::swap(this->materials, other.materials); std::swap(this->objects, other.objects); } Model Model::read_from_file(const std::string &input_file, bool add_default_instances) { Model model; bool result = false; if (boost::algorithm::iends_with(input_file, ".stl")) result = load_stl(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".obj")) result = load_obj(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) result = load_amf(input_file.c_str(), &model); #ifdef SLIC3R_PRUS else if (boost::algorithm::iends_with(input_file, ".prusa")) result = load_prus(input_file.c_str(), &model); #endif /* SLIC3R_PRUS */ else throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); if (! result) throw std::runtime_error("Loading of a model file failed."); if (model.objects.empty()) throw std::runtime_error("The supplied file couldn't be read because it's empty"); for (ModelObject *o : model.objects) o->input_file = input_file; if (add_default_instances) model.add_default_instances(); return model; } ModelObject* Model::add_object() { this->objects.emplace_back(new ModelObject(this)); return this->objects.back(); } ModelObject* Model::add_object(const char *name, const char *path, const TriangleMesh &mesh) { ModelObject* new_object = new ModelObject(this); this->objects.push_back(new_object); new_object->name = name; new_object->input_file = path; ModelVolume *new_volume = new_object->add_volume(mesh); new_volume->name = name; new_object->invalidate_bounding_box(); return new_object; } ModelObject* Model::add_object(const char *name, const char *path, TriangleMesh &&mesh) { ModelObject* new_object = new ModelObject(this); this->objects.push_back(new_object); new_object->name = name; new_object->input_file = path; ModelVolume *new_volume = new_object->add_volume(std::move(mesh)); new_volume->name = name; new_object->invalidate_bounding_box(); return new_object; } ModelObject* Model::add_object(const ModelObject &other, bool copy_volumes) { ModelObject* new_object = new ModelObject(this, other, copy_volumes); this->objects.push_back(new_object); return new_object; } void Model::delete_object(size_t idx) { ModelObjectPtrs::iterator i = this->objects.begin() + idx; delete *i; this->objects.erase(i); } void Model::clear_objects() { for (ModelObject *o : this->objects) delete o; this->objects.clear(); } void Model::delete_material(t_model_material_id material_id) { ModelMaterialMap::iterator i = this->materials.find(material_id); if (i != this->materials.end()) { delete i->second; this->materials.erase(i); } } void Model::clear_materials() { for (auto &m : this->materials) delete m.second; this->materials.clear(); } ModelMaterial* Model::add_material(t_model_material_id material_id) { ModelMaterial* material = this->get_material(material_id); if (material == nullptr) material = this->materials[material_id] = new ModelMaterial(this); return material; } ModelMaterial* Model::add_material(t_model_material_id material_id, const ModelMaterial &other) { // delete existing material if any ModelMaterial* material = this->get_material(material_id); delete material; // set new material material = new ModelMaterial(this, other); this->materials[material_id] = material; return material; } // makes sure all objects have at least one instance bool Model::add_default_instances() { // apply a default position to all objects not having one for (ModelObject *o : this->objects) if (o->instances.empty()) o->add_instance(); return true; } // this returns the bounding box of the *transformed* instances BoundingBoxf3 Model::bounding_box() { BoundingBoxf3 bb; for (ModelObject *o : this->objects) bb.merge(o->bounding_box()); return bb; } void Model::center_instances_around_point(const Pointf &point) { // BoundingBoxf3 bb = this->bounding_box(); BoundingBoxf3 bb; for (ModelObject *o : this->objects) for (size_t i = 0; i < o->instances.size(); ++ i) bb.merge(o->instance_bounding_box(i, false)); Sizef3 size = bb.size(); coordf_t shift_x = -bb.min.x + point.x - size.x/2; coordf_t shift_y = -bb.min.y + point.y - size.y/2; for (ModelObject *o : this->objects) { for (ModelInstance *i : o->instances) i->offset.translate(shift_x, shift_y); o->invalidate_bounding_box(); } } // flattens everything to a single mesh TriangleMesh Model::mesh() const { TriangleMesh mesh; for (const ModelObject *o : this->objects) mesh.merge(o->mesh()); return mesh; } static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb, Pointfs &out) { // we supply unscaled data to arrange() bool result = Slic3r::Geometry::arrange( sizes.size(), // number of parts BoundingBoxf(sizes).max, // width and height of a single cell dist, // distance between cells bb, // bounding box of the area to fill out // output positions ); if (!result && bb != nullptr) { // Try to arrange again ignoring bb result = Slic3r::Geometry::arrange( sizes.size(), // number of parts BoundingBoxf(sizes).max, // width and height of a single cell dist, // distance between cells nullptr, // bounding box of the area to fill out // output positions ); } return result; } /* arrange objects preserving their instance count but altering their instance positions */ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) { // get the (transformed) size of each instance so that we take // into account their different transformations when packing Pointfs instance_sizes; Pointfs instance_centers; for (const ModelObject *o : this->objects) for (size_t i = 0; i < o->instances.size(); ++ i) { // an accurate snug bounding box around the transformed mesh. BoundingBoxf3 bbox(o->instance_bounding_box(i, true)); instance_sizes.push_back(bbox.size()); instance_centers.push_back(bbox.center()); } Pointfs positions; if (! _arrange(instance_sizes, dist, bb, positions)) return false; size_t idx = 0; for (ModelObject *o : this->objects) { for (ModelInstance *i : o->instances) { i->offset = positions[idx] - instance_centers[idx]; ++ idx; } o->invalidate_bounding_box(); } return true; } // Duplicate the entire model preserving instance relative positions. void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) { Pointfs model_sizes(copies_num-1, this->bounding_box().size()); Pointfs positions; if (! _arrange(model_sizes, dist, bb, positions)) CONFESS("Cannot duplicate part as the resulting objects would not fit on the print bed.\n"); // note that this will leave the object count unaltered for (ModelObject *o : this->objects) { // make a copy of the pointers in order to avoid recursion when appending their copies ModelInstancePtrs instances = o->instances; for (const ModelInstance *i : instances) { for (const Pointf &pos : positions) { ModelInstance *instance = o->add_instance(*i); instance->offset.translate(pos); } } o->invalidate_bounding_box(); } } /* this will append more instances to each object and then automatically rearrange everything */ void Model::duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) { for (ModelObject *o : this->objects) { // make a copy of the pointers in order to avoid recursion when appending their copies ModelInstancePtrs instances = o->instances; for (const ModelInstance *i : instances) for (size_t k = 2; k <= copies_num; ++ k) o->add_instance(*i); } this->arrange_objects(dist, bb); } void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist) { if (this->objects.size() > 1) throw "Grid duplication is not supported with multiple objects"; if (this->objects.empty()) throw "No objects!"; ModelObject* object = this->objects.front(); object->clear_instances(); Sizef3 size = object->bounding_box().size(); for (size_t x_copy = 1; x_copy <= x; ++x_copy) { for (size_t y_copy = 1; y_copy <= y; ++y_copy) { ModelInstance* instance = object->add_instance(); instance->offset.x = (size.x + dist) * (x_copy-1); instance->offset.y = (size.y + dist) * (y_copy-1); } } } bool Model::looks_like_multipart_object() const { if (this->objects.size() <= 1) return false; double zmin = std::numeric_limits::max(); for (const ModelObject *obj : this->objects) { if (obj->volumes.size() > 1 || obj->config.keys().size() > 1) return false; for (const ModelVolume *vol : obj->volumes) { double zmin_this = vol->mesh.bounding_box().min.z; if (zmin == std::numeric_limits::max()) zmin = zmin_this; else if (std::abs(zmin - zmin_this) > EPSILON) // The volumes don't share zmin. return true; } } return false; } void Model::convert_multipart_object() { if (this->objects.empty()) return; ModelObject* object = new ModelObject(this); object->input_file = this->objects.front()->input_file; for (const ModelObject* o : this->objects) for (const ModelVolume* v : o->volumes) object->add_volume(*v)->name = o->name; for (const ModelInstance* i : this->objects.front()->instances) object->add_instance(*i); this->clear_objects(); this->objects.push_back(object); } ModelObject::ModelObject(Model *model, const ModelObject &other, bool copy_volumes) : name(other.name), input_file(other.input_file), instances(), volumes(), config(other.config), layer_height_ranges(other.layer_height_ranges), layer_height_profile(other.layer_height_profile), layer_height_profile_valid(other.layer_height_profile_valid), origin_translation(other.origin_translation), m_bounding_box(other.m_bounding_box), m_bounding_box_valid(other.m_bounding_box_valid), m_model(model) { if (copy_volumes) { this->volumes.reserve(other.volumes.size()); for (ModelVolumePtrs::const_iterator i = other.volumes.begin(); i != other.volumes.end(); ++i) this->add_volume(**i); } this->instances.reserve(other.instances.size()); for (ModelInstancePtrs::const_iterator i = other.instances.begin(); i != other.instances.end(); ++i) this->add_instance(**i); } ModelObject& ModelObject::operator=(ModelObject other) { this->swap(other); return *this; } void ModelObject::swap(ModelObject &other) { std::swap(this->input_file, other.input_file); std::swap(this->instances, other.instances); std::swap(this->volumes, other.volumes); std::swap(this->config, other.config); std::swap(this->layer_height_ranges, other.layer_height_ranges); std::swap(this->layer_height_profile, other.layer_height_profile); std::swap(this->layer_height_profile_valid, other.layer_height_profile_valid); std::swap(this->origin_translation, other.origin_translation); std::swap(m_bounding_box, other.m_bounding_box); std::swap(m_bounding_box_valid, other.m_bounding_box_valid); } ModelObject::~ModelObject() { this->clear_volumes(); this->clear_instances(); } ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh) { ModelVolume* v = new ModelVolume(this, mesh); this->volumes.push_back(v); this->invalidate_bounding_box(); return v; } ModelVolume* ModelObject::add_volume(TriangleMesh &&mesh) { ModelVolume* v = new ModelVolume(this, std::move(mesh)); this->volumes.push_back(v); this->invalidate_bounding_box(); return v; } ModelVolume* ModelObject::add_volume(const ModelVolume &other) { ModelVolume* v = new ModelVolume(this, other); this->volumes.push_back(v); this->invalidate_bounding_box(); return v; } void ModelObject::delete_volume(size_t idx) { ModelVolumePtrs::iterator i = this->volumes.begin() + idx; delete *i; this->volumes.erase(i); this->invalidate_bounding_box(); } void ModelObject::clear_volumes() { for (ModelVolume *v : this->volumes) delete v; this->volumes.clear(); this->invalidate_bounding_box(); } ModelInstance* ModelObject::add_instance() { ModelInstance* i = new ModelInstance(this); this->instances.push_back(i); this->invalidate_bounding_box(); return i; } ModelInstance* ModelObject::add_instance(const ModelInstance &other) { ModelInstance* i = new ModelInstance(this, other); this->instances.push_back(i); this->invalidate_bounding_box(); return i; } void ModelObject::delete_instance(size_t idx) { ModelInstancePtrs::iterator i = this->instances.begin() + idx; delete *i; this->instances.erase(i); this->invalidate_bounding_box(); } void ModelObject::delete_last_instance() { this->delete_instance(this->instances.size() - 1); } void ModelObject::clear_instances() { for (ModelInstance *i : this->instances) delete i; this->instances.clear(); this->invalidate_bounding_box(); } // Returns the bounding box of the transformed instances. // This bounding box is approximate and not snug. BoundingBoxf3 ModelObject::bounding_box() { if (! m_bounding_box_valid) { BoundingBoxf3 raw_bbox; for (const ModelVolume *v : this->volumes) if (! v->modifier) raw_bbox.merge(v->mesh.bounding_box()); BoundingBoxf3 bb; for (const ModelInstance *i : this->instances) bb.merge(i->transform_bounding_box(raw_bbox)); m_bounding_box = bb; m_bounding_box_valid = true; } return m_bounding_box; } // A mesh containing all transformed instances of this object. TriangleMesh ModelObject::mesh() const { TriangleMesh mesh; TriangleMesh raw_mesh = this->raw_mesh(); for (const ModelInstance *i : this->instances) { TriangleMesh m = raw_mesh; i->transform_mesh(&m); mesh.merge(m); } return mesh; } // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. // Currently used by ModelObject::mesh(), to calculate the 2D envelope for 2D platter // and to display the object statistics at ModelObject::print_info(). TriangleMesh ModelObject::raw_mesh() const { TriangleMesh mesh; for (const ModelVolume *v : this->volumes) if (! v->modifier) mesh.merge(v->mesh); return mesh; } // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. // This bounding box is only used for the actual slicing. BoundingBoxf3 ModelObject::raw_bounding_box() const { BoundingBoxf3 bb; for (const ModelVolume *v : this->volumes) if (! v->modifier) { if (this->instances.empty()) CONFESS("Can't call raw_bounding_box() with no instances"); bb.merge(this->instances.front()->transform_mesh_bounding_box(&v->mesh, true)); } return bb; } // This returns an accurate snug bounding box of the transformed object instance, without the translation applied. BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_translate) const { BoundingBoxf3 bb; for (ModelVolume *v : this->volumes) if (! v->modifier) bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(&v->mesh, dont_translate)); return bb; } void ModelObject::center_around_origin() { // calculate the displacements needed to // center this object around the origin BoundingBoxf3 bb; for (ModelVolume *v : this->volumes) if (! v->modifier) bb.merge(v->mesh.bounding_box()); // first align to origin on XYZ Vectorf3 vector(-bb.min.x, -bb.min.y, -bb.min.z); // then center it on XY Sizef3 size = bb.size(); vector.x -= size.x/2; vector.y -= size.y/2; this->translate(vector); this->origin_translation.translate(vector); if (!this->instances.empty()) { for (ModelInstance *i : this->instances) { // apply rotation and scaling to vector as well before translating instance, // in order to leave final position unaltered Vectorf3 v = vector.negative(); v.rotate(i->rotation); v.scale(i->scaling_factor); i->offset.translate(v.x, v.y); } this->invalidate_bounding_box(); } } void ModelObject::translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelVolume *v : this->volumes) v->mesh.translate(float(x), float(y), float(z)); if (m_bounding_box_valid) m_bounding_box.translate(x, y, z); } void ModelObject::scale(const Pointf3 &versor) { for (ModelVolume *v : this->volumes) v->mesh.scale(versor); // reset origin translation since it doesn't make sense anymore this->origin_translation = Pointf3(0,0,0); this->invalidate_bounding_box(); } void ModelObject::rotate(float angle, const Axis &axis) { for (ModelVolume *v : this->volumes) v->mesh.rotate(angle, axis); this->origin_translation = Pointf3(0,0,0); this->invalidate_bounding_box(); } void ModelObject::mirror(const Axis &axis) { for (ModelVolume *v : this->volumes) v->mesh.mirror(axis); this->origin_translation = Pointf3(0,0,0); this->invalidate_bounding_box(); } size_t ModelObject::materials_count() const { std::set material_ids; for (const ModelVolume *v : this->volumes) material_ids.insert(v->material_id()); return material_ids.size(); } size_t ModelObject::facets_count() const { size_t num = 0; for (const ModelVolume *v : this->volumes) if (! v->modifier) num += v->mesh.stl.stats.number_of_facets; return num; } bool ModelObject::needed_repair() const { for (const ModelVolume *v : this->volumes) if (! v->modifier && v->mesh.needed_repair()) return true; return false; } void ModelObject::cut(coordf_t z, Model* model) const { // clone this one to duplicate instances, materials etc. ModelObject* upper = model->add_object(*this); ModelObject* lower = model->add_object(*this); upper->clear_volumes(); lower->clear_volumes(); upper->input_file = ""; lower->input_file = ""; for (ModelVolume *volume : this->volumes) { if (volume->modifier) { // don't cut modifiers upper->add_volume(*volume); lower->add_volume(*volume); } else { TriangleMesh upper_mesh, lower_mesh; // TODO: shouldn't we use object bounding box instead of per-volume bb? coordf_t cut_z = z + volume->mesh.bounding_box().min.z; if (false) { // if (volume->mesh.has_multiple_patches()) { // Cutting algorithm does not work on intersecting meshes. // As we are not sure whether the meshes don't intersect, // we rather split the mesh into multiple non-intersecting pieces. TriangleMeshPtrs meshptrs = volume->mesh.split(); for (TriangleMeshPtrs::iterator mesh = meshptrs.begin(); mesh != meshptrs.end(); ++mesh) { printf("Cutting mesh patch %d of %d\n", int(mesh - meshptrs.begin()), int(meshptrs.size())); (*mesh)->repair(); TriangleMeshSlicer tms(*mesh); if (mesh == meshptrs.begin()) { tms.cut(cut_z, &upper_mesh, &lower_mesh); } else { TriangleMesh upper_mesh_this, lower_mesh_this; tms.cut(cut_z, &upper_mesh_this, &lower_mesh_this); upper_mesh.merge(upper_mesh_this); lower_mesh.merge(lower_mesh_this); } delete *mesh; } } else { printf("Cutting mesh patch\n"); TriangleMeshSlicer tms(&volume->mesh); tms.cut(cut_z, &upper_mesh, &lower_mesh); } upper_mesh.repair(); lower_mesh.repair(); upper_mesh.reset_repair_stats(); lower_mesh.reset_repair_stats(); if (upper_mesh.facets_count() > 0) { ModelVolume* vol = upper->add_volume(upper_mesh); vol->name = volume->name; vol->config = volume->config; vol->set_material(volume->material_id(), *volume->material()); } if (lower_mesh.facets_count() > 0) { ModelVolume* vol = lower->add_volume(lower_mesh); vol->name = volume->name; vol->config = volume->config; vol->set_material(volume->material_id(), *volume->material()); } } } } void ModelObject::split(ModelObjectPtrs* new_objects) { if (this->volumes.size() > 1) { // We can't split meshes if there's more than one volume, because // we can't group the resulting meshes by object afterwards new_objects->push_back(this); return; } ModelVolume* volume = this->volumes.front(); TriangleMeshPtrs meshptrs = volume->mesh.split(); for (TriangleMesh *mesh : meshptrs) { // Snap the mesh to Z=0. float z0 = FLT_MAX; mesh->repair(); ModelObject* new_object = m_model->add_object(*this, false); new_object->input_file = ""; ModelVolume* new_volume = new_object->add_volume(*mesh); new_volume->name = volume->name; new_volume->config = volume->config; new_volume->modifier = volume->modifier; new_volume->material_id(volume->material_id()); new_objects->push_back(new_object); delete mesh; } return; } void ModelObject::print_info() const { using namespace std; cout << fixed; boost::nowide::cout << "[" << boost::filesystem::path(this->input_file).filename().string() << "]" << endl; TriangleMesh mesh = this->raw_mesh(); mesh.check_topology(); BoundingBoxf3 bb = mesh.bounding_box(); Sizef3 size = bb.size(); cout << "size_x = " << size.x << endl; cout << "size_y = " << size.y << endl; cout << "size_z = " << size.z << endl; cout << "min_x = " << bb.min.x << endl; cout << "min_y = " << bb.min.y << endl; cout << "min_z = " << bb.min.z << endl; cout << "max_x = " << bb.max.x << endl; cout << "max_y = " << bb.max.y << endl; cout << "max_z = " << bb.max.z << endl; cout << "number_of_facets = " << mesh.stl.stats.number_of_facets << endl; cout << "manifold = " << (mesh.is_manifold() ? "yes" : "no") << endl; mesh.repair(); // this calculates number_of_parts if (mesh.needed_repair()) { mesh.repair(); if (mesh.stl.stats.degenerate_facets > 0) cout << "degenerate_facets = " << mesh.stl.stats.degenerate_facets << endl; if (mesh.stl.stats.edges_fixed > 0) cout << "edges_fixed = " << mesh.stl.stats.edges_fixed << endl; if (mesh.stl.stats.facets_removed > 0) cout << "facets_removed = " << mesh.stl.stats.facets_removed << endl; if (mesh.stl.stats.facets_added > 0) cout << "facets_added = " << mesh.stl.stats.facets_added << endl; if (mesh.stl.stats.facets_reversed > 0) cout << "facets_reversed = " << mesh.stl.stats.facets_reversed << endl; if (mesh.stl.stats.backwards_edges > 0) cout << "backwards_edges = " << mesh.stl.stats.backwards_edges << endl; } cout << "number_of_parts = " << mesh.stl.stats.number_of_parts << endl; cout << "volume = " << mesh.volume() << endl; } void ModelVolume::material_id(t_model_material_id material_id) { this->_material_id = material_id; // ensure this->_material_id references an existing material (void)this->object->get_model()->add_material(material_id); } ModelMaterial* ModelVolume::material() const { return this->object->get_model()->get_material(this->_material_id); } void ModelVolume::set_material(t_model_material_id material_id, const ModelMaterial &material) { this->_material_id = material_id; (void)this->object->get_model()->add_material(material_id, material); } ModelMaterial* ModelVolume::assign_unique_material() { Model* model = this->get_object()->get_model(); // as material-id "0" is reserved by the AMF spec we start from 1 this->_material_id = 1 + model->materials.size(); // watchout for implicit cast return model->add_material(this->_material_id); } // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. size_t ModelVolume::split() { TriangleMeshPtrs meshptrs = this->mesh.split(); if (meshptrs.size() <= 1) { delete meshptrs.front(); return 1; } size_t idx = 0; size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin(); std::string name = this->name; for (TriangleMesh *mesh : meshptrs) { mesh->repair(); if (idx == 0) this->mesh = std::move(*mesh); else this->object->volumes.insert(this->object->volumes.begin() + (++ ivolume), new ModelVolume(object, *this, std::move(*mesh))); char str_idx[64]; sprintf(str_idx, "_%d", idx + 1); this->object->volumes[ivolume]->name = name + str_idx; delete mesh; ++ idx; } return idx; } void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { mesh->rotate_z(this->rotation); // rotate around mesh origin mesh->scale(this->scaling_factor); // scale around mesh origin if (!dont_translate) mesh->translate(this->offset.x, this->offset.y, 0); } BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mesh, bool dont_translate) const { // Rotate around mesh origin. double c = cos(this->rotation); double s = sin(this->rotation); BoundingBoxf3 bbox; for (int i = 0; i < mesh->stl.stats.number_of_facets; ++ i) { const stl_facet &facet = mesh->stl.facet_start[i]; for (int j = 0; j < 3; ++ j) { stl_vertex v = facet.vertex[j]; double xold = v.x; double yold = v.y; v.x = float(c * xold - s * yold); v.y = float(s * xold + c * yold); bbox.merge(Pointf3(v.x, v.y, v.z)); } } if (! empty(bbox)) { // Scale the bounding box uniformly. if (std::abs(this->scaling_factor - 1.) > EPSILON) { bbox.min.x *= float(this->scaling_factor); bbox.min.y *= float(this->scaling_factor); bbox.min.z *= float(this->scaling_factor); bbox.max.x *= float(this->scaling_factor); bbox.max.y *= float(this->scaling_factor); bbox.max.z *= float(this->scaling_factor); } // Translate the bounding box. if (! dont_translate) { bbox.min.x += float(this->offset.x); bbox.min.y += float(this->offset.y); bbox.max.x += float(this->offset.x); bbox.max.y += float(this->offset.y); } } return bbox; } BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const { // rotate around mesh origin double c = cos(this->rotation); double s = sin(this->rotation); Pointf3 pts[4] = { bbox.min, bbox.max, Pointf3(bbox.min.x, bbox.max.y, bbox.min.z), Pointf3(bbox.max.x, bbox.min.y, bbox.max.z) }; BoundingBoxf3 out; for (int i = 0; i < 4; ++ i) { Pointf3 &v = pts[i]; double xold = v.x; double yold = v.y; v.x = float(c * xold - s * yold); v.y = float(s * xold + c * yold); v.x *= this->scaling_factor; v.y *= this->scaling_factor; v.z *= this->scaling_factor; if (! dont_translate) { v.x += this->offset.x; v.y += this->offset.y; } out.merge(v); } return out; } void ModelInstance::transform_polygon(Polygon* polygon) const { polygon->rotate(this->rotation); // rotate around polygon origin polygon->scale(this->scaling_factor); // scale around polygon origin } } Slic3r-version_1.39.1/xs/src/libslic3r/Model.hpp000066400000000000000000000300741324354444700214070ustar00rootroot00000000000000#ifndef slic3r_Model_hpp_ #define slic3r_Model_hpp_ #include "libslic3r.h" #include "PrintConfig.hpp" #include "Layer.hpp" #include "Point.hpp" #include "TriangleMesh.hpp" #include "Slicing.hpp" #include #include #include #include namespace Slic3r { class Model; class ModelInstance; class ModelMaterial; class ModelObject; class ModelVolume; typedef std::string t_model_material_id; typedef std::string t_model_material_attribute; typedef std::map t_model_material_attributes; typedef std::map ModelMaterialMap; typedef std::vector ModelObjectPtrs; typedef std::vector ModelVolumePtrs; typedef std::vector ModelInstancePtrs; // Material, which may be shared across multiple ModelObjects of a single Model. class ModelMaterial { friend class Model; public: // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. t_model_material_attributes attributes; // Dynamic configuration storage for the object specific configuration values, overriding the global configuration. DynamicPrintConfig config; Model* get_model() const { return m_model; } void apply(const t_model_material_attributes &attributes) { this->attributes.insert(attributes.begin(), attributes.end()); } private: // Parent, owning this material. Model *m_model; ModelMaterial(Model *model) : m_model(model) {} ModelMaterial(Model *model, const ModelMaterial &other) : attributes(other.attributes), config(other.config), m_model(model) {} }; // A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), // and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. // Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, // different rotation and different uniform scaling. class ModelObject { friend class Model; public: std::string name; std::string input_file; // Instances of this ModelObject. Each instance defines a shift on the print bed, rotation around the Z axis and a uniform scaling. // Instances are owned by this ModelObject. ModelInstancePtrs instances; // Printable and modifier volumes, each with its material ID and a set of override parameters. // ModelVolumes are owned by this ModelObject. ModelVolumePtrs volumes; // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings. DynamicPrintConfig config; // Variation of a layer thickness for spans of Z coordinates. t_layer_height_ranges layer_height_ranges; // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. // The pairs of are packed into a 1D array to simplify handling by the Perl XS. std::vector layer_height_profile; // layer_height_profile is initialized when the layer editing mode is entered. // Only if the user really modified the layer height, layer_height_profile_valid is set // and used subsequently by the PrintObject. bool layer_height_profile_valid; /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation to new volumes before adding them to this object in order to preserve alignment when user expects that. */ Pointf3 origin_translation; Model* get_model() const { return m_model; }; ModelVolume* add_volume(const TriangleMesh &mesh); ModelVolume* add_volume(TriangleMesh &&mesh); ModelVolume* add_volume(const ModelVolume &volume); void delete_volume(size_t idx); void clear_volumes(); ModelInstance* add_instance(); ModelInstance* add_instance(const ModelInstance &instance); void delete_instance(size_t idx); void delete_last_instance(); void clear_instances(); // Returns the bounding box of the transformed instances. // This bounding box is approximate and not snug. BoundingBoxf3 bounding_box(); void invalidate_bounding_box() { m_bounding_box_valid = false; } // A mesh containing all transformed instances of this object. TriangleMesh mesh() const; // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. // Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D platter. TriangleMesh raw_mesh() const; // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. // This bounding box is only used for the actual slicing. BoundingBoxf3 raw_bounding_box() const; // A snug bounding box around the transformed non-modifier object volumes. BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; void center_around_origin(); void translate(const Vectorf3 &vector) { this->translate(vector.x, vector.y, vector.z); } void translate(coordf_t x, coordf_t y, coordf_t z); void scale(const Pointf3 &versor); void rotate(float angle, const Axis &axis); void mirror(const Axis &axis); size_t materials_count() const; size_t facets_count() const; bool needed_repair() const; void cut(coordf_t z, Model* model) const; void split(ModelObjectPtrs* new_objects); // Print object statistics to console. void print_info() const; private: ModelObject(Model *model) : layer_height_profile_valid(false), m_model(model), m_bounding_box_valid(false) {} ModelObject(Model *model, const ModelObject &other, bool copy_volumes = true); ModelObject& operator= (ModelObject other); void swap(ModelObject &other); ~ModelObject(); // Parent object, owning this ModelObject. Model *m_model; // Bounding box, cached. BoundingBoxf3 m_bounding_box; bool m_bounding_box_valid; }; // An object STL, or a modifier volume, over which a different set of parameters shall be applied. // ModelVolume instances are owned by a ModelObject. class ModelVolume { friend class ModelObject; public: std::string name; // The triangular model. TriangleMesh mesh; // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. DynamicPrintConfig config; // Is it an object to be printed, or a modifier volume? bool modifier; // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; }; t_model_material_id material_id() const { return this->_material_id; } void material_id(t_model_material_id material_id); ModelMaterial* material() const; void set_material(t_model_material_id material_id, const ModelMaterial &material); // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. // This is useful to assign different materials to different volumes of an object. size_t split(); ModelMaterial* assign_unique_material(); private: // Parent object owning this ModelVolume. ModelObject* object; t_model_material_id _material_id; ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object) {} ModelVolume(ModelObject *object, TriangleMesh &&mesh) : mesh(std::move(mesh)), modifier(false), object(object) {} ModelVolume(ModelObject *object, const ModelVolume &other) : name(other.name), mesh(other.mesh), config(other.config), modifier(other.modifier), object(object) { this->material_id(other.material_id()); } ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : name(other.name), mesh(std::move(mesh)), config(other.config), modifier(other.modifier), object(object) { this->material_id(other.material_id()); } }; // A single instance of a ModelObject. // Knows the affine transformation of an object. class ModelInstance { friend class ModelObject; public: double rotation; // Rotation around the Z axis, in radians around mesh center point double scaling_factor; Pointf offset; // in unscaled coordinates ModelObject* get_object() const { return this->object; }; // To be called on an external mesh void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; // Calculate a bounding box of a transformed mesh. To be called on an external mesh. BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh* mesh, bool dont_translate = false) const; // Transform an external bounding box. BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const; // To be called on an external polygon. It does not translate the polygon, only rotates and scales. void transform_polygon(Polygon* polygon) const; private: // Parent object, owning this instance. ModelObject* object; ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), object(object) {} ModelInstance(ModelObject *object, const ModelInstance &other) : rotation(other.rotation), scaling_factor(other.scaling_factor), offset(other.offset), object(object) {} }; // The print bed content. // Description of a triangular model with multiple materials, multiple instances with various affine transformations // and with multiple modifier meshes. // A model groups multiple objects, each object having possibly multiple instances, // all objects may share mutliple materials. class Model { public: // Materials are owned by a model and referenced by objects through t_model_material_id. // Single material may be shared by multiple models. ModelMaterialMap materials; // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation). ModelObjectPtrs objects; Model() {} Model(const Model &other); Model& operator= (Model other); void swap(Model &other); ~Model() { this->clear_objects(); this->clear_materials(); } static Model read_from_file(const std::string &input_file, bool add_default_instances = true); ModelObject* add_object(); ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); ModelObject* add_object(const ModelObject &other, bool copy_volumes = true); void delete_object(size_t idx); void clear_objects(); ModelMaterial* add_material(t_model_material_id material_id); ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other); ModelMaterial* get_material(t_model_material_id material_id) { ModelMaterialMap::iterator i = this->materials.find(material_id); return (i == this->materials.end()) ? nullptr : i->second; } void delete_material(t_model_material_id material_id); void clear_materials(); bool add_default_instances(); BoundingBoxf3 bounding_box(); void center_instances_around_point(const Pointf &point); void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } TriangleMesh mesh() const; bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); // Croaks if the duplicated objects do not fit the print bed. void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); bool looks_like_multipart_object() const; void convert_multipart_object(); void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/MotionPlanner.cpp000066400000000000000000000376051324354444700231360ustar00rootroot00000000000000#include "BoundingBox.hpp" #include "MotionPlanner.hpp" #include "MutablePriorityQueue.hpp" #include "Utils.hpp" #include // for numeric_limits #include #include "boost/polygon/voronoi.hpp" using boost::polygon::voronoi_builder; using boost::polygon::voronoi_diagram; namespace Slic3r { MotionPlanner::MotionPlanner(const ExPolygons &islands) : m_initialized(false) { ExPolygons expp; for (const ExPolygon &island : islands) { island.simplify(SCALED_EPSILON, &expp); for (ExPolygon &island : expp) m_islands.emplace_back(MotionPlannerEnv(island)); expp.clear(); } } void MotionPlanner::initialize() { // prevent initialization of empty BoundingBox if (m_initialized || m_islands.empty()) return; // loop through islands in order to create inner expolygons and collect their contours. Polygons outer_holes; for (MotionPlannerEnv &island : m_islands) { // Generate the internal env boundaries by shrinking the island // we'll use these inner rings for motion planning (endpoints of the Voronoi-based // graph, visibility check) in order to avoid moving too close to the boundaries. island.m_env = ExPolygonCollection(offset_ex(island.m_island, -MP_INNER_MARGIN)); // Island contours are holes of our external environment. outer_holes.push_back(island.m_island.contour); } // Generate a box contour around everyting. Polygons contour = offset(get_extents(outer_holes).polygon(), +MP_OUTER_MARGIN*2); assert(contour.size() == 1); // make expolygon for outer environment ExPolygons outer = diff_ex(contour, outer_holes); assert(outer.size() == 1); // If some of the islands are nested, then the 0th contour is the outer contour due to the order of conversion // from Clipper data structure into the Slic3r expolygons inside diff_ex(). m_outer = MotionPlannerEnv(outer.front()); m_outer.m_env = ExPolygonCollection(diff_ex(contour, offset(outer_holes, +MP_OUTER_MARGIN))); m_graphs.resize(m_islands.size() + 1); m_initialized = true; } Polyline MotionPlanner::shortest_path(const Point &from, const Point &to) { // If we have an empty configuration space, return a straight move. if (m_islands.empty()) return Line(from, to); // Are both points in the same island? int island_idx_from = -1; int island_idx_to = -1; int island_idx = -1; for (MotionPlannerEnv &island : m_islands) { int idx = &island - m_islands.data(); if (island.island_contains(from)) island_idx_from = idx; if (island.island_contains(to)) island_idx_to = idx; if (island_idx_from == idx && island_idx_to == idx) { // Since both points are in the same island, is a direct move possible? // If so, we avoid generating the visibility environment. if (island.m_island.contains(Line(from, to))) return Line(from, to); // Both points are inside a single island, but the straight line crosses the island boundary. island_idx = idx; break; } } // lazy generation of configuration space. this->initialize(); // Get environment. If the from / to points do not share an island, then they cross an open space, // therefore island_idx == -1 and env will be set to the environment of the empty space. const MotionPlannerEnv &env = this->get_env(island_idx); if (env.m_env.expolygons.empty()) { // if this environment is empty (probably because it's too small), perform straight move // and avoid running the algorithms on empty dataset return Line(from, to); } // Now check whether points are inside the environment. Point inner_from = from; Point inner_to = to; if (island_idx == -1) { // The end points do not share the same island. In that case some of the travel // will be likely performed inside the empty space. // TODO: instead of using the nearest_env_point() logic, we should // create a temporary graph where we connect 'from' and 'to' to the // nodes which don't require more than one crossing, and let Dijkstra // figure out the entire path - this should also replace the call to // find_node() below if (island_idx_from != -1) // The start point is inside some island. Find the closest point at the empty space to start from. inner_from = env.nearest_env_point(from, to); if (island_idx_to != -1) // The start point is inside some island. Find the closest point at the empty space to start from. inner_to = env.nearest_env_point(to, inner_from); } // Perform a path search either in the open space, or in a common island of from/to. const MotionPlannerGraph &graph = this->init_graph(island_idx); // If no path exists without crossing perimeters, returns a straight segment. Polyline polyline = graph.shortest_path(inner_from, inner_to); polyline.points.insert(polyline.points.begin(), from); polyline.points.emplace_back(to); { // grow our environment slightly in order for simplify_by_visibility() // to work best by considering moves on boundaries valid as well ExPolygonCollection grown_env(offset_ex(env.m_env.expolygons, float(+SCALED_EPSILON))); if (island_idx == -1) { /* If 'from' or 'to' are not inside our env, they were connected using the nearest_env_point() search which maybe produce ugly paths since it does not include the endpoint in the Dijkstra search; the simplify_by_visibility() call below will not work in many cases where the endpoint is not contained in grown_env (whose contour was arbitrarily constructed with MP_OUTER_MARGIN, which may not be enough for, say, including a skirt point). So we prune the extra points manually. */ if (! grown_env.contains(from)) { // delete second point while the line connecting first to third crosses the // boundaries as many times as the current first to second while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), grown_env).size() == 1) polyline.points.erase(polyline.points.begin() + 1); } if (! grown_env.contains(to)) while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), grown_env).size() == 1) polyline.points.erase(polyline.points.end() - 2); } // Perform some quick simplification (simplify_by_visibility() would make this // unnecessary, but this is much faster) polyline.simplify(MP_INNER_MARGIN/10); // remove unnecessary vertices // Note: this is computationally intensive and does not look very necessary // now that we prune the endpoints with the logic above, // so we comment it for now until a good test case arises //polyline.simplify_by_visibility(grown_env); /* SVG svg("shortest_path.svg"); svg.draw(grown_env.expolygons); svg.arrows = false; for (MotionPlannerGraph::adjacency_list_t::const_iterator it = graph->adjacency_list.begin(); it != graph->adjacency_list.end(); ++it) { Point a = graph->nodes[it - graph->adjacency_list.begin()]; for (std::vector::const_iterator n = it->begin(); n != it->end(); ++n) { Point b = graph->nodes[n->target]; svg.draw(Line(a, b)); } } svg.arrows = true; svg.draw(from); svg.draw(inner_from, "red"); svg.draw(to); svg.draw(inner_to, "red"); svg.draw(polyline, "red"); svg.Close(); */ } return polyline; } const MotionPlannerGraph& MotionPlanner::init_graph(int island_idx) { // 0th graph is the graph for m_outer. Other graphs are 1 indexed. MotionPlannerGraph *graph = m_graphs[island_idx + 1].get(); if (graph == nullptr) { // If this graph doesn't exist, initialize it. m_graphs[island_idx + 1] = make_unique(); graph = m_graphs[island_idx + 1].get(); /* We don't add polygon boundaries as graph edges, because we'd need to connect them to the Voronoi-generated edges by recognizing coinciding nodes. */ typedef voronoi_diagram VD; VD vd; // Mapping between Voronoi vertices and graph nodes. std::map vd_vertices; // get boundaries as lines const MotionPlannerEnv &env = this->get_env(island_idx); Lines lines = env.m_env.lines(); boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd); // traverse the Voronoi diagram and generate graph nodes and edges for (const VD::edge_type &edge : vd.edges()) { if (edge.is_infinite()) continue; const VD::vertex_type* v0 = edge.vertex0(); const VD::vertex_type* v1 = edge.vertex1(); Point p0(v0->x(), v0->y()); Point p1(v1->x(), v1->y()); // Insert only Voronoi edges fully contained in the island. //FIXME This test has a terrible O(n^2) time complexity. if (env.island_contains_b(p0) && env.island_contains_b(p1)) { // Find v0 in the graph, allocate a new node if v0 does not exist in the graph yet. auto i_v0 = vd_vertices.find(v0); size_t v0_idx; if (i_v0 == vd_vertices.end()) vd_vertices[v0] = v0_idx = graph->add_node(p0); else v0_idx = i_v0->second; // Find v1 in the graph, allocate a new node if v0 does not exist in the graph yet. auto i_v1 = vd_vertices.find(v1); size_t v1_idx; if (i_v1 == vd_vertices.end()) vd_vertices[v1] = v1_idx = graph->add_node(p1); else v1_idx = i_v1->second; // Euclidean distance is used as weight for the graph edge graph->add_edge(v0_idx, v1_idx, p0.distance_to(p1)); } } } return *graph; } // Find a middle point on the path from start_point to end_point with the shortest path. static inline size_t nearest_waypoint_index(const Point &start_point, const Points &middle_points, const Point &end_point) { size_t idx = size_t(-1); double dmin = std::numeric_limits::infinity(); for (const Point &p : middle_points) { double d = start_point.distance_to(p) + p.distance_to(end_point); if (d < dmin) { idx = &p - middle_points.data(); dmin = d; if (dmin < EPSILON) break; } } return idx; } Point MotionPlannerEnv::nearest_env_point(const Point &from, const Point &to) const { /* In order to ensure that the move between 'from' and the initial env point does not violate any of the configuration space boundaries, we limit our search to the points that satisfy this condition. */ /* Assume that this method is never called when 'env' contains 'from'; so 'from' is either inside a hole or outside all contours */ // get the points of the hole containing 'from', if any Points pp; for (const ExPolygon &ex : m_env.expolygons) { for (const Polygon &hole : ex.holes) if (hole.contains(from)) pp = hole; if (! pp.empty()) break; } // If 'from' is not inside a hole, it's outside of all contours, so take all contours' points. if (pp.empty()) for (const ExPolygon &ex : m_env.expolygons) append(pp, ex.contour.points); // Find the candidate result and check that it doesn't cross too many boundaries. while (pp.size() > 1) { // find the point in pp that is closest to both 'from' and 'to' size_t result = nearest_waypoint_index(from, pp, to); // as we assume 'from' is outside env, any node will require at least one crossing if (intersection_ln(Line(from, pp[result]), m_island).size() > 1) { // discard result pp.erase(pp.begin() + result); } else return pp[result]; } // if we're here, return last point if any (better than nothing) // if we have no points at all, then we have an empty environment and we // make this method behave as a no-op (we shouldn't get here by the way) return pp.empty() ? from : pp.front(); } // Add a new directed edge to the adjacency graph. void MotionPlannerGraph::add_edge(size_t from, size_t to, double weight) { // Extend adjacency list until this start node. if (m_adjacency_list.size() < from + 1) { // Reserve in powers of two to avoid repeated reallocation. m_adjacency_list.reserve(std::max(8, next_highest_power_of_2(from + 1))); // Allocate new empty adjacency vectors. m_adjacency_list.resize(from + 1); } m_adjacency_list[from].emplace_back(Neighbor(node_t(to), weight)); } // Dijkstra's shortest path in a weighted graph from node_start to node_end. // The returned path contains the end points. // If no path exists from node_start to node_end, a straight segment is returned. Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) const { // This prevents a crash in case for some reason we got here with an empty adjacency list. if (this->empty()) return Polyline(); // Dijkstra algorithm, previous node of the current node 'u' in the shortest path towards node_start. std::vector previous(m_adjacency_list.size(), -1); std::vector distance(m_adjacency_list.size(), std::numeric_limits::infinity()); std::vector map_node_to_queue_id(m_adjacency_list.size(), size_t(-1)); distance[node_start] = 0.; auto queue = make_mutable_priority_queue( [&map_node_to_queue_id](const node_t node, size_t idx) { map_node_to_queue_id[node] = idx; }, [&distance](const node_t node1, const node_t node2) { return distance[node1] < distance[node2]; }); queue.reserve(m_adjacency_list.size()); for (size_t i = 0; i < m_adjacency_list.size(); ++ i) queue.push(node_t(i)); while (! queue.empty()) { // Get the next node with the lowest distance to node_start. node_t u = node_t(queue.top()); queue.pop(); map_node_to_queue_id[u] = size_t(-1); // Stop searching if we reached our destination. if (u == node_end) break; // Visit each edge starting at node u. for (const Neighbor& neighbor : m_adjacency_list[u]) if (map_node_to_queue_id[neighbor.target] != size_t(-1)) { weight_t alt = distance[u] + neighbor.weight; // If total distance through u is shorter than the previous // distance (if any) between node_start and neighbor.target, replace it. if (alt < distance[neighbor.target]) { distance[neighbor.target] = alt; previous[neighbor.target] = u; queue.update(map_node_to_queue_id[neighbor.target]); } } } // In case the end point was not reached, previous[node_end] contains -1 // and a straight line from node_start to node_end is returned. Polyline polyline; polyline.points.reserve(m_adjacency_list.size()); for (node_t vertex = node_t(node_end); vertex != -1; vertex = previous[vertex]) polyline.points.emplace_back(m_nodes[vertex]); polyline.points.emplace_back(m_nodes[node_start]); polyline.reverse(); return polyline; } } Slic3r-version_1.39.1/xs/src/libslic3r/MotionPlanner.hpp000066400000000000000000000055601324354444700231360ustar00rootroot00000000000000#ifndef slic3r_MotionPlanner_hpp_ #define slic3r_MotionPlanner_hpp_ #include "libslic3r.h" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "ExPolygonCollection.hpp" #include "Polyline.hpp" #include #include #include #include #define MP_INNER_MARGIN scale_(1.0) #define MP_OUTER_MARGIN scale_(2.0) namespace Slic3r { class MotionPlanner; class MotionPlannerEnv { friend class MotionPlanner; public: MotionPlannerEnv() {}; MotionPlannerEnv(const ExPolygon &island) : m_island(island), m_island_bbox(get_extents(island)) {}; Point nearest_env_point(const Point &from, const Point &to) const; bool island_contains(const Point &pt) const { return m_island_bbox.contains(pt) && m_island.contains(pt); } bool island_contains_b(const Point &pt) const { return m_island_bbox.contains(pt) && m_island.contains_b(pt); } private: ExPolygon m_island; BoundingBox m_island_bbox; // Region, where the travel is allowed. ExPolygonCollection m_env; }; // A 2D directed graph for searching a shortest path using the famous Dijkstra algorithm. class MotionPlannerGraph { public: // Add a directed edge into the graph. size_t add_node(const Point &p) { m_nodes.emplace_back(p); return m_nodes.size() - 1; } void add_edge(size_t from, size_t to, double weight); size_t find_closest_node(const Point &point) const { return point.nearest_point_index(m_nodes); } bool empty() const { return m_adjacency_list.empty(); } Polyline shortest_path(size_t from, size_t to) const; Polyline shortest_path(const Point &from, const Point &to) const { return this->shortest_path(this->find_closest_node(from), this->find_closest_node(to)); } private: typedef int node_t; typedef double weight_t; struct Neighbor { Neighbor(node_t target, weight_t weight) : target(target), weight(weight) {} node_t target; weight_t weight; }; Points m_nodes; std::vector> m_adjacency_list; }; class MotionPlanner { public: MotionPlanner(const ExPolygons &islands); ~MotionPlanner() {} Polyline shortest_path(const Point &from, const Point &to); size_t islands_count() const { return m_islands.size(); } private: bool m_initialized; std::vector m_islands; MotionPlannerEnv m_outer; // 0th graph is the graph for m_outer. Other graphs are 1 indexed. std::vector> m_graphs; void initialize(); const MotionPlannerGraph& init_graph(int island_idx); const MotionPlannerEnv& get_env(int island_idx) const { return (island_idx == -1) ? m_outer : m_islands[island_idx]; } }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/MultiPoint.cpp000066400000000000000000000151411324354444700224440ustar00rootroot00000000000000#include "MultiPoint.hpp" #include "BoundingBox.hpp" namespace Slic3r { MultiPoint::operator Points() const { return this->points; } void MultiPoint::scale(double factor) { for (Points::iterator it = points.begin(); it != points.end(); ++it) { (*it).scale(factor); } } void MultiPoint::translate(double x, double y) { for (Points::iterator it = points.begin(); it != points.end(); ++it) { (*it).translate(x, y); } } void MultiPoint::translate(const Point &vector) { this->translate(vector.x, vector.y); } void MultiPoint::rotate(double cos_angle, double sin_angle) { for (Point &pt : this->points) { double cur_x = double(pt.x); double cur_y = double(pt.y); pt.x = coord_t(round(cos_angle * cur_x - sin_angle * cur_y)); pt.y = coord_t(round(cos_angle * cur_y + sin_angle * cur_x)); } } void MultiPoint::rotate(double angle, const Point ¢er) { double s = sin(angle); double c = cos(angle); for (Points::iterator it = points.begin(); it != points.end(); ++it) { double dx = double(it->x - center.x); double dy = double(it->y - center.y); it->x = (coord_t)round(double(center.x) + c * dx - s * dy); it->y = (coord_t)round(double(center.y) + c * dy + s * dx); } } void MultiPoint::reverse() { std::reverse(this->points.begin(), this->points.end()); } Point MultiPoint::first_point() const { return this->points.front(); } double MultiPoint::length() const { Lines lines = this->lines(); double len = 0; for (Lines::iterator it = lines.begin(); it != lines.end(); ++it) { len += it->length(); } return len; } int MultiPoint::find_point(const Point &point) const { for (Points::const_iterator it = this->points.begin(); it != this->points.end(); ++it) { if (it->coincides_with(point)) return it - this->points.begin(); } return -1; // not found } bool MultiPoint::has_boundary_point(const Point &point) const { double dist = point.distance_to(point.projection_onto(*this)); return dist < SCALED_EPSILON; } BoundingBox MultiPoint::bounding_box() const { return BoundingBox(this->points); } bool MultiPoint::has_duplicate_points() const { for (size_t i = 1; i < points.size(); ++i) if (points[i-1].coincides_with(points[i])) return true; return false; } bool MultiPoint::remove_duplicate_points() { size_t j = 0; for (size_t i = 1; i < points.size(); ++i) { if (points[j].coincides_with(points[i])) { // Just increase index i. } else { ++ j; if (j < i) points[j] = points[i]; } } if (++ j < points.size()) { points.erase(points.begin() + j, points.end()); return true; } return false; } bool MultiPoint::intersection(const Line& line, Point* intersection) const { Lines lines = this->lines(); for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { if (it->intersection(line, intersection)) return true; } return false; } bool MultiPoint::first_intersection(const Line& line, Point* intersection) const { bool found = false; double dmin = 0.; for (const Line &l : this->lines()) { Point ip; if (l.intersection(line, &ip)) { if (! found) { found = true; dmin = ip.distance_to(line.a); *intersection = ip; } else { double d = ip.distance_to(line.a); if (d < dmin) { dmin = d; *intersection = ip; } } } } return found; } std::string MultiPoint::dump_perl() const { std::ostringstream ret; ret << "["; for (Points::const_iterator p = this->points.begin(); p != this->points.end(); ++p) { ret << p->dump_perl(); if (p != this->points.end()-1) ret << ","; } ret << "]"; return ret.str(); } //FIXME This is very inefficient in term of memory use. // The recursive algorithm shall run in place, not allocating temporary data in each recursion. Points MultiPoint::_douglas_peucker(const Points &points, const double tolerance) { assert(points.size() >= 2); Points results; double dmax = 0; size_t index = 0; Line full(points.front(), points.back()); for (Points::const_iterator it = points.begin() + 1; it != points.end(); ++it) { // we use shortest distance, not perpendicular distance double d = it->distance_to(full); if (d > dmax) { index = it - points.begin(); dmax = d; } } if (dmax >= tolerance) { Points dp0; dp0.reserve(index + 1); dp0.insert(dp0.end(), points.begin(), points.begin() + index + 1); // Recursive call. Points dp1 = MultiPoint::_douglas_peucker(dp0, tolerance); results.reserve(results.size() + dp1.size() - 1); results.insert(results.end(), dp1.begin(), dp1.end() - 1); dp0.clear(); dp0.reserve(points.size() - index); dp0.insert(dp0.end(), points.begin() + index, points.end()); // Recursive call. dp1 = MultiPoint::_douglas_peucker(dp0, tolerance); results.reserve(results.size() + dp1.size()); results.insert(results.end(), dp1.begin(), dp1.end()); } else { results.push_back(points.front()); results.push_back(points.back()); } return results; } BoundingBox get_extents(const MultiPoint &mp) { return BoundingBox(mp.points); } BoundingBox get_extents_rotated(const Points &points, double angle) { BoundingBox bbox; if (! points.empty()) { double s = sin(angle); double c = cos(angle); Points::const_iterator it = points.begin(); double cur_x = (double)it->x; double cur_y = (double)it->y; bbox.min.x = bbox.max.x = (coord_t)round(c * cur_x - s * cur_y); bbox.min.y = bbox.max.y = (coord_t)round(c * cur_y + s * cur_x); for (++it; it != points.end(); ++it) { double cur_x = (double)it->x; double cur_y = (double)it->y; coord_t x = (coord_t)round(c * cur_x - s * cur_y); coord_t y = (coord_t)round(c * cur_y + s * cur_x); bbox.min.x = std::min(x, bbox.min.x); bbox.min.y = std::min(y, bbox.min.y); bbox.max.x = std::max(x, bbox.max.x); bbox.max.y = std::max(y, bbox.max.y); } bbox.defined = true; } return bbox; } BoundingBox get_extents_rotated(const MultiPoint &mp, double angle) { return get_extents_rotated(mp.points, angle); } } Slic3r-version_1.39.1/xs/src/libslic3r/MultiPoint.hpp000066400000000000000000000063111324354444700224500ustar00rootroot00000000000000#ifndef slic3r_MultiPoint_hpp_ #define slic3r_MultiPoint_hpp_ #include "libslic3r.h" #include #include #include "Line.hpp" #include "Point.hpp" namespace Slic3r { class BoundingBox; class MultiPoint { public: Points points; operator Points() const; MultiPoint() {}; MultiPoint(const MultiPoint &other) : points(other.points) {} MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {} explicit MultiPoint(const Points &_points): points(_points) {} MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; } MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } void scale(double factor); void translate(double x, double y); void translate(const Point &vector); void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } void rotate(double cos_angle, double sin_angle); void rotate(double angle, const Point ¢er); void reverse(); Point first_point() const; virtual Point last_point() const = 0; virtual Lines lines() const = 0; double length() const; bool is_valid() const { return this->points.size() >= 2; } int find_point(const Point &point) const; bool has_boundary_point(const Point &point) const; int closest_point_index(const Point &point) const { int idx = -1; if (! this->points.empty()) { idx = 0; double dist_min = this->points.front().distance_to(point); for (int i = 1; i < int(this->points.size()); ++ i) { double d = this->points[i].distance_to(point); if (d < dist_min) { dist_min = d; idx = i; } } } return idx; } const Point* closest_point(const Point &point) const { return this->points.empty() ? nullptr : &this->points[this->closest_point_index(point)]; } BoundingBox bounding_box() const; // Return true if there are exact duplicates. bool has_duplicate_points() const; // Remove exact duplicates, return true if any duplicate has been removed. bool remove_duplicate_points(); void append(const Point &point) { this->points.push_back(point); } void append(const Points &src) { this->append(src.begin(), src.end()); } void append(const Points::const_iterator &begin, const Points::const_iterator &end) { this->points.insert(this->points.end(), begin, end); } void append(Points &&src) { if (this->points.empty()) { this->points = std::move(src); } else { this->points.insert(this->points.end(), src.begin(), src.end()); src.clear(); } } bool intersection(const Line& line, Point* intersection) const; bool first_intersection(const Line& line, Point* intersection) const; std::string dump_perl() const; static Points _douglas_peucker(const Points &points, const double tolerance); }; extern BoundingBox get_extents(const MultiPoint &mp); extern BoundingBox get_extents_rotated(const std::vector &points, double angle); extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle); } // namespace Slic3r #endif Slic3r-version_1.39.1/xs/src/libslic3r/MutablePriorityQueue.hpp000066400000000000000000000104061324354444700245040ustar00rootroot00000000000000#ifndef slic3r_MutablePriorityQueue_hpp_ #define slic3r_MutablePriorityQueue_hpp_ #include template class MutablePriorityQueue { public: MutablePriorityQueue(IndexSetter &&index_setter, LessPredicate &&less_predicate) : m_index_setter(std::forward(index_setter)), m_less_predicate(std::forward(less_predicate)) {} ~MutablePriorityQueue() { clear(); } inline void clear() { m_heap.clear(); } inline void reserve(size_t cnt) { m_heap.reserve(cnt); } inline void push(const T &item); inline void push(T &&item); inline void pop(); inline T& top() { return m_heap.front(); } inline void remove(size_t idx); inline void update(size_t idx) { T item = m_heap[idx]; remove(idx); push(item); } inline size_t size() const { return m_heap.size(); } inline bool empty() const { return m_heap.empty(); } protected: inline void update_heap_up(size_t top, size_t bottom); inline void update_heap_down(size_t top, size_t bottom); private: std::vector m_heap; IndexSetter m_index_setter; LessPredicate m_less_predicate; }; template MutablePriorityQueue make_mutable_priority_queue(IndexSetter &&index_setter, LessPredicate &&less_predicate) { return MutablePriorityQueue( std::forward(index_setter), std::forward(less_predicate)); } template inline void MutablePriorityQueue::push(const T &item) { size_t idx = m_heap.size(); m_heap.emplace_back(item); m_index_setter(m_heap.back(), idx); update_heap_up(0, idx); } template inline void MutablePriorityQueue::push(T &&item) { size_t idx = m_heap.size(); m_heap.emplace_back(std::move(item)); m_index_setter(m_heap.back(), idx); update_heap_up(0, idx); } template inline void MutablePriorityQueue::pop() { assert(! m_heap.empty()); if (m_heap.size() > 1) { m_heap.front() = m_heap.back(); m_heap.pop_back(); m_index_setter(m_heap.front(), 0); update_heap_down(0, m_heap.size() - 1); } else m_heap.clear(); } template inline void MutablePriorityQueue::remove(size_t idx) { assert(idx < m_heap.size()); if (idx + 1 == m_heap.size()) { m_heap.pop_back(); return; } m_heap[idx] = m_heap.back(); m_index_setter(m_heap[idx], idx); m_heap.pop_back(); update_heap_down(idx, m_heap.size() - 1); update_heap_up(0, idx); } template inline void MutablePriorityQueue::update_heap_up(size_t top, size_t bottom) { size_t childIdx = bottom; T *child = &m_heap[childIdx]; for (;;) { size_t parentIdx = (childIdx - 1) >> 1; if (childIdx == 0 || parentIdx < top) break; T *parent = &m_heap[parentIdx]; // switch nodes if (! m_less_predicate(*parent, *child)) { T tmp = *parent; m_index_setter(*parent, childIdx); m_index_setter(*child, parentIdx); m_heap[parentIdx] = *child; m_heap[childIdx] = tmp; } // shift up childIdx = parentIdx; child = parent; } } template inline void MutablePriorityQueue::update_heap_down(size_t top, size_t bottom) { size_t parentIdx = top; T *parent = &m_heap[parentIdx]; for (;;) { size_t childIdx = (parentIdx << 1) + 1; if (childIdx > bottom) break; T *child = &m_heap[childIdx]; size_t child2Idx = childIdx + 1; if (child2Idx <= bottom) { T *child2 = &m_heap[child2Idx]; if (! m_less_predicate(*child, *child2)) { child = child2; childIdx = child2Idx; } } if (m_less_predicate(*parent, *child)) return; // switch nodes m_index_setter(*parent, childIdx); m_index_setter(*child, parentIdx); T tmp = *parent; m_heap[parentIdx] = *child; m_heap[childIdx] = tmp; // shift down parentIdx = childIdx; parent = child; } } #endif /* slic3r_MutablePriorityQueue_hpp_ */ Slic3r-version_1.39.1/xs/src/libslic3r/PerimeterGenerator.cpp000066400000000000000000000612211324354444700241430ustar00rootroot00000000000000#include "PerimeterGenerator.hpp" #include "ClipperUtils.hpp" #include "ExtrusionEntityCollection.hpp" #include #include namespace Slic3r { void PerimeterGenerator::process() { // other perimeters this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); coord_t perimeter_width = this->perimeter_flow.scaled_width(); coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); // external perimeters this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow); // overhang perimeters this->_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); // solid infill coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); // Calculate the minimum required spacing between two adjacent traces. // This should be equal to the nominal flow spacing but we experiment // with some tolerance in order to avoid triggering medial axis when // some squishing might work. Loops are still spaced by the entire // flow spacing; this only applies to collapsing parts. // For ext_min_spacing we use the ext_perimeter_spacing calculated for two adjacent // external loops (which is the correct way) instead of using ext_perimeter_spacing2 // which is the spacing between external and internal, which is not correct // and would make the collapsing (thus the details resolution) dependent on // internal flow which is unrelated. coord_t min_spacing = perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE); coord_t ext_min_spacing = ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE); // prepare grown lower layer slices for overhang detection if (this->lower_slices != NULL && this->config->overhangs) { // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1); this->_lower_slices_p = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2))); } // we need to process each island separately because we might have different // extra perimeters for each one for (const Surface &surface : this->slices->surfaces) { // detect how many perimeters must be generated for this island const int loop_number = this->config->perimeters + surface.extra_perimeters -1; // 0-indexed loops Polygons gaps; Polygons last = surface.expolygon.simplify_p(SCALED_RESOLUTION); if (loop_number >= 0) { // no loops = -1 std::vector contours(loop_number+1); // depth => loops std::vector holes(loop_number+1); // depth => loops ThickPolylines thin_walls; // we loop one time more than needed in order to find gaps after the last perimeter was applied for (int i = 0; i <= loop_number+1; ++i) { // outer loop is 0 Polygons offsets; if (i == 0) { // the minimum thickness of a single loop is: // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 if (this->config->thin_walls) { offsets = offset2( last, -(ext_perimeter_width / 2 + ext_min_spacing / 2 - 1), +(ext_min_spacing/2 - 1) ); } else { offsets = offset(last, - ext_perimeter_width / 2); } // look for thin walls if (this->config->thin_walls) { Polygons diffpp = diff( last, offset(offsets, ext_perimeter_width / 2), true // medial axis requires non-overlapping geometry ); // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // (actually, something larger than that still may exist due to mitering or other causes) coord_t min_width = scale_(this->ext_perimeter_flow.nozzle_diameter / 3); ExPolygons expp = offset2_ex(diffpp, -min_width/2, +min_width/2); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) ex->medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); #ifdef DEBUG printf(" " PRINTF_ZU " thin walls detected\n", thin_walls.size()); #endif /* if (false) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "medial_axis.svg", no_arrows => 1, #expolygons => \@expp, polylines => \@thin_walls, ); } */ } } else { //FIXME Is this offset correct if the line width of the inner perimeters differs // from the line width of the infill? coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing; if (this->config->thin_walls) { // This path will ensure, that the perimeters do not overfill, as in // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters // excessively, creating gaps, which then need to be filled in by the not very // reliable gap fill algorithm. // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than // the original. offsets = offset2( last, -(distance + min_spacing/2 - 1), +(min_spacing/2 - 1) ); } else { // If "detect thin walls" is not enabled, this paths will be entered, which // leads to overflows, as in prusa3d/Slic3r GH #32 offsets = offset( last, -distance ); } // look for gaps if (this->config->gap_fill_speed.value > 0 && this->config->fill_density.value > 0) { // not using safety offset here would "detect" very narrow gaps // (but still long enough to escape the area threshold) that gap fill // won't be able to fill but we'd still remove from infill area Polygons diff_pp = diff( offset(last, -0.5*distance), offset(offsets, +0.5*distance + 10) // safety offset ); gaps.insert(gaps.end(), diff_pp.begin(), diff_pp.end()); } } if (offsets.empty()) break; if (i > loop_number) break; // we were only looking for gaps this time last = offsets; for (Polygons::const_iterator polygon = offsets.begin(); polygon != offsets.end(); ++polygon) { PerimeterGeneratorLoop loop(*polygon, i); loop.is_contour = polygon->is_counter_clockwise(); if (loop.is_contour) { contours[i].push_back(loop); } else { holes[i].push_back(loop); } } } // nest loops: holes first for (int d = 0; d <= loop_number; ++d) { PerimeterGeneratorLoops &holes_d = holes[d]; // loop through all holes having depth == d for (int i = 0; i < (int)holes_d.size(); ++i) { const PerimeterGeneratorLoop &loop = holes_d[i]; // find the hole loop that contains this one, if any for (int t = d+1; t <= loop_number; ++t) { for (int j = 0; j < (int)holes[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = holes[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); holes_d.erase(holes_d.begin() + i); --i; goto NEXT_LOOP; } } } // if no hole contains this hole, find the contour loop that contains it for (int t = loop_number; t >= 0; --t) { for (int j = 0; j < (int)contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); holes_d.erase(holes_d.begin() + i); --i; goto NEXT_LOOP; } } } NEXT_LOOP: ; } } // nest contour loops for (int d = loop_number; d >= 1; --d) { PerimeterGeneratorLoops &contours_d = contours[d]; // loop through all contours having depth == d for (int i = 0; i < (int)contours_d.size(); ++i) { const PerimeterGeneratorLoop &loop = contours_d[i]; // find the contour loop that contains it for (int t = d-1; t >= 0; --t) { for (int j = 0; j < contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); contours_d.erase(contours_d.begin() + i); --i; goto NEXT_CONTOUR; } } } NEXT_CONTOUR: ; } } // at this point, all loops should be in contours[0] ExtrusionEntityCollection entities = this->_traverse_loops(contours.front(), thin_walls); // if brim will be printed, reverse the order of perimeters so that // we continue inwards after having finished the brim // TODO: add test for perimeter order if (this->config->external_perimeters_first || (this->layer_id == 0 && this->print_config->brim_width.value > 0)) entities.reverse(); // append perimeters for this slice as a collection if (!entities.empty()) this->loops->append(entities); } // for each loop of an island // fill gaps if (!gaps.empty()) { /* SVG svg("gaps.svg"); svg.draw(union_ex(gaps)); svg.Close(); */ // collapse double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); double max = 2. * perimeter_spacing; ExPolygons gaps_ex = diff_ex( offset2(gaps, -min/2, +min/2), offset2(gaps, -max/2, +max/2), true ); ThickPolylines polylines; for (ExPolygons::const_iterator ex = gaps_ex.begin(); ex != gaps_ex.end(); ++ex) ex->medial_axis(max, min, &polylines); if (!polylines.empty()) { ExtrusionEntityCollection gap_fill = this->_variable_width(polylines, erGapFill, this->solid_infill_flow); this->gap_fill->append(gap_fill.entities); /* Make sure we don't infill narrow parts that are already gap-filled (we only consider this surface's gaps to reduce the diff() complexity). Growing actual extrusions ensures that gaps not filled by medial axis are not subtracted from fill surfaces (they might be too short gaps that medial axis skips but infill might join with other infill regions and use zigzag). */ //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing, // therefore it may cover the area, but no the volume. last = diff(last, gap_fill.polygons_covered_by_width(10.f)); } } // create one more offset to be used as boundary for fill // we offset by half the perimeter spacing (to get to the actual infill boundary) // and then we offset back and forth by half the infill spacing to only consider the // non-collapsing regions coord_t inset = 0; if (loop_number == 0) { // one loop inset += ext_perimeter_spacing / 2; } else if (loop_number > 0) { // two or more loops inset += perimeter_spacing / 2; } // only apply infill overlap if we actually have one perimeter if (inset > 0) inset -= this->config->get_abs_value("infill_overlap", inset + solid_infill_spacing / 2); // simplify infill contours according to resolution Polygons pp; for (ExPolygon &ex : union_ex(last)) ex.simplify_p(SCALED_RESOLUTION, &pp); // collapse too narrow infill areas coord_t min_perimeter_infill_spacing = solid_infill_spacing * (1 - INSET_OVERLAP_TOLERANCE); // append infill areas to fill_surfaces this->fill_surfaces->append( offset2_ex( pp, -inset -min_perimeter_infill_spacing/2, +min_perimeter_infill_spacing/2), stInternal); } // for each island } ExtrusionEntityCollection PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const { // loops is an arrayref of ::Loop objects // turn each one into an ExtrusionLoop object ExtrusionEntityCollection coll; for (PerimeterGeneratorLoops::const_iterator loop = loops.begin(); loop != loops.end(); ++loop) { bool is_external = loop->is_external(); ExtrusionRole role; ExtrusionLoopRole loop_role; role = is_external ? erExternalPerimeter : erPerimeter; if (loop->is_internal_contour()) { // Note that we set loop role to ContourInternalPerimeter // also when loop is both internal and external (i.e. // there's only one contour loop). loop_role = elrContourInternalPerimeter; } else { loop_role = elrDefault; } // detect overhanging/bridging perimeters ExtrusionPaths paths; if (this->config->overhangs && this->layer_id > 0 && !(this->object_config->support_material && this->object_config->support_material_contact_distance.value == 0)) { // get non-overhang paths by intersecting this loop with the grown lower slices extrusion_paths_append( paths, intersection_pl(loop->polygon, this->_lower_slices_p), role, is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm, is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width, this->layer_height); // get overhang paths by checking what parts of this loop fall // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter extrusion_paths_append( paths, diff_pl(loop->polygon, this->_lower_slices_p), erOverhangPerimeter, this->_mm3_per_mm_overhang, this->overhang_flow.width, this->overhang_flow.height); // reapply the nearest point search for starting point // We allow polyline reversal because Clipper may have randomly // reversed polylines during clipping. paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path(); } else { ExtrusionPath path(role); path.polyline = loop->polygon.split_at_first_point(); path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm; path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width; path.height = this->layer_height; paths.push_back(path); } coll.append(ExtrusionLoop(paths, loop_role)); } // append thin walls to the nearest-neighbor search (only for first iteration) if (!thin_walls.empty()) { ExtrusionEntityCollection tw = this->_variable_width (thin_walls, erExternalPerimeter, this->ext_perimeter_flow); coll.append(tw.entities); thin_walls.clear(); } // sort entities into a new collection using a nearest-neighbor search, // preserving the original indices which are useful for detecting thin walls ExtrusionEntityCollection sorted_coll; coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices); // traverse children and build the final collection ExtrusionEntityCollection entities; for (std::vector::const_iterator idx = sorted_coll.orig_indices.begin(); idx != sorted_coll.orig_indices.end(); ++idx) { if (*idx >= loops.size()) { // this is a thin wall // let's get it from the sorted collection as it might have been reversed size_t i = idx - sorted_coll.orig_indices.begin(); entities.append(*sorted_coll.entities[i]); } else { const PerimeterGeneratorLoop &loop = loops[*idx]; ExtrusionLoop eloop = *dynamic_cast(coll.entities[*idx]); ExtrusionEntityCollection children = this->_traverse_loops(loop.children, thin_walls); if (loop.is_contour) { eloop.make_counter_clockwise(); entities.append(children.entities); entities.append(eloop); } else { eloop.make_clockwise(); entities.append(eloop); entities.append(children.entities); } } } return entities; } ExtrusionEntityCollection PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const { // this value determines granularity of adaptive width, as G-code does not allow // variable extrusion within a single move; this value shall only affect the amount // of segments, and any pruning shall be performed before we apply this tolerance const double tolerance = scale_(0.05); ExtrusionEntityCollection coll; for (ThickPolylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) { ExtrusionPaths paths; ExtrusionPath path(role); ThickLines lines = p->thicklines(); for (int i = 0; i < (int)lines.size(); ++i) { const ThickLine& line = lines[i]; const coordf_t line_len = line.length(); if (line_len < SCALED_EPSILON) continue; double thickness_delta = fabs(line.a_width - line.b_width); if (thickness_delta > tolerance) { const unsigned short segments = ceil(thickness_delta / tolerance); const coordf_t seg_len = line_len / segments; Points pp; std::vector width; { pp.push_back(line.a); width.push_back(line.a_width); for (size_t j = 1; j < segments; ++j) { pp.push_back(line.point_at(j*seg_len)); coordf_t w = line.a_width + (j*seg_len) * (line.b_width-line.a_width) / line_len; width.push_back(w); width.push_back(w); } pp.push_back(line.b); width.push_back(line.b_width); assert(pp.size() == segments + 1); assert(width.size() == segments*2); } // delete this line and insert new ones lines.erase(lines.begin() + i); for (size_t j = 0; j < segments; ++j) { ThickLine new_line(pp[j], pp[j+1]); new_line.a_width = width[2*j]; new_line.b_width = width[2*j+1]; lines.insert(lines.begin() + i + j, new_line); } --i; continue; } const double w = fmax(line.a_width, line.b_width); if (path.polyline.points.empty()) { path.polyline.append(line.a); path.polyline.append(line.b); // Convert from spacing to extrusion width based on the extrusion model // of a square extrusion ended with semi circles. flow.width = unscale(w) + flow.height * (1. - 0.25 * PI); #ifdef SLIC3R_DEBUG printf(" filling %f gap\n", flow.width); #endif path.mm3_per_mm = flow.mm3_per_mm(); path.width = flow.width; path.height = flow.height; } else { thickness_delta = fabs(scale_(flow.width) - w); if (thickness_delta <= tolerance) { // the width difference between this line and the current flow width is // within the accepted tolerance path.polyline.append(line.b); } else { // we need to initialize a new line paths.push_back(path); path = ExtrusionPath(role); --i; } } } if (path.polyline.is_valid()) paths.push_back(path); // append paths to collection if (!paths.empty()) { if (paths.front().first_point().coincides_with(paths.back().last_point())) coll.append(ExtrusionLoop(paths)); else coll.append(paths); } } return coll; } bool PerimeterGeneratorLoop::is_internal_contour() const { if (this->is_contour) { // an internal contour is a contour containing no other contours for (std::vector::const_iterator loop = this->children.begin(); loop != this->children.end(); ++loop) { if (loop->is_contour) { return false; } } return true; } return false; } } Slic3r-version_1.39.1/xs/src/libslic3r/PerimeterGenerator.hpp000066400000000000000000000063221324354444700241510ustar00rootroot00000000000000#ifndef slic3r_PerimeterGenerator_hpp_ #define slic3r_PerimeterGenerator_hpp_ #include "libslic3r.h" #include #include "ExPolygonCollection.hpp" #include "Flow.hpp" #include "Polygon.hpp" #include "PrintConfig.hpp" #include "SurfaceCollection.hpp" namespace Slic3r { // Hierarchy of perimeters. class PerimeterGeneratorLoop { public: // Polygon of this contour. Polygon polygon; // Is it a contour or a hole? // Contours are CCW oriented, holes are CW oriented. bool is_contour; // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole. unsigned short depth; // Children contour, may be both CCW and CW oriented (outer contours or holes). std::vector children; PerimeterGeneratorLoop(Polygon polygon, unsigned short depth) : polygon(polygon), is_contour(false), depth(depth) {}; // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). bool is_external() const { return this->depth == 0; } // An island, which may have holes, but it does not have another internal island. bool is_internal_contour() const; }; typedef std::vector PerimeterGeneratorLoops; class PerimeterGenerator { public: // Inputs: const SurfaceCollection* slices; const ExPolygonCollection* lower_slices; double layer_height; int layer_id; Flow perimeter_flow; Flow ext_perimeter_flow; Flow overhang_flow; Flow solid_infill_flow; PrintRegionConfig* config; PrintObjectConfig* object_config; PrintConfig* print_config; // Outputs: ExtrusionEntityCollection* loops; ExtrusionEntityCollection* gap_fill; SurfaceCollection* fill_surfaces; PerimeterGenerator( // Input: const SurfaceCollection* slices, double layer_height, Flow flow, PrintRegionConfig* config, PrintObjectConfig* object_config, PrintConfig* print_config, // Output: // Loops with the external thin walls ExtrusionEntityCollection* loops, // Gaps without the thin walls ExtrusionEntityCollection* gap_fill, // Infills without the gap fills SurfaceCollection* fill_surfaces) : slices(slices), lower_slices(NULL), layer_height(layer_height), layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow), overhang_flow(flow), solid_infill_flow(flow), config(config), object_config(object_config), print_config(print_config), loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), _ext_mm3_per_mm(-1), _mm3_per_mm(-1), _mm3_per_mm_overhang(-1) {}; void process(); private: double _ext_mm3_per_mm; double _mm3_per_mm; double _mm3_per_mm_overhang; Polygons _lower_slices_p; ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const; ExtrusionEntityCollection _variable_width (const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const; }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/PlaceholderParser.cpp000066400000000000000000001604131324354444700237420ustar00rootroot00000000000000#include "PlaceholderParser.hpp" #include #include #include #include #include #ifdef _MSC_VER #include // provides **_environ #else #include // provides **environ #endif #ifdef __APPLE__ #include #undef environ #define environ (*_NSGetEnviron()) #else #ifdef _MSC_VER #define environ _environ #else extern char **environ; #endif #endif #include // Spirit v2.5 allows you to suppress automatic generation // of predefined terminals to speed up complation. With // BOOST_SPIRIT_NO_PREDEFINED_TERMINALS defined, you are // responsible in creating instances of the terminals that // you need (e.g. see qi::uint_type uint_ below). //#define BOOST_SPIRIT_NO_PREDEFINED_TERMINALS #define BOOST_RESULT_OF_USE_DECLTYPE #define BOOST_SPIRIT_USE_PHOENIX_V3 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // #define USE_CPP11_REGEX #ifdef USE_CPP11_REGEX #include #define SLIC3R_REGEX_NAMESPACE std #else /* USE_CPP11_REGEX */ #include #define SLIC3R_REGEX_NAMESPACE boost #endif /* USE_CPP11_REGEX */ namespace Slic3r { PlaceholderParser::PlaceholderParser() { this->set("version", std::string(SLIC3R_VERSION)); this->apply_env_variables(); this->update_timestamp(); } void PlaceholderParser::update_timestamp() { time_t rawtime; time(&rawtime); struct tm* timeinfo = localtime(&rawtime); { std::ostringstream ss; ss << (1900 + timeinfo->tm_year); ss << std::setw(2) << std::setfill('0') << (1 + timeinfo->tm_mon); ss << std::setw(2) << std::setfill('0') << timeinfo->tm_mday; ss << "-"; ss << std::setw(2) << std::setfill('0') << timeinfo->tm_hour; ss << std::setw(2) << std::setfill('0') << timeinfo->tm_min; ss << std::setw(2) << std::setfill('0') << timeinfo->tm_sec; this->set("timestamp", ss.str()); } this->set("year", 1900 + timeinfo->tm_year); this->set("month", 1 + timeinfo->tm_mon); this->set("day", timeinfo->tm_mday); this->set("hour", timeinfo->tm_hour); this->set("minute", timeinfo->tm_min); this->set("second", timeinfo->tm_sec); } // Scalar configuration values are stored into m_single, // vector configuration values are stored into m_multiple. // All vector configuration values stored into the PlaceholderParser // are expected to be addressed by the extruder ID, therefore // if a vector configuration value is addressed without an index, // a current extruder ID is used. void PlaceholderParser::apply_config(const DynamicPrintConfig &rhs) { const ConfigDef *def = rhs.def(); for (const t_config_option_key &opt_key : rhs.keys()) { const ConfigOptionDef *opt_def = def->get(opt_key); if ((opt_def->multiline && boost::ends_with(opt_key, "_gcode")) || opt_key == "post_process") continue; const ConfigOption *opt = rhs.option(opt_key); // Store a copy of the config option. // Convert FloatOrPercent values to floats first. //FIXME there are some ratio_over chains, which end with empty ratio_with. // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly. this->set(opt_key, (opt->type() == coFloatOrPercent) ? new ConfigOptionFloat(rhs.get_abs_value(opt_key)) : opt->clone()); } } void PlaceholderParser::apply_env_variables() { for (char** env = environ; *env; ++ env) { if (strncmp(*env, "SLIC3R_", 7) == 0) { std::stringstream ss(*env); std::string key, value; std::getline(ss, key, '='); ss >> value; this->set(key, value); } } } namespace spirit = boost::spirit; namespace qi = boost::spirit::qi; namespace px = boost::phoenix; namespace client { template struct OptWithPos { OptWithPos() {} OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range it_range) : opt(opt), it_range(it_range) {} ConfigOptionConstPtr opt = nullptr; boost::iterator_range it_range; }; template std::ostream& operator<<(std::ostream& os, OptWithPos const& opt) { os << std::string(opt.it_range.begin(), opt.it_range.end()); return os; } template struct expr { expr() : type(TYPE_EMPTY) {} explicit expr(bool b) : type(TYPE_BOOL) { data.b = b; } explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_BOOL), it_range(it_begin, it_end) { data.b = b; } explicit expr(int i) : type(TYPE_INT) { data.i = i; } explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_INT), it_range(it_begin, it_end) { data.i = i; } explicit expr(double d) : type(TYPE_DOUBLE) { data.d = d; } explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_DOUBLE), it_range(it_begin, it_end) { data.d = d; } explicit expr(const char *s) : type(TYPE_STRING) { data.s = new std::string(s); } explicit expr(const std::string &s) : type(TYPE_STRING) { data.s = new std::string(s); } explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_STRING), it_range(it_begin, it_end) { data.s = new std::string(s); } expr(const expr &rhs) : type(rhs.type), it_range(rhs.it_range) { if (rhs.type == TYPE_STRING) data.s = new std::string(*rhs.data.s); else data.set(rhs.data); } explicit expr(expr &&rhs) : type(rhs.type), it_range(rhs.it_range) { data.set(rhs.data); rhs.type = TYPE_EMPTY; } explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : type(rhs.type), it_range(it_begin, it_end) { data.set(rhs.data); rhs.type = TYPE_EMPTY; } ~expr() { this->reset(); } expr &operator=(const expr &rhs) { this->type = rhs.type; this->it_range = rhs.it_range; if (rhs.type == TYPE_STRING) this->data.s = new std::string(*rhs.data.s); else this->data.set(rhs.data); return *this; } expr &operator=(expr &&rhs) { type = rhs.type; this->it_range = rhs.it_range; data.set(rhs.data); rhs.type = TYPE_EMPTY; return *this; } void reset() { if (this->type == TYPE_STRING) delete data.s; this->type = TYPE_EMPTY; } bool& b() { return data.b; } bool b() const { return data.b; } void set_b(bool v) { this->reset(); this->data.b = v; this->type = TYPE_BOOL; } int& i() { return data.i; } int i() const { return data.i; } void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; } int as_i() const { return (this->type == TYPE_INT) ? this->i() : int(this->d()); } double& d() { return data.d; } double d() const { return data.d; } void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; } double as_d() const { return (this->type == TYPE_DOUBLE) ? this->d() : double(this->i()); } std::string& s() { return *data.s; } const std::string& s() const { return *data.s; } void set_s(const std::string &s) { this->reset(); this->data.s = new std::string(s); this->type = TYPE_STRING; } void set_s(std::string &&s) { this->reset(); this->data.s = new std::string(std::move(s)); this->type = TYPE_STRING; } std::string to_string() const { std::string out; switch (type) { case TYPE_BOOL: out = boost::to_string(data.b); break; case TYPE_INT: out = boost::to_string(data.i); break; case TYPE_DOUBLE: out = boost::to_string(data.d); break; case TYPE_STRING: out = *data.s; break; default: break; } return out; } union Data { // Raw image of the other data members. // The C++ compiler will consider a possible aliasing of char* with any other union member, // therefore copying the raw data is safe. char raw[8]; bool b; int i; double d; std::string *s; // Copy the largest member variable through char*, which will alias with all other union members by default. void set(const Data &rhs) { memcpy(this->raw, rhs.raw, sizeof(rhs.raw)); } } data; enum Type { TYPE_EMPTY = 0, TYPE_BOOL, TYPE_INT, TYPE_DOUBLE, TYPE_STRING, }; Type type; // Range of input iterators covering this expression. // Used for throwing parse exceptions. boost::iterator_range it_range; expr unary_minus(const Iterator start_pos) const { switch (this->type) { case TYPE_INT : return expr(- this->i(), start_pos, this->it_range.end()); case TYPE_DOUBLE: return expr(- this->d(), start_pos, this->it_range.end()); default: this->throw_exception("Cannot apply unary minus operator."); } assert(false); // Suppress compiler warnings. return expr(); } expr unary_not(const Iterator start_pos) const { switch (this->type) { case TYPE_BOOL : return expr(! this->b(), start_pos, this->it_range.end()); default: this->throw_exception("Cannot apply a not operator."); } assert(false); // Suppress compiler warnings. return expr(); } expr &operator+=(const expr &rhs) { if (this->type == TYPE_STRING) { // Convert the right hand side to string and append. *this->data.s += rhs.to_string(); } else if (rhs.type == TYPE_STRING) { // Conver the left hand side to string, append rhs. this->data.s = new std::string(this->to_string() + rhs.s()); this->type = TYPE_STRING; } else { const char *err_msg = "Cannot add non-numeric types."; this->throw_if_not_numeric(err_msg); rhs.throw_if_not_numeric(err_msg); if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { double d = this->as_d() + rhs.as_d(); this->data.d = d; this->type = TYPE_DOUBLE; } else this->data.i += rhs.i(); } this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); return *this; } expr &operator-=(const expr &rhs) { const char *err_msg = "Cannot subtract non-numeric types."; this->throw_if_not_numeric(err_msg); rhs.throw_if_not_numeric(err_msg); if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { double d = this->as_d() - rhs.as_d(); this->data.d = d; this->type = TYPE_DOUBLE; } else this->data.i -= rhs.i(); this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); return *this; } expr &operator*=(const expr &rhs) { const char *err_msg = "Cannot multiply with non-numeric type."; this->throw_if_not_numeric(err_msg); rhs.throw_if_not_numeric(err_msg); if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { double d = this->as_d() * rhs.as_d(); this->data.d = d; this->type = TYPE_DOUBLE; } else this->data.i *= rhs.i(); this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); return *this; } expr &operator/=(const expr &rhs) { this->throw_if_not_numeric("Cannot divide a non-numeric type."); rhs.throw_if_not_numeric("Cannot divide with a non-numeric type."); if ((this->type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.)) rhs.throw_exception("Division by zero"); if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { double d = this->as_d() / rhs.as_d(); this->data.d = d; this->type = TYPE_DOUBLE; } else this->data.i /= rhs.i(); this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); return *this; } static void to_string2(expr &self, std::string &out) { out = self.to_string(); } static void evaluate_boolean(expr &self, bool &out) { if (self.type != TYPE_BOOL) self.throw_exception("Not a boolean expression"); out = self.b(); } static void evaluate_boolean_to_string(expr &self, std::string &out) { if (self.type != TYPE_BOOL) self.throw_exception("Not a boolean expression"); out = self.b() ? "true" : "false"; } // Is lhs==rhs? Store the result into lhs. static void compare_op(expr &lhs, expr &rhs, char op, bool invert) { bool value = false; if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) && (rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) { // Both types are numeric. switch (op) { case '=': value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? (std::abs(lhs.as_d() - rhs.as_d()) < 1e-8) : (lhs.i() == rhs.i()); break; case '<': value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? (lhs.as_d() < rhs.as_d()) : (lhs.i() < rhs.i()); break; case '>': default: value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? (lhs.as_d() > rhs.as_d()) : (lhs.i() > rhs.i()); break; } } else if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) { // Both type are bool. if (op != '=') boost::throw_exception(qi::expectation_failure( lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types."))); value = lhs.b() == rhs.b(); } else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) { // One type is string, the other could be converted to string. value = (op == '=') ? (lhs.to_string() == rhs.to_string()) : (op == '<') ? (lhs.to_string() < rhs.to_string()) : (lhs.to_string() > rhs.to_string()); } else { boost::throw_exception(qi::expectation_failure( lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types."))); } lhs.type = TYPE_BOOL; lhs.data.b = invert ? ! value : value; } static void equal (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', false); } static void not_equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', true ); } static void lower (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '<', false); } static void greater (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '>', false); } static void leq (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '>', true ); } static void geq (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '<', true ); } static void regex_op(expr &lhs, boost::iterator_range &rhs, char op) { const std::string *subject = nullptr; const std::string *mask = nullptr; if (lhs.type == TYPE_STRING) { // One type is string, the other could be converted to string. subject = &lhs.s(); } else { lhs.throw_exception("Left hand side of a regex match must be a string."); } try { std::string pattern(++ rhs.begin(), -- rhs.end()); bool result = SLIC3R_REGEX_NAMESPACE::regex_match(*subject, SLIC3R_REGEX_NAMESPACE::regex(pattern)); if (op == '!') result = ! result; lhs.reset(); lhs.type = TYPE_BOOL; lhs.data.b = result; } catch (SLIC3R_REGEX_NAMESPACE::regex_error &ex) { // Syntax error in the regular expression boost::throw_exception(qi::expectation_failure( rhs.begin(), rhs.end(), spirit::info(std::string("*Regular expression compilation failed: ") + ex.what()))); } } static void regex_matches (expr &lhs, boost::iterator_range &rhs) { return regex_op(lhs, rhs, '='); } static void regex_doesnt_match(expr &lhs, boost::iterator_range &rhs) { return regex_op(lhs, rhs, '!'); } static void logical_op(expr &lhs, expr &rhs, char op) { bool value = false; if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) { value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b()); } else { boost::throw_exception(qi::expectation_failure( lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot apply logical operation to non-boolean operators."))); } lhs.type = TYPE_BOOL; lhs.data.b = value; } static void logical_or (expr &lhs, expr &rhs) { logical_op(lhs, rhs, '|'); } static void logical_and(expr &lhs, expr &rhs) { logical_op(lhs, rhs, '&'); } static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2) { bool value = false; if (lhs.type != TYPE_BOOL) lhs.throw_exception("Not a boolean expression"); if (lhs.b()) lhs = std::move(rhs1); else lhs = std::move(rhs2); } static void set_if(bool &cond, bool ¬_yet_consumed, std::string &str_in, std::string &str_out) { if (cond && not_yet_consumed) { str_out = str_in; not_yet_consumed = false; } } void throw_exception(const char *message) const { boost::throw_exception(qi::expectation_failure( this->it_range.begin(), this->it_range.end(), spirit::info(std::string("*") + message))); } void throw_if_not_numeric(const char *message) const { if (this->type != TYPE_INT && this->type != TYPE_DOUBLE) this->throw_exception(message); } }; template std::ostream& operator<<(std::ostream &os, const expr &expression) { typedef expr Expr; os << std::string(expression.it_range.begin(), expression.it_range.end()) << " - "; switch (expression.type) { case Expr::TYPE_EMPTY: os << "empty"; break; case Expr::TYPE_BOOL: os << "bool (" << expression.b() << ")"; break; case Expr::TYPE_INT: os << "int (" << expression.i() << ")"; break; case Expr::TYPE_DOUBLE: os << "double (" << expression.d() << ")"; break; case Expr::TYPE_STRING: os << "string (" << expression.s() << ")"; break; default: os << "unknown"; }; return os; } struct MyContext { const DynamicConfig *config = nullptr; const DynamicConfig *config_override = nullptr; size_t current_extruder_id = 0; // If false, the macro_processor will evaluate a full macro. // If true, the macro processor will evaluate just a boolean condition using the full expressive power of the macro processor. bool just_boolean_expression = false; std::string error_message; // Table to translate symbol tag to a human readable error message. static std::map tag_to_error_message; static void evaluate_full_macro(const MyContext *ctx, bool &result) { result = ! ctx->just_boolean_expression; } const ConfigOption* resolve_symbol(const std::string &opt_key) const { const ConfigOption *opt = nullptr; if (config_override != nullptr) opt = config_override->option(opt_key); if (opt == nullptr) opt = config->option(opt_key); return opt; } template static void legacy_variable_expansion( const MyContext *ctx, boost::iterator_range &opt_key, std::string &output) { std::string opt_key_str(opt_key.begin(), opt_key.end()); const ConfigOption *opt = ctx->resolve_symbol(opt_key_str); size_t idx = ctx->current_extruder_id; if (opt == nullptr) { // Check whether this is a legacy vector indexing. idx = opt_key_str.rfind('_'); if (idx != std::string::npos) { opt = ctx->resolve_symbol(opt_key_str.substr(0, idx)); if (opt != nullptr) { if (! opt->is_vector()) ctx->throw_exception("Trying to index a scalar variable", opt_key); char *endptr = nullptr; idx = strtol(opt_key_str.c_str() + idx + 1, &endptr, 10); if (endptr == nullptr || *endptr != 0) ctx->throw_exception("Invalid vector index", boost::iterator_range(opt_key.begin() + idx + 1, opt_key.end())); } } } if (opt == nullptr) ctx->throw_exception("Variable does not exist", boost::iterator_range(opt_key.begin(), opt_key.end())); if (opt->is_scalar()) output = opt->serialize(); else { const ConfigOptionVectorBase *vec = static_cast(opt); if (vec->empty()) ctx->throw_exception("Indexing an empty vector variable", opt_key); output = vec->vserialize()[(idx >= vec->size()) ? 0 : idx]; } } template static void legacy_variable_expansion2( const MyContext *ctx, boost::iterator_range &opt_key, boost::iterator_range &opt_vector_index, std::string &output) { std::string opt_key_str(opt_key.begin(), opt_key.end()); const ConfigOption *opt = ctx->resolve_symbol(opt_key_str); if (opt == nullptr) { // Check whether the opt_key ends with '_'. if (opt_key_str.back() == '_') opt_key_str.resize(opt_key_str.size() - 1); opt = ctx->resolve_symbol(opt_key_str); } if (! opt->is_vector()) ctx->throw_exception("Trying to index a scalar variable", opt_key); const ConfigOptionVectorBase *vec = static_cast(opt); if (vec->empty()) ctx->throw_exception("Indexing an empty vector variable", boost::iterator_range(opt_key.begin(), opt_key.end())); const ConfigOption *opt_index = ctx->resolve_symbol(std::string(opt_vector_index.begin(), opt_vector_index.end())); if (opt_index == nullptr) ctx->throw_exception("Variable does not exist", opt_key); if (opt_index->type() != coInt) ctx->throw_exception("Indexing variable has to be integer", opt_key); int idx = opt_index->getInt(); if (idx < 0) ctx->throw_exception("Negative vector index", opt_key); output = vec->vserialize()[(idx >= (int)vec->size()) ? 0 : idx]; } template static void resolve_variable( const MyContext *ctx, boost::iterator_range &opt_key, OptWithPos &output) { const ConfigOption *opt = ctx->resolve_symbol(std::string(opt_key.begin(), opt_key.end())); if (opt == nullptr) ctx->throw_exception("Not a variable name", opt_key); output.opt = opt; output.it_range = opt_key; } template static void scalar_variable_reference( const MyContext *ctx, OptWithPos &opt, expr &output) { if (opt.opt->is_vector()) ctx->throw_exception("Referencing a scalar variable in a vector context", opt.it_range); switch (opt.opt->type()) { case coFloat: output.set_d(opt.opt->getFloat()); break; case coInt: output.set_i(opt.opt->getInt()); break; case coString: output.set_s(static_cast(opt.opt)->value); break; case coPercent: output.set_d(opt.opt->getFloat()); break; case coPoint: output.set_s(opt.opt->serialize()); break; case coBool: output.set_b(opt.opt->getBool()); break; case coFloatOrPercent: ctx->throw_exception("FloatOrPercent variables are not supported", opt.it_range); default: ctx->throw_exception("Unknown scalar variable type", opt.it_range); } output.it_range = opt.it_range; } template static void vector_variable_reference( const MyContext *ctx, OptWithPos &opt, int &index, Iterator it_end, expr &output) { if (opt.opt->is_scalar()) ctx->throw_exception("Referencing a vector variable in a scalar context", opt.it_range); const ConfigOptionVectorBase *vec = static_cast(opt.opt); if (vec->empty()) ctx->throw_exception("Indexing an empty vector variable", opt.it_range); size_t idx = (index < 0) ? 0 : (index >= int(vec->size())) ? 0 : size_t(index); switch (opt.opt->type()) { case coFloats: output.set_d(static_cast(opt.opt)->values[idx]); break; case coInts: output.set_i(static_cast(opt.opt)->values[idx]); break; case coStrings: output.set_s(static_cast(opt.opt)->values[idx]); break; case coPercents: output.set_d(static_cast(opt.opt)->values[idx]); break; case coPoints: output.set_s(static_cast(opt.opt)->values[idx].dump_perl()); break; case coBools: output.set_b(static_cast(opt.opt)->values[idx] != 0); break; default: ctx->throw_exception("Unknown vector variable type", opt.it_range); } output.it_range = boost::iterator_range(opt.it_range.begin(), it_end); } // Verify that the expression returns an integer, which may be used // to address a vector. template static void evaluate_index(expr &expr_index, int &output) { if (expr_index.type != expr::TYPE_INT) expr_index.throw_exception("Non-integer index is not allowed to address a vector variable."); output = expr_index.i(); } template static void throw_exception(const std::string &msg, const boost::iterator_range &it_range) { // An asterix is added to the start of the string to differentiate the boost::spirit::info::tag content // between the grammer terminal / non-terminal symbol name and a free-form error message. boost::throw_exception(qi::expectation_failure(it_range.begin(), it_range.end(), spirit::info(std::string("*") + msg))); } template static void process_error_message(const MyContext *context, const boost::spirit::info &info, const Iterator &it_begin, const Iterator &it_end, const Iterator &it_error) { std::string &msg = const_cast(context)->error_message; std::string first(it_begin, it_error); std::string last(it_error, it_end); auto first_pos = first.rfind('\n'); auto last_pos = last.find('\n'); int line_nr = 1; if (first_pos == std::string::npos) first_pos = 0; else { // Calculate the current line number. for (size_t i = 0; i <= first_pos; ++ i) if (first[i] == '\n') ++ line_nr; ++ first_pos; } auto error_line = std::string(first, first_pos) + std::string(last, 0, last_pos); // Position of the it_error from the start of its line. auto error_pos = (it_error - it_begin) - first_pos; msg += "Parsing error at line " + std::to_string(line_nr); if (! info.tag.empty() && info.tag.front() == '*') { // The gat contains an explanatory string. msg += ": "; msg += info.tag.substr(1); } else { auto it = tag_to_error_message.find(info.tag); if (it == tag_to_error_message.end()) { // A generic error report based on the nonterminal or terminal symbol name. msg += ". Expecting tag "; msg += info.tag; } else { // Use the human readable error message. msg += ". "; msg + it->second; } } msg += '\n'; msg += error_line; msg += '\n'; for (size_t i = 0; i < error_pos; ++ i) msg += ' '; msg += "^\n"; } }; // Table to translate symbol tag to a human readable error message. std::map MyContext::tag_to_error_message = { { "eoi", "Unknown syntax error" }, { "start", "Unknown syntax error" }, { "text", "Invalid text." }, { "text_block", "Invalid text block." }, { "macro", "Invalid macro." }, { "if_else_output", "Not an {if}{else}{endif} macro." }, { "switch_output", "Not a {switch} macro." }, { "legacy_variable_expansion", "Expecting a legacy variable expansion format" }, { "identifier", "Expecting an identifier." }, { "conditional_expression", "Expecting a conditional expression." }, { "logical_or_expression", "Expecting a boolean expression." }, { "logical_and_expression", "Expecting a boolean expression." }, { "equality_expression", "Expecting an expression." }, { "bool_expr_eval", "Expecting a boolean expression."}, { "relational_expression", "Expecting an expression." }, { "additive_expression", "Expecting an expression." }, { "multiplicative_expression", "Expecting an expression." }, { "unary_expression", "Expecting an expression." }, { "scalar_variable_reference", "Expecting a scalar variable reference."}, { "variable_reference", "Expecting a variable reference."}, { "regular_expression", "Expecting a regular expression."} }; // For debugging the boost::spirit parsers. Print out the string enclosed in it_range. template std::ostream& operator<<(std::ostream& os, const boost::iterator_range &it_range) { os << std::string(it_range.begin(), it_range.end()); return os; } // Disable parsing int numbers (without decimals) and Inf/NaN symbols by the double parser. struct strict_real_policies_without_nan_inf : public qi::strict_real_policies { template static bool parse_nan(It&, It const&, Attr&) { return false; } template static bool parse_inf(It&, It const&, Attr&) { return false; } }; // This parser is to be used inside a raw[] directive to accept a single valid UTF-8 character. // If an invalid UTF-8 sequence is encountered, a qi::expectation_failure is thrown. struct utf8_char_skipper_parser : qi::primitive_parser { // Define the attribute type exposed by this parser component template struct attribute { typedef wchar_t type; }; // This function is called during the actual parsing process template bool parse(Iterator& first, Iterator const& last, Context& context, Skipper const& skipper, Attribute& attr) const { // The skipper shall always be empty, any white space will be accepted. // skip_over(first, last, skipper); if (first == last) return false; // Iterator over the UTF-8 sequence. auto it = first; // Read the first byte of the UTF-8 sequence. unsigned char c = static_cast(*it ++); unsigned int cnt = 0; // UTF-8 sequence must not start with a continuation character: if ((c & 0xC0) == 0x80) goto err; // Skip high surrogate first if there is one. // If the most significant bit with a zero in it is in position // 8-N then there are N bytes in this UTF-8 sequence: { unsigned char mask = 0x80u; unsigned int result = 0; while (c & mask) { ++ result; mask >>= 1; } cnt = (result == 0) ? 1 : ((result > 4) ? 4 : result); } // Since we haven't read in a value, we need to validate the code points: for (-- cnt; cnt > 0; -- cnt) { if (it == last) goto err; c = static_cast(*it ++); // We must have a continuation byte: if (cnt > 1 && (c & 0xC0) != 0x80) goto err; } first = it; return true; err: MyContext::throw_exception("Invalid utf8 sequence", boost::iterator_range(first, last)); return false; } // This function is called during error handling to create a human readable string for the error context. template spirit::info what(Context&) const { return spirit::info("unicode_char"); } }; /////////////////////////////////////////////////////////////////////////// // Our macro_processor grammar /////////////////////////////////////////////////////////////////////////// // Inspired by the C grammar rules https://www.lysator.liu.se/c/ANSI-C-grammar-y.html template struct macro_processor : qi::grammar, spirit::ascii::space_type> { macro_processor() : macro_processor::base_type(start) { using namespace qi::labels; qi::alpha_type alpha; qi::alnum_type alnum; qi::eps_type eps; qi::raw_type raw; qi::lit_type lit; qi::lexeme_type lexeme; qi::no_skip_type no_skip; qi::real_parser strict_double; spirit::ascii::char_type char_; utf8_char_skipper_parser utf8char; spirit::bool_type bool_; spirit::int_type int_; spirit::double_type double_; spirit::ascii::string_type string; spirit::eoi_type eoi; spirit::repository::qi::iter_pos_type iter_pos; auto kw = spirit::repository::qi::distinct(qi::copy(alnum | '_')); qi::_val_type _val; qi::_1_type _1; qi::_2_type _2; qi::_3_type _3; qi::_4_type _4; qi::_a_type _a; qi::_b_type _b; qi::_r1_type _r1; // Starting symbol of the grammer. // The leading eps is required by the "expectation point" operator ">". // Without it, some of the errors would not trigger the error handler. // Also the start symbol switches between the "full macro syntax" and a "boolean expression only", // depending on the context->just_boolean_expression flag. This way a single static expression parser // could serve both purposes. start = eps[px::bind(&MyContext::evaluate_full_macro, _r1, _a)] > ( eps(_a==true) > text_block(_r1) [_val=_1] | conditional_expression(_r1) [ px::bind(&expr::evaluate_boolean_to_string, _1, _val) ] ) > eoi; start.name("start"); qi::on_error(start, px::bind(&MyContext::process_error_message, _r1, _4, _1, _2, _3)); text_block = *( text [_val+=_1] // Allow back tracking after '{' in case of a text_block embedded inside a condition. // In that case the inner-most {else} wins and the {if}/{elsif}/{else} shall be paired. // {elsif}/{else} without an {if} will be allowed to back track from the embedded text_block. | (lit('{') >> macro(_r1) [_val+=_1] > '}') | (lit('[') > legacy_variable_expansion(_r1) [_val+=_1] > ']') ); text_block.name("text_block"); // Free-form text up to a first brace, including spaces and newlines. // The free-form text will be inserted into the processed text without a modification. text = no_skip[raw[+(utf8char - char_('[') - char_('{'))]]; text.name("text"); // New style of macro expansion. // The macro expansion may contain numeric or string expressions, ifs and cases. macro = (kw["if"] > if_else_output(_r1) [_val = _1]) // | (kw["switch"] > switch_output(_r1) [_val = _1]) | additive_expression(_r1) [ px::bind(&expr::to_string2, _1, _val) ]; macro.name("macro"); // An if expression enclosed in {} (the outmost {} are already parsed by the caller). if_else_output = eps[_b=true] > bool_expr_eval(_r1)[_a=_1] > '}' > text_block(_r1)[px::bind(&expr::set_if, _a, _b, _1, _val)] > '{' > *(kw["elsif"] > bool_expr_eval(_r1)[_a=_1] > '}' > text_block(_r1)[px::bind(&expr::set_if, _a, _b, _1, _val)] > '{') > -(kw["else"] > lit('}') > text_block(_r1)[px::bind(&expr::set_if, _b, _b, _1, _val)] > '{') > kw["endif"]; if_else_output.name("if_else_output"); // A switch expression enclosed in {} (the outmost {} are already parsed by the caller). /* switch_output = eps[_b=true] > omit[expr(_r1)[_a=_1]] > '}' > text_block(_r1)[px::bind(&expr::set_if_equal, _a, _b, _1, _val)] > '{' > *("elsif" > omit[bool_expr_eval(_r1)[_a=_1]] > '}' > text_block(_r1)[px::bind(&expr::set_if, _a, _b, _1, _val)]) >> -("else" > '}' >> text_block(_r1)[px::bind(&expr::set_if, _b, _b, _1, _val)]) > "endif"; */ // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. legacy_variable_expansion = (identifier >> &lit(']')) [ px::bind(&MyContext::legacy_variable_expansion, _r1, _1, _val) ] | (identifier > lit('[') > identifier > ']') [ px::bind(&MyContext::legacy_variable_expansion2, _r1, _1, _2, _val) ] ; legacy_variable_expansion.name("legacy_variable_expansion"); identifier = ! kw[keywords] >> raw[lexeme[(alpha | '_') >> *(alnum | '_')]]; identifier.name("identifier"); conditional_expression = logical_or_expression(_r1) [_val = _1] >> -('?' > conditional_expression(_r1) > ':' > conditional_expression(_r1)) [px::bind(&expr::ternary_op, _val, _1, _2)]; conditional_expression.name("conditional_expression"); logical_or_expression = logical_and_expression(_r1) [_val = _1] >> *( ((kw["or"] | "||") > logical_and_expression(_r1) ) [px::bind(&expr::logical_or, _val, _1)] ); logical_or_expression.name("logical_or_expression"); logical_and_expression = equality_expression(_r1) [_val = _1] >> *( ((kw["and"] | "&&") > equality_expression(_r1) ) [px::bind(&expr::logical_and, _val, _1)] ); logical_and_expression.name("logical_and_expression"); equality_expression = relational_expression(_r1) [_val = _1] >> *( ("==" > relational_expression(_r1) ) [px::bind(&expr::equal, _val, _1)] | ("!=" > relational_expression(_r1) ) [px::bind(&expr::not_equal, _val, _1)] | ("<>" > relational_expression(_r1) ) [px::bind(&expr::not_equal, _val, _1)] | ("=~" > regular_expression ) [px::bind(&expr::regex_matches, _val, _1)] | ("!~" > regular_expression ) [px::bind(&expr::regex_doesnt_match, _val, _1)] ); equality_expression.name("bool expression"); // Evaluate a boolean expression stored as expr into a boolean value. // Throw if the equality_expression does not produce a expr of boolean type. bool_expr_eval = conditional_expression(_r1) [ px::bind(&expr::evaluate_boolean, _1, _val) ]; bool_expr_eval.name("bool_expr_eval"); relational_expression = additive_expression(_r1) [_val = _1] >> *( ("<=" > additive_expression(_r1) ) [px::bind(&expr::leq, _val, _1)] | (">=" > additive_expression(_r1) ) [px::bind(&expr::geq, _val, _1)] | (lit('<') > additive_expression(_r1) ) [px::bind(&expr::lower, _val, _1)] | (lit('>') > additive_expression(_r1) ) [px::bind(&expr::greater, _val, _1)] ); relational_expression.name("relational_expression"); additive_expression = multiplicative_expression(_r1) [_val = _1] >> *( (lit('+') > multiplicative_expression(_r1) ) [_val += _1] | (lit('-') > multiplicative_expression(_r1) ) [_val -= _1] ); additive_expression.name("additive_expression"); multiplicative_expression = unary_expression(_r1) [_val = _1] >> *( (lit('*') > unary_expression(_r1) ) [_val *= _1] | (lit('/') > unary_expression(_r1) ) [_val /= _1] ); multiplicative_expression.name("multiplicative_expression"); struct FactorActions { static void set_start_pos(Iterator &start_pos, expr &out) { out.it_range = boost::iterator_range(start_pos, start_pos); } static void int_(int &value, Iterator &end_pos, expr &out) { out = expr(value, out.it_range.begin(), end_pos); } static void double_(double &value, Iterator &end_pos, expr &out) { out = expr(value, out.it_range.begin(), end_pos); } static void bool_(bool &value, Iterator &end_pos, expr &out) { out = expr(value, out.it_range.begin(), end_pos); } static void string_(boost::iterator_range &it_range, expr &out) { out = expr(std::string(it_range.begin() + 1, it_range.end() - 1), it_range.begin(), it_range.end()); } static void expr_(expr &value, Iterator &end_pos, expr &out) { out = expr(std::move(value), out.it_range.begin(), end_pos); } static void minus_(expr &value, expr &out) { out = value.unary_minus(out.it_range.begin()); } static void not_(expr &value, expr &out) { out = value.unary_not(out.it_range.begin()); } }; unary_expression = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( scalar_variable_reference(_r1) [ _val = _1 ] | (lit('(') > conditional_expression(_r1) > ')' > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] | (lit('-') > unary_expression(_r1) ) [ px::bind(&FactorActions::minus_, _1, _val) ] | (lit('+') > unary_expression(_r1) > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] | ((kw["not"] | '!') > unary_expression(_r1) > iter_pos) [ px::bind(&FactorActions::not_, _1, _val) ] | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ] | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ] | (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ] | raw[lexeme['"' > *((utf8char - char_('\\') - char_('"')) | ('\\' > char_)) > '"']] [ px::bind(&FactorActions::string_, _1, _val) ] ); unary_expression.name("unary_expression"); scalar_variable_reference = variable_reference(_r1)[_a=_1] >> ( ('[' > additive_expression(_r1)[px::bind(&MyContext::evaluate_index, _1, _b)] > ']' > iter_pos[px::bind(&MyContext::vector_variable_reference, _r1, _a, _b, _1, _val)]) | eps[px::bind(&MyContext::scalar_variable_reference, _r1, _a, _val)] ); scalar_variable_reference.name("scalar variable reference"); variable_reference = identifier [ px::bind(&MyContext::resolve_variable, _r1, _1, _val) ]; variable_reference.name("variable reference"); regular_expression = raw[lexeme['/' > *((utf8char - char_('\\') - char_('/')) | ('\\' > char_)) > '/']]; regular_expression.name("regular_expression"); keywords.add ("and") ("if") //("inf") ("else") ("elsif") ("endif") ("false") ("not") ("or") ("true"); if (0) { debug(start); debug(text); debug(text_block); debug(macro); debug(if_else_output); // debug(switch_output); debug(legacy_variable_expansion); debug(identifier); debug(conditional_expression); debug(logical_or_expression); debug(logical_and_expression); debug(equality_expression); debug(bool_expr_eval); debug(relational_expression); debug(additive_expression); debug(multiplicative_expression); debug(unary_expression); debug(scalar_variable_reference); debug(variable_reference); debug(regular_expression); } } // Generic expression over expr. typedef qi::rule(const MyContext*), spirit::ascii::space_type> RuleExpression; // The start of the grammar. qi::rule, spirit::ascii::space_type> start; // A free-form text. qi::rule text; // A free-form text, possibly empty, possibly containing macro expansions. qi::rule text_block; // Statements enclosed in curely braces {} qi::rule macro; // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. qi::rule legacy_variable_expansion; // Parsed identifier name. qi::rule(), spirit::ascii::space_type> identifier; // Ternary operator (?:) over logical_or_expression. RuleExpression conditional_expression; // Logical or over logical_and_expressions. RuleExpression logical_or_expression; // Logical and over relational_expressions. RuleExpression logical_and_expression; // <, >, <=, >= RuleExpression relational_expression; // Math expression consisting of +- operators over multiplicative_expressions. RuleExpression additive_expression; // Boolean expressions over expressions. RuleExpression equality_expression; // Math expression consisting of */ operators over factors. RuleExpression multiplicative_expression; // Number literals, functions, braced expressions, variable references, variable indexing references. RuleExpression unary_expression; // Rule to capture a regular expression enclosed in //. qi::rule(), spirit::ascii::space_type> regular_expression; // Evaluate boolean expression into bool. qi::rule bool_expr_eval; // Reference of a scalar variable, or reference to a field of a vector variable. qi::rule(const MyContext*), qi::locals, int>, spirit::ascii::space_type> scalar_variable_reference; // Rule to translate an identifier to a ConfigOption, or to fail. qi::rule(const MyContext*), spirit::ascii::space_type> variable_reference; qi::rule, spirit::ascii::space_type> if_else_output; // qi::rule, bool, std::string>, spirit::ascii::space_type> switch_output; qi::symbols keywords; }; } static std::string process_macro(const std::string &templ, client::MyContext &context) { typedef std::string::const_iterator iterator_type; typedef client::macro_processor macro_processor; // Our whitespace skipper. spirit::ascii::space_type space; // Our grammar, statically allocated inside the method, meaning it will be allocated the first time // PlaceholderParser::process() runs. //FIXME this kind of initialization is not thread safe! static macro_processor macro_processor_instance; // Iterators over the source template. std::string::const_iterator iter = templ.begin(); std::string::const_iterator end = templ.end(); // Accumulator for the processed template. std::string output; bool res = phrase_parse(iter, end, macro_processor_instance(&context), space, output); if (!context.error_message.empty()) { if (context.error_message.back() != '\n' && context.error_message.back() != '\r') context.error_message += '\n'; throw std::runtime_error(context.error_message); } return output; } std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const { client::MyContext context; context.config = &this->config(); context.config_override = config_override; context.current_extruder_id = current_extruder_id; return process_macro(templ, context); } // Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax. // Throws std::runtime_error on syntax or runtime error. bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override) { client::MyContext context; context.config = &config; context.config_override = config_override; // Let the macro processor parse just a boolean expression, not the full macro language. context.just_boolean_expression = true; return process_macro(templ, context) == "true"; } } Slic3r-version_1.39.1/xs/src/libslic3r/PlaceholderParser.hpp000066400000000000000000000040021324354444700237360ustar00rootroot00000000000000#ifndef slic3r_PlaceholderParser_hpp_ #define slic3r_PlaceholderParser_hpp_ #include "libslic3r.h" #include #include #include #include "PrintConfig.hpp" namespace Slic3r { class PlaceholderParser { public: PlaceholderParser(); void update_timestamp(); void apply_config(const DynamicPrintConfig &config); void apply_env_variables(); // Add new ConfigOption values to m_config. void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); } void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); } void set(const std::string &key, unsigned int value) { this->set(key, int(value)); } void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); } void set(const std::string &key, double value) { this->set(key, new ConfigOptionFloat(value)); } void set(const std::string &key, const std::vector &values) { this->set(key, new ConfigOptionStrings(values)); } void set(const std::string &key, ConfigOption *opt) { m_config.set_key_value(key, opt); } const DynamicConfig& config() const { return m_config; } const ConfigOption* option(const std::string &key) const { return m_config.option(key); } // Fill in the template using a macro processing language. // Throws std::runtime_error on syntax or runtime error. std::string process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr) const; // Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax. // Throws std::runtime_error on syntax or runtime error. static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override = nullptr); private: DynamicConfig m_config; }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/Point.cpp000066400000000000000000000227111324354444700214320ustar00rootroot00000000000000#include "Point.hpp" #include "Line.hpp" #include "MultiPoint.hpp" #include #include namespace Slic3r { Point::Point(double x, double y) { this->x = lrint(x); this->y = lrint(y); } std::string Point::wkt() const { std::ostringstream ss; ss << "POINT(" << this->x << " " << this->y << ")"; return ss.str(); } std::string Point::dump_perl() const { std::ostringstream ss; ss << "[" << this->x << "," << this->y << "]"; return ss.str(); } void Point::scale(double factor) { this->x *= factor; this->y *= factor; } void Point::translate(double x, double y) { this->x += x; this->y += y; } void Point::translate(const Vector &vector) { this->translate(vector.x, vector.y); } void Point::rotate(double angle) { double cur_x = (double)this->x; double cur_y = (double)this->y; double s = sin(angle); double c = cos(angle); this->x = (coord_t)round(c * cur_x - s * cur_y); this->y = (coord_t)round(c * cur_y + s * cur_x); } void Point::rotate(double angle, const Point ¢er) { double cur_x = (double)this->x; double cur_y = (double)this->y; double s = sin(angle); double c = cos(angle); double dx = cur_x - (double)center.x; double dy = cur_y - (double)center.y; this->x = (coord_t)round( (double)center.x + c * dx - s * dy ); this->y = (coord_t)round( (double)center.y + c * dy + s * dx ); } bool Point::coincides_with_epsilon(const Point &point) const { return std::abs(this->x - point.x) < SCALED_EPSILON && std::abs(this->y - point.y) < SCALED_EPSILON; } int Point::nearest_point_index(const Points &points) const { PointConstPtrs p; p.reserve(points.size()); for (Points::const_iterator it = points.begin(); it != points.end(); ++it) p.push_back(&*it); return this->nearest_point_index(p); } int Point::nearest_point_index(const PointConstPtrs &points) const { int idx = -1; double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough for (PointConstPtrs::const_iterator it = points.begin(); it != points.end(); ++it) { /* If the X distance of the candidate is > than the total distance of the best previous candidate, we know we don't want it */ double d = sqr(this->x - (*it)->x); if (distance != -1 && d > distance) continue; /* If the Y distance of the candidate is > than the total distance of the best previous candidate, we know we don't want it */ d += sqr(this->y - (*it)->y); if (distance != -1 && d > distance) continue; idx = it - points.begin(); distance = d; if (distance < EPSILON) break; } return idx; } int Point::nearest_point_index(const PointPtrs &points) const { PointConstPtrs p; p.reserve(points.size()); for (PointPtrs::const_iterator it = points.begin(); it != points.end(); ++it) p.push_back(*it); return this->nearest_point_index(p); } bool Point::nearest_point(const Points &points, Point* point) const { int idx = this->nearest_point_index(points); if (idx == -1) return false; *point = points.at(idx); return true; } /* distance to the closest point of line */ double Point::distance_to(const Line &line) const { const double dx = line.b.x - line.a.x; const double dy = line.b.y - line.a.y; const double l2 = dx*dx + dy*dy; // avoid a sqrt if (l2 == 0.0) return this->distance_to(line.a); // line.a == line.b case // Consider the line extending the segment, parameterized as line.a + t (line.b - line.a). // We find projection of this point onto the line. // It falls where t = [(this-line.a) . (line.b-line.a)] / |line.b-line.a|^2 const double t = ((this->x - line.a.x) * dx + (this->y - line.a.y) * dy) / l2; if (t < 0.0) return this->distance_to(line.a); // beyond the 'a' end of the segment else if (t > 1.0) return this->distance_to(line.b); // beyond the 'b' end of the segment Point projection( line.a.x + t * dx, line.a.y + t * dy ); return this->distance_to(projection); } double Point::perp_distance_to(const Line &line) const { if (line.a.coincides_with(line.b)) return this->distance_to(line.a); double n = (double)(line.b.x - line.a.x) * (double)(line.a.y - this->y) - (double)(line.a.x - this->x) * (double)(line.b.y - line.a.y); return std::abs(n) / line.length(); } /* Three points are a counter-clockwise turn if ccw > 0, clockwise if * ccw < 0, and collinear if ccw = 0 because ccw is a determinant that * gives the signed area of the triangle formed by p1, p2 and this point. * In other words it is the 2D cross product of p1-p2 and p1-this, i.e. * z-component of their 3D cross product. * We return double because it must be big enough to hold 2*max(|coordinate|)^2 */ double Point::ccw(const Point &p1, const Point &p2) const { return (double)(p2.x - p1.x)*(double)(this->y - p1.y) - (double)(p2.y - p1.y)*(double)(this->x - p1.x); } double Point::ccw(const Line &line) const { return this->ccw(line.a, line.b); } // returns the CCW angle between this-p1 and this-p2 // i.e. this assumes a CCW rotation from p1 to p2 around this double Point::ccw_angle(const Point &p1, const Point &p2) const { double angle = atan2(p1.x - this->x, p1.y - this->y) - atan2(p2.x - this->x, p2.y - this->y); // we only want to return only positive angles return angle <= 0 ? angle + 2*PI : angle; } Point Point::projection_onto(const MultiPoint &poly) const { Point running_projection = poly.first_point(); double running_min = this->distance_to(running_projection); Lines lines = poly.lines(); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { Point point_temp = this->projection_onto(*line); if (this->distance_to(point_temp) < running_min) { running_projection = point_temp; running_min = this->distance_to(running_projection); } } return running_projection; } Point Point::projection_onto(const Line &line) const { if (line.a.coincides_with(line.b)) return line.a; /* (Ported from VisiLibity by Karl J. Obermeyer) The projection of point_temp onto the line determined by line_segment_temp can be represented as an affine combination expressed in the form projection of Point = theta*line_segment_temp.first + (1.0-theta)*line_segment_temp.second. If theta is outside the interval [0,1], then one of the Line_Segment's endpoints must be closest to calling Point. */ double lx = (double)(line.b.x - line.a.x); double ly = (double)(line.b.y - line.a.y); double theta = ( (double)(line.b.x - this->x)*lx + (double)(line.b.y- this->y)*ly ) / ( sqr(lx) + sqr(ly) ); if (0.0 <= theta && theta <= 1.0) return theta * line.a + (1.0-theta) * line.b; // Else pick closest endpoint. if (this->distance_to(line.a) < this->distance_to(line.b)) { return line.a; } else { return line.b; } } Point Point::negative() const { return Point(-this->x, -this->y); } Vector Point::vector_to(const Point &point) const { return Vector(point.x - this->x, point.y - this->y); } std::ostream& operator<<(std::ostream &stm, const Pointf &pointf) { return stm << pointf.x << "," << pointf.y; } std::string Pointf::wkt() const { std::ostringstream ss; ss << "POINT(" << this->x << " " << this->y << ")"; return ss.str(); } std::string Pointf::dump_perl() const { std::ostringstream ss; ss << "[" << this->x << "," << this->y << "]"; return ss.str(); } void Pointf::scale(double factor) { this->x *= factor; this->y *= factor; } void Pointf::translate(double x, double y) { this->x += x; this->y += y; } void Pointf::translate(const Vectorf &vector) { this->translate(vector.x, vector.y); } void Pointf::rotate(double angle) { double cur_x = this->x; double cur_y = this->y; double s = sin(angle); double c = cos(angle); this->x = c * cur_x - s * cur_y; this->y = c * cur_y + s * cur_x; } void Pointf::rotate(double angle, const Pointf ¢er) { double cur_x = this->x; double cur_y = this->y; double s = sin(angle); double c = cos(angle); double dx = cur_x - center.x; double dy = cur_y - center.y; this->x = center.x + c * dx - s * dy; this->y = center.y + c * dy + s * dx; } Pointf Pointf::negative() const { return Pointf(-this->x, -this->y); } Vectorf Pointf::vector_to(const Pointf &point) const { return Vectorf(point.x - this->x, point.y - this->y); } void Pointf3::scale(double factor) { Pointf::scale(factor); this->z *= factor; } void Pointf3::translate(const Vectorf3 &vector) { this->translate(vector.x, vector.y, vector.z); } void Pointf3::translate(double x, double y, double z) { Pointf::translate(x, y); this->z += z; } double Pointf3::distance_to(const Pointf3 &point) const { double dx = ((double)point.x - this->x); double dy = ((double)point.y - this->y); double dz = ((double)point.z - this->z); return sqrt(dx*dx + dy*dy + dz*dz); } Pointf3 Pointf3::negative() const { return Pointf3(-this->x, -this->y, -this->z); } Vectorf3 Pointf3::vector_to(const Pointf3 &point) const { return Vectorf3(point.x - this->x, point.y - this->y, point.z - this->z); } } Slic3r-version_1.39.1/xs/src/libslic3r/Point.hpp000066400000000000000000000315371324354444700214450ustar00rootroot00000000000000#ifndef slic3r_Point_hpp_ #define slic3r_Point_hpp_ #include "libslic3r.h" #include #include #include #include #include namespace Slic3r { class Line; class Linef; class MultiPoint; class Point; class Pointf; class Pointf3; typedef Point Vector; typedef Pointf Vectorf; typedef Pointf3 Vectorf3; typedef std::vector Points; typedef std::vector PointPtrs; typedef std::vector PointConstPtrs; typedef std::vector Pointfs; typedef std::vector Pointf3s; class Point { public: typedef coord_t coord_type; coord_t x; coord_t y; Point(coord_t _x = 0, coord_t _y = 0): x(_x), y(_y) {}; Point(int64_t _x, int64_t _y): x(coord_t(_x)), y(coord_t(_y)) {}; // for Clipper Point(double x, double y); static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } bool operator==(const Point& rhs) const { return this->x == rhs.x && this->y == rhs.y; } bool operator!=(const Point& rhs) const { return ! (*this == rhs); } bool operator<(const Point& rhs) const { return this->x < rhs.x || (this->x == rhs.x && this->y < rhs.y); } Point& operator+=(const Point& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } Point& operator-=(const Point& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } Point& operator*=(const coord_t& rhs) { this->x *= rhs; this->y *= rhs; return *this; } std::string wkt() const; std::string dump_perl() const; void scale(double factor); void translate(double x, double y); void translate(const Vector &vector); void rotate(double angle); void rotate(double angle, const Point ¢er); Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } bool coincides_with(const Point &point) const { return this->x == point.x && this->y == point.y; } bool coincides_with_epsilon(const Point &point) const; int nearest_point_index(const Points &points) const; int nearest_point_index(const PointConstPtrs &points) const; int nearest_point_index(const PointPtrs &points) const; bool nearest_point(const Points &points, Point* point) const; double distance_to(const Point &point) const { return sqrt(distance_to_sq(point)); } double distance_to_sq(const Point &point) const { double dx = double(point.x - this->x); double dy = double(point.y - this->y); return dx*dx + dy*dy; } double distance_to(const Line &line) const; double perp_distance_to(const Line &line) const; double ccw(const Point &p1, const Point &p2) const; double ccw(const Line &line) const; double ccw_angle(const Point &p1, const Point &p2) const; Point projection_onto(const MultiPoint &poly) const; Point projection_onto(const Line &line) const; Point negative() const; Vector vector_to(const Point &point) const; }; inline Point operator+(const Point& point1, const Point& point2) { return Point(point1.x + point2.x, point1.y + point2.y); } inline Point operator-(const Point& point1, const Point& point2) { return Point(point1.x - point2.x, point1.y - point2.y); } inline Point operator*(double scalar, const Point& point2) { return Point(scalar * point2.x, scalar * point2.y); } inline int64_t cross(const Point &v1, const Point &v2) { return int64_t(v1.x) * int64_t(v2.y) - int64_t(v1.y) * int64_t(v2.x); } inline int64_t dot(const Point &v1, const Point &v2) { return int64_t(v1.x) * int64_t(v2.x) + int64_t(v1.y) * int64_t(v2.y); } // To be used by std::unordered_map, std::unordered_multimap and friends. struct PointHash { size_t operator()(const Point &pt) const { return std::hash()(pt.x) ^ std::hash()(pt.y); } }; // A generic class to search for a closest Point in a given radius. // It uses std::unordered_multimap to implement an efficient 2D spatial hashing. // The PointAccessor has to return const Point*. // If a nullptr is returned, it is ignored by the query. template class ClosestPointInRadiusLookup { public: ClosestPointInRadiusLookup(coord_t search_radius, PointAccessor point_accessor = PointAccessor()) : m_search_radius(search_radius), m_point_accessor(point_accessor), m_grid_log2(0) { // Resolution of a grid, twice the search radius + some epsilon. coord_t gridres = 2 * m_search_radius + 4; m_grid_resolution = gridres; assert(m_grid_resolution > 0); assert(m_grid_resolution < (coord_t(1) << 30)); // Compute m_grid_log2 = log2(m_grid_resolution) if (m_grid_resolution > 32767) { m_grid_resolution >>= 16; m_grid_log2 += 16; } if (m_grid_resolution > 127) { m_grid_resolution >>= 8; m_grid_log2 += 8; } if (m_grid_resolution > 7) { m_grid_resolution >>= 4; m_grid_log2 += 4; } if (m_grid_resolution > 1) { m_grid_resolution >>= 2; m_grid_log2 += 2; } if (m_grid_resolution > 0) ++ m_grid_log2; m_grid_resolution = 1 << m_grid_log2; assert(m_grid_resolution >= gridres); assert(gridres > m_grid_resolution / 2); } void insert(const ValueType &value) { const Point *pt = m_point_accessor(value); if (pt != nullptr) m_map.emplace(std::make_pair(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2), value)); } void insert(ValueType &&value) { const Point *pt = m_point_accessor(value); if (pt != nullptr) m_map.emplace(std::make_pair(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2), std::move(value))); } // Return a pair of std::pair find(const Point &pt) { // Iterate over 4 closest grid cells around pt, // find the closest start point inside these cells to pt. const ValueType *value_min = nullptr; double dist_min = std::numeric_limits::max(); // Round pt to a closest grid_cell corner. Point grid_corner((pt.x+(m_grid_resolution>>1))>>m_grid_log2, (pt.y+(m_grid_resolution>>1))>>m_grid_log2); // For four neighbors of grid_corner: for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { // Range of fragment starts around grid_corner, close to pt. auto range = m_map.equal_range(Point(grid_corner.x + neighbor_x, grid_corner.y + neighbor_y)); // Find the map entry closest to pt. for (auto it = range.first; it != range.second; ++it) { const ValueType &value = it->second; const Point *pt2 = m_point_accessor(value); if (pt2 != nullptr) { const double d2 = pt.distance_to_sq(*pt2); if (d2 < dist_min) { dist_min = d2; value_min = &value; } } } } } return (value_min != nullptr && dist_min < coordf_t(m_search_radius * m_search_radius)) ? std::make_pair(value_min, dist_min) : std::make_pair(nullptr, std::numeric_limits::max()); } private: typedef typename std::unordered_multimap map_type; PointAccessor m_point_accessor; map_type m_map; coord_t m_search_radius; coord_t m_grid_resolution; coord_t m_grid_log2; }; class Point3 : public Point { public: coord_t z; explicit Point3(coord_t _x = 0, coord_t _y = 0, coord_t _z = 0): Point(_x, _y), z(_z) {}; static Point3 new_scale(coordf_t x, coordf_t y, coordf_t z) { return Point3(coord_t(scale_(x)), coord_t(scale_(y)), coord_t(scale_(z))); } bool operator==(const Point3 &rhs) const { return this->x == rhs.x && this->y == rhs.y && this->z == rhs.z; } bool operator!=(const Point3 &rhs) const { return ! (*this == rhs); } private: // Hide the following inherited methods: bool operator==(const Point &rhs); bool operator!=(const Point &rhs); }; std::ostream& operator<<(std::ostream &stm, const Pointf &pointf); class Pointf { public: typedef coordf_t coord_type; coordf_t x; coordf_t y; explicit Pointf(coordf_t _x = 0, coordf_t _y = 0): x(_x), y(_y) {}; static Pointf new_unscale(coord_t x, coord_t y) { return Pointf(unscale(x), unscale(y)); }; static Pointf new_unscale(const Point &p) { return Pointf(unscale(p.x), unscale(p.y)); }; std::string wkt() const; std::string dump_perl() const; void scale(double factor); void translate(double x, double y); void translate(const Vectorf &vector); void rotate(double angle); void rotate(double angle, const Pointf ¢er); Pointf negative() const; Vectorf vector_to(const Pointf &point) const; Pointf& operator+=(const Pointf& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } Pointf& operator-=(const Pointf& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } Pointf& operator*=(const coordf_t& rhs) { this->x *= rhs; this->y *= rhs; return *this; } bool operator==(const Pointf &rhs) const { return this->x == rhs.x && this->y == rhs.y; } bool operator!=(const Pointf &rhs) const { return ! (*this == rhs); } bool operator< (const Pointf& rhs) const { return this->x < rhs.x || (this->x == rhs.x && this->y < rhs.y); } }; inline Pointf operator+(const Pointf& point1, const Pointf& point2) { return Pointf(point1.x + point2.x, point1.y + point2.y); } inline Pointf operator-(const Pointf& point1, const Pointf& point2) { return Pointf(point1.x - point2.x, point1.y - point2.y); } inline Pointf operator*(double scalar, const Pointf& point2) { return Pointf(scalar * point2.x, scalar * point2.y); } inline Pointf operator*(const Pointf& point2, double scalar) { return Pointf(scalar * point2.x, scalar * point2.y); } inline coordf_t cross(const Pointf &v1, const Pointf &v2) { return v1.x * v2.y - v1.y * v2.x; } inline coordf_t dot(const Pointf &v1, const Pointf &v2) { return v1.x * v2.x + v1.y * v2.y; } inline coordf_t dot(const Pointf &v) { return v.x * v.x + v.y * v.y; } inline double length(const Vectorf &v) { return sqrt(dot(v)); } inline double l2(const Vectorf &v) { return dot(v); } class Pointf3 : public Pointf { public: coordf_t z; explicit Pointf3(coordf_t _x = 0, coordf_t _y = 0, coordf_t _z = 0): Pointf(_x, _y), z(_z) {}; static Pointf3 new_unscale(coord_t x, coord_t y, coord_t z) { return Pointf3(unscale(x), unscale(y), unscale(z)); }; void scale(double factor); void translate(const Vectorf3 &vector); void translate(double x, double y, double z); double distance_to(const Pointf3 &point) const; Pointf3 negative() const; Vectorf3 vector_to(const Pointf3 &point) const; bool operator==(const Pointf3 &rhs) const { return this->x == rhs.x && this->y == rhs.y && this->z == rhs.z; } bool operator!=(const Pointf3 &rhs) const { return ! (*this == rhs); } private: // Hide the following inherited methods: bool operator==(const Pointf &rhs); bool operator!=(const Pointf &rhs); }; template inline TO convert_to(const Point &src) { return TO(typename TO::coord_type(src.x), typename TO::coord_type(src.y)); } template inline TO convert_to(const Pointf &src) { return TO(typename TO::coord_type(src.x), typename TO::coord_type(src.y)); } template inline TO convert_to(const Point3 &src) { return TO(typename TO::coord_type(src.x), typename TO::coord_type(src.y), typename TO::coord_type(src.z)); } template inline TO convert_to(const Pointf3 &src) { return TO(typename TO::coord_type(src.x), typename TO::coord_type(src.y), typename TO::coord_type(src.z)); } } // namespace Slic3r // start Boost #include #include namespace boost { namespace polygon { template <> struct geometry_concept { typedef point_concept type; }; template <> struct point_traits { typedef coord_t coordinate_type; static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) { return (orient == HORIZONTAL) ? point.x : point.y; } }; template <> struct point_mutable_traits { typedef coord_t coordinate_type; static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) { if (orient == HORIZONTAL) point.x = value; else point.y = value; } static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) { Slic3r::Point retval; retval.x = x_value; retval.y = y_value; return retval; } }; } } // end Boost #endif Slic3r-version_1.39.1/xs/src/libslic3r/Polygon.cpp000066400000000000000000000333421324354444700217720ustar00rootroot00000000000000#include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Polygon.hpp" #include "Polyline.hpp" namespace Slic3r { Polygon::operator Polygons() const { Polygons pp; pp.push_back(*this); return pp; } Polygon::operator Polyline() const { return this->split_at_first_point(); } Point& Polygon::operator[](Points::size_type idx) { return this->points[idx]; } const Point& Polygon::operator[](Points::size_type idx) const { return this->points[idx]; } Point Polygon::last_point() const { return this->points.front(); // last point == first point for polygons } Lines Polygon::lines() const { return to_lines(*this); } Polyline Polygon::split_at_vertex(const Point &point) const { // find index of point for (Points::const_iterator it = this->points.begin(); it != this->points.end(); ++it) { if (it->coincides_with(point)) { return this->split_at_index(it - this->points.begin()); } } CONFESS("Point not found"); return Polyline(); } // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline Polygon::split_at_index(int index) const { Polyline polyline; polyline.points.reserve(this->points.size() + 1); for (Points::const_iterator it = this->points.begin() + index; it != this->points.end(); ++it) polyline.points.push_back(*it); for (Points::const_iterator it = this->points.begin(); it != this->points.begin() + index + 1; ++it) polyline.points.push_back(*it); return polyline; } // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline Polygon::split_at_first_point() const { return this->split_at_index(0); } Points Polygon::equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); } /* int64_t Polygon::area2x() const { size_t n = poly.size(); if (n < 3) return 0; int64_t a = 0; for (size_t i = 0, j = n - 1; i < n; ++i) a += int64_t(poly[j].x + poly[i].x) * int64_t(poly[j].y - poly[i].y); j = i; } return -a * 0.5; } */ double Polygon::area() const { size_t n = points.size(); if (n < 3) return 0.; double a = 0.; for (size_t i = 0, j = n - 1; i < n; ++i) { a += double(points[j].x + points[i].x) * double(points[i].y - points[j].y); j = i; } return 0.5 * a; } bool Polygon::is_counter_clockwise() const { return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this)); } bool Polygon::is_clockwise() const { return !this->is_counter_clockwise(); } bool Polygon::make_counter_clockwise() { if (!this->is_counter_clockwise()) { this->reverse(); return true; } return false; } bool Polygon::make_clockwise() { if (this->is_counter_clockwise()) { this->reverse(); return true; } return false; } bool Polygon::is_valid() const { return this->points.size() >= 3; } // Does an unoriented polygon contain a point? // Tested by counting intersections along a horizontal line. bool Polygon::contains(const Point &point) const { // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html bool result = false; Points::const_iterator i = this->points.begin(); Points::const_iterator j = this->points.end() - 1; for (; i != this->points.end(); j = i++) { //FIXME this test is not numerically robust. Particularly, it does not handle horizontal segments at y == point.y well. // Does the ray with y == point.y intersect this line segment? #if 1 if ( ((i->y > point.y) != (j->y > point.y)) && ((double)point.x < (double)(j->x - i->x) * (double)(point.y - i->y) / (double)(j->y - i->y) + (double)i->x) ) result = !result; #else if ((i->y > point.y) != (j->y > point.y)) { // Orientation predicated relative to i-th point. double orient = (double)(point.x - i->x) * (double)(j->y - i->y) - (double)(point.y - i->y) * (double)(j->x - i->x); if ((i->y > j->y) ? (orient > 0.) : (orient < 0.)) result = !result; } #endif } return result; } // this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons() Polygons Polygon::simplify(double tolerance) const { // repeat first point at the end in order to apply Douglas-Peucker // on the whole polygon Points points = this->points; points.push_back(points.front()); Polygon p(MultiPoint::_douglas_peucker(points, tolerance)); p.points.pop_back(); Polygons pp; pp.push_back(p); return simplify_polygons(pp); } void Polygon::simplify(double tolerance, Polygons &polygons) const { Polygons pp = this->simplify(tolerance); polygons.reserve(polygons.size() + pp.size()); polygons.insert(polygons.end(), pp.begin(), pp.end()); } // Only call this on convex polygons or it will return invalid results void Polygon::triangulate_convex(Polygons* polygons) const { for (Points::const_iterator it = this->points.begin() + 2; it != this->points.end(); ++it) { Polygon p; p.points.reserve(3); p.points.push_back(this->points.front()); p.points.push_back(*(it-1)); p.points.push_back(*it); // this should be replaced with a more efficient call to a merge_collinear_segments() method if (p.area() > 0) polygons->push_back(p); } } // center of mass Point Polygon::centroid() const { double area_temp = this->area(); double x_temp = 0; double y_temp = 0; Polyline polyline = this->split_at_first_point(); for (Points::const_iterator point = polyline.points.begin(); point != polyline.points.end() - 1; ++point) { x_temp += (double)( point->x + (point+1)->x ) * ( (double)point->x*(point+1)->y - (double)(point+1)->x*point->y ); y_temp += (double)( point->y + (point+1)->y ) * ( (double)point->x*(point+1)->y - (double)(point+1)->x*point->y ); } return Point(x_temp/(6*area_temp), y_temp/(6*area_temp)); } std::string Polygon::wkt() const { std::ostringstream wkt; wkt << "POLYGON(("; for (Points::const_iterator p = this->points.begin(); p != this->points.end(); ++p) { wkt << p->x << " " << p->y; if (p != this->points.end()-1) wkt << ","; } wkt << "))"; return wkt.str(); } // find all concave vertices (i.e. having an internal angle greater than the supplied angle) // (external = right side, thus we consider ccw orientation) Points Polygon::concave_points(double angle) const { Points points; angle = 2*PI - angle; // check whether first point forms a concave angle if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) <= angle) points.push_back(this->points.front()); // check whether points 1..(n-1) form concave angles for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { if (p->ccw_angle(*(p-1), *(p+1)) <= angle) points.push_back(*p); } // check whether last point forms a concave angle if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) <= angle) points.push_back(this->points.back()); return points; } // find all convex vertices (i.e. having an internal angle smaller than the supplied angle) // (external = right side, thus we consider ccw orientation) Points Polygon::convex_points(double angle) const { Points points; angle = 2*PI - angle; // check whether first point forms a convex angle if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle) points.push_back(this->points.front()); // check whether points 1..(n-1) form convex angles for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { if (p->ccw_angle(*(p-1), *(p+1)) >= angle) points.push_back(*p); } // check whether last point forms a convex angle if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) >= angle) points.push_back(this->points.back()); return points; } // Projection of a point onto the polygon. Point Polygon::point_projection(const Point &point) const { Point proj = point; double dmin = std::numeric_limits::max(); if (! this->points.empty()) { for (size_t i = 0; i < this->points.size(); ++ i) { const Point &pt0 = this->points[i]; const Point &pt1 = this->points[(i + 1 == this->points.size()) ? 0 : i + 1]; double d = pt0.distance_to(point); if (d < dmin) { dmin = d; proj = pt0; } d = pt1.distance_to(point); if (d < dmin) { dmin = d; proj = pt1; } Pointf v1(coordf_t(pt1.x - pt0.x), coordf_t(pt1.y - pt0.y)); coordf_t div = dot(v1); if (div > 0.) { Pointf v2(coordf_t(point.x - pt0.x), coordf_t(point.y - pt0.y)); coordf_t t = dot(v1, v2) / div; if (t > 0. && t < 1.) { Point foot(coord_t(floor(coordf_t(pt0.x) + t * v1.x + 0.5)), coord_t(floor(coordf_t(pt0.y) + t * v1.y + 0.5))); d = foot.distance_to(point); if (d < dmin) { dmin = d; proj = foot; } } } } } return proj; } BoundingBox get_extents(const Polygon &poly) { return poly.bounding_box(); } BoundingBox get_extents(const Polygons &polygons) { BoundingBox bb; if (! polygons.empty()) { bb = get_extents(polygons.front()); for (size_t i = 1; i < polygons.size(); ++ i) bb.merge(get_extents(polygons[i])); } return bb; } BoundingBox get_extents_rotated(const Polygon &poly, double angle) { return get_extents_rotated(poly.points, angle); } BoundingBox get_extents_rotated(const Polygons &polygons, double angle) { BoundingBox bb; if (! polygons.empty()) { bb = get_extents_rotated(polygons.front().points, angle); for (size_t i = 1; i < polygons.size(); ++ i) bb.merge(get_extents_rotated(polygons[i].points, angle)); } return bb; } extern std::vector get_extents_vector(const Polygons &polygons) { std::vector out; out.reserve(polygons.size()); for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++ it) out.push_back(get_extents(*it)); return out; } static inline bool is_stick(const Point &p1, const Point &p2, const Point &p3) { Point v1 = p2 - p1; Point v2 = p3 - p2; int64_t dir = int64_t(v1.x) * int64_t(v2.x) + int64_t(v1.y) * int64_t(v2.y); if (dir > 0) // p3 does not turn back to p1. Do not remove p2. return false; double l2_1 = double(v1.x) * double(v1.x) + double(v1.y) * double(v1.y); double l2_2 = double(v2.x) * double(v2.x) + double(v2.y) * double(v2.y); if (dir == 0) // p1, p2, p3 may make a perpendicular corner, or there is a zero edge length. // Remove p2 if it is coincident with p1 or p2. return l2_1 == 0 || l2_2 == 0; // p3 turns back to p1 after p2. Are p1, p2, p3 collinear? // Calculate distance from p3 to a segment (p1, p2) or from p1 to a segment(p2, p3), // whichever segment is longer double cross = double(v1.x) * double(v2.y) - double(v2.x) * double(v1.y); double dist2 = cross * cross / std::max(l2_1, l2_2); return dist2 < EPSILON * EPSILON; } bool remove_sticks(Polygon &poly) { bool modified = false; size_t j = 1; for (size_t i = 1; i + 1 < poly.points.size(); ++ i) { if (! is_stick(poly[j-1], poly[i], poly[i+1])) { // Keep the point. if (j < i) poly.points[j] = poly.points[i]; ++ j; } } if (++ j < poly.points.size()) { poly.points[j-1] = poly.points.back(); poly.points.erase(poly.points.begin() + j, poly.points.end()); modified = true; } while (poly.points.size() >= 3 && is_stick(poly.points[poly.points.size()-2], poly.points.back(), poly.points.front())) { poly.points.pop_back(); modified = true; } while (poly.points.size() >= 3 && is_stick(poly.points.back(), poly.points.front(), poly.points[1])) poly.points.erase(poly.points.begin()); return modified; } bool remove_sticks(Polygons &polys) { bool modified = false; size_t j = 0; for (size_t i = 0; i < polys.size(); ++ i) { modified |= remove_sticks(polys[i]); if (polys[i].points.size() >= 3) { if (j < i) std::swap(polys[i].points, polys[j].points); ++ j; } } if (j < polys.size()) polys.erase(polys.begin() + j, polys.end()); return modified; } bool remove_degenerate(Polygons &polys) { bool modified = false; size_t j = 0; for (size_t i = 0; i < polys.size(); ++ i) { if (polys[i].points.size() >= 3) { if (j < i) std::swap(polys[i].points, polys[j].points); ++ j; } else modified = true; } if (j < polys.size()) polys.erase(polys.begin() + j, polys.end()); return modified; } bool remove_small(Polygons &polys, double min_area) { bool modified = false; size_t j = 0; for (size_t i = 0; i < polys.size(); ++ i) { if (std::abs(polys[i].area()) >= min_area) { if (j < i) std::swap(polys[i].points, polys[j].points); ++ j; } else modified = true; } if (j < polys.size()) polys.erase(polys.begin() + j, polys.end()); return modified; } } Slic3r-version_1.39.1/xs/src/libslic3r/Polygon.hpp000066400000000000000000000210431324354444700217720ustar00rootroot00000000000000#ifndef slic3r_Polygon_hpp_ #define slic3r_Polygon_hpp_ #include "libslic3r.h" #include #include #include "Line.hpp" #include "MultiPoint.hpp" #include "Polyline.hpp" namespace Slic3r { class Polygon; typedef std::vector Polygons; class Polygon : public MultiPoint { public: operator Polygons() const; operator Polyline() const; Point& operator[](Points::size_type idx); const Point& operator[](Points::size_type idx) const; Polygon() {} explicit Polygon(const Points &points): MultiPoint(points) {} Polygon(const Polygon &other) : MultiPoint(other.points) {} Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {} Polygon& operator=(const Polygon &other) { points = other.points; return *this; } Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; } Point last_point() const; virtual Lines lines() const; Polyline split_at_vertex(const Point &point) const; // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline split_at_index(int index) const; // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline split_at_first_point() const; Points equally_spaced_points(double distance) const; double area() const; bool is_counter_clockwise() const; bool is_clockwise() const; bool make_counter_clockwise(); bool make_clockwise(); bool is_valid() const; // Does an unoriented polygon contain a point? // Tested by counting intersections along a horizontal line. bool contains(const Point &point) const; Polygons simplify(double tolerance) const; void simplify(double tolerance, Polygons &polygons) const; void triangulate_convex(Polygons* polygons) const; Point centroid() const; std::string wkt() const; Points concave_points(double angle = PI) const; Points convex_points(double angle = PI) const; // Projection of a point onto the polygon. Point point_projection(const Point &point) const; }; extern BoundingBox get_extents(const Polygon &poly); extern BoundingBox get_extents(const Polygons &polygons); extern BoundingBox get_extents_rotated(const Polygon &poly, double angle); extern BoundingBox get_extents_rotated(const Polygons &polygons, double angle); extern std::vector get_extents_vector(const Polygons &polygons); inline double total_length(const Polygons &polylines) { double total = 0; for (Polygons::const_iterator it = polylines.begin(); it != polylines.end(); ++it) total += it->length(); return total; } // Remove sticks (tentacles with zero area) from the polygon. extern bool remove_sticks(Polygon &poly); extern bool remove_sticks(Polygons &polys); // Remove polygons with less than 3 edges. extern bool remove_degenerate(Polygons &polys); extern bool remove_small(Polygons &polys, double min_area); // Append a vector of polygons at the end of another vector of polygons. inline void polygons_append(Polygons &dst, const Polygons &src) { dst.insert(dst.end(), src.begin(), src.end()); } inline void polygons_append(Polygons &dst, Polygons &&src) { if (dst.empty()) { dst = std::move(src); } else { std::move(std::begin(src), std::end(src), std::back_inserter(dst)); src.clear(); } } inline void polygons_rotate(Polygons &polys, double angle) { const double cos_angle = cos(angle); const double sin_angle = sin(angle); for (Polygon &p : polys) p.rotate(cos_angle, sin_angle); } inline Points to_points(const Polygon &poly) { return poly.points; } inline Points to_points(const Polygons &polys) { size_t n_points = 0; for (size_t i = 0; i < polys.size(); ++ i) n_points += polys[i].points.size(); Points points; points.reserve(n_points); for (const Polygon &poly : polys) append(points, poly.points); return points; } inline Lines to_lines(const Polygon &poly) { Lines lines; lines.reserve(poly.points.size()); for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) lines.push_back(Line(*it, *(it + 1))); lines.push_back(Line(poly.points.back(), poly.points.front())); return lines; } inline Lines to_lines(const Polygons &polys) { size_t n_lines = 0; for (size_t i = 0; i < polys.size(); ++ i) n_lines += polys[i].points.size(); Lines lines; lines.reserve(n_lines); for (size_t i = 0; i < polys.size(); ++ i) { const Polygon &poly = polys[i]; for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) lines.push_back(Line(*it, *(it + 1))); lines.push_back(Line(poly.points.back(), poly.points.front())); } return lines; } inline Polylines to_polylines(const Polygons &polys) { Polylines polylines; polylines.assign(polys.size(), Polyline()); size_t idx = 0; for (Polygons::const_iterator it = polys.begin(); it != polys.end(); ++ it) { Polyline &pl = polylines[idx ++]; pl.points = it->points; pl.points.push_back(it->points.front()); } assert(idx == polylines.size()); return polylines; } inline Polylines to_polylines(Polygons &&polys) { Polylines polylines; polylines.assign(polys.size(), Polyline()); size_t idx = 0; for (Polygons::const_iterator it = polys.begin(); it != polys.end(); ++ it) { Polyline &pl = polylines[idx ++]; pl.points = std::move(it->points); pl.points.push_back(it->points.front()); } assert(idx == polylines.size()); return polylines; } } // Slic3r // start Boost #include namespace boost { namespace polygon { template <> struct geometry_concept{ typedef polygon_concept type; }; template <> struct polygon_traits { typedef coord_t coordinate_type; typedef Slic3r::Points::const_iterator iterator_type; typedef Slic3r::Point point_type; // Get the begin iterator static inline iterator_type begin_points(const Slic3r::Polygon& t) { return t.points.begin(); } // Get the end iterator static inline iterator_type end_points(const Slic3r::Polygon& t) { return t.points.end(); } // Get the number of sides of the polygon static inline std::size_t size(const Slic3r::Polygon& t) { return t.points.size(); } // Get the winding direction of the polygon static inline winding_direction winding(const Slic3r::Polygon& /* t */) { return unknown_winding; } }; template <> struct polygon_mutable_traits { // expects stl style iterators template static inline Slic3r::Polygon& set_points(Slic3r::Polygon& polygon, iT input_begin, iT input_end) { polygon.points.clear(); while (input_begin != input_end) { polygon.points.push_back(Slic3r::Point()); boost::polygon::assign(polygon.points.back(), *input_begin); ++input_begin; } // skip last point since Boost will set last point = first point polygon.points.pop_back(); return polygon; } }; template <> struct geometry_concept { typedef polygon_set_concept type; }; //next we map to the concept through traits template <> struct polygon_set_traits { typedef coord_t coordinate_type; typedef Slic3r::Polygons::const_iterator iterator_type; typedef Slic3r::Polygons operator_arg_type; static inline iterator_type begin(const Slic3r::Polygons& polygon_set) { return polygon_set.begin(); } static inline iterator_type end(const Slic3r::Polygons& polygon_set) { return polygon_set.end(); } //don't worry about these, just return false from them static inline bool clean(const Slic3r::Polygons& /* polygon_set */) { return false; } static inline bool sorted(const Slic3r::Polygons& /* polygon_set */) { return false; } }; template <> struct polygon_set_mutable_traits { template static inline void set(Slic3r::Polygons& polygons, input_iterator_type input_begin, input_iterator_type input_end) { polygons.assign(input_begin, input_end); } }; } } // end Boost #endif Slic3r-version_1.39.1/xs/src/libslic3r/Polyline.cpp000066400000000000000000000171061324354444700221360ustar00rootroot00000000000000#include "BoundingBox.hpp" #include "Polyline.hpp" #include "ExPolygon.hpp" #include "ExPolygonCollection.hpp" #include "Line.hpp" #include "Polygon.hpp" #include #include namespace Slic3r { Polyline::operator Polylines() const { Polylines polylines; polylines.push_back(*this); return polylines; } Polyline::operator Line() const { if (this->points.size() > 2) CONFESS("Can't convert polyline with more than two points to a line"); return Line(this->points.front(), this->points.back()); } Point Polyline::last_point() const { return this->points.back(); } Point Polyline::leftmost_point() const { Point p = this->points.front(); for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) { if (it->x < p.x) p = *it; } return p; } Lines Polyline::lines() const { Lines lines; if (this->points.size() >= 2) { lines.reserve(this->points.size() - 1); for (Points::const_iterator it = this->points.begin(); it != this->points.end()-1; ++it) { lines.push_back(Line(*it, *(it + 1))); } } return lines; } // removes the given distance from the end of the polyline void Polyline::clip_end(double distance) { while (distance > 0) { Point last_point = this->last_point(); this->points.pop_back(); if (this->points.empty()) break; double last_segment_length = last_point.distance_to(this->last_point()); if (last_segment_length <= distance) { distance -= last_segment_length; continue; } Line segment(last_point, this->last_point()); this->points.push_back(segment.point_at(distance)); distance = 0; } } // removes the given distance from the start of the polyline void Polyline::clip_start(double distance) { this->reverse(); this->clip_end(distance); if (this->points.size() >= 2) this->reverse(); } void Polyline::extend_end(double distance) { // relocate last point by extending the last segment by the specified length Line line( this->points.back(), *(this->points.end() - 2) ); this->points.back() = line.point_at(-distance); } void Polyline::extend_start(double distance) { // relocate first point by extending the first segment by the specified length this->points.front() = Line(this->points.front(), this->points[1]).point_at(-distance); } /* this method returns a collection of points picked on the polygon contour so that they are evenly spaced according to the input distance */ Points Polyline::equally_spaced_points(double distance) const { Points points; points.push_back(this->first_point()); double len = 0; for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) { double segment_length = it->distance_to(*(it-1)); len += segment_length; if (len < distance) continue; if (len == distance) { points.push_back(*it); len = 0; continue; } double take = segment_length - (len - distance); // how much we take of this segment Line segment(*(it-1), *it); points.push_back(segment.point_at(take)); --it; len = -take; } return points; } void Polyline::simplify(double tolerance) { this->points = MultiPoint::_douglas_peucker(this->points, tolerance); } /* This method simplifies all *lines* contained in the supplied area */ template void Polyline::simplify_by_visibility(const T &area) { Points &pp = this->points; size_t s = 0; bool did_erase = false; for (size_t i = s+2; i < pp.size(); i = s + 2) { if (area.contains(Line(pp[s], pp[i]))) { pp.erase(pp.begin() + s + 1, pp.begin() + i); did_erase = true; } else { ++s; } } if (did_erase) this->simplify_by_visibility(area); } template void Polyline::simplify_by_visibility(const ExPolygon &area); template void Polyline::simplify_by_visibility(const ExPolygonCollection &area); void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const { if (this->points.empty()) return; // find the line to split at size_t line_idx = 0; Point p = this->first_point(); double min = point.distance_to(p); Lines lines = this->lines(); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { Point p_tmp = point.projection_onto(*line); if (point.distance_to(p_tmp) < min) { p = p_tmp; min = point.distance_to(p); line_idx = line - lines.begin(); } } // create first half p1->points.clear(); for (Lines::const_iterator line = lines.begin(); line != lines.begin() + line_idx + 1; ++line) { if (!line->a.coincides_with(p)) p1->points.push_back(line->a); } // we add point instead of p because they might differ because of numerical issues // and caller might want to rely on point belonging to result polylines p1->points.push_back(point); // create second half p2->points.clear(); p2->points.push_back(point); for (Lines::const_iterator line = lines.begin() + line_idx; line != lines.end(); ++line) { p2->points.push_back(line->b); } } bool Polyline::is_straight() const { /* Check that each segment's direction is equal to the line connecting first point and last point. (Checking each line against the previous one would cause the error to accumulate.) */ double dir = Line(this->first_point(), this->last_point()).direction(); Lines lines = this->lines(); for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { if (!line->parallel_to(dir)) return false; } return true; } std::string Polyline::wkt() const { std::ostringstream wkt; wkt << "LINESTRING(("; for (Points::const_iterator p = this->points.begin(); p != this->points.end(); ++p) { wkt << p->x << " " << p->y; if (p != this->points.end()-1) wkt << ","; } wkt << "))"; return wkt.str(); } BoundingBox get_extents(const Polyline &polyline) { return polyline.bounding_box(); } BoundingBox get_extents(const Polylines &polylines) { BoundingBox bb; if (! polylines.empty()) { bb = polylines.front().bounding_box(); for (size_t i = 1; i < polylines.size(); ++ i) bb.merge(polylines[i]); } return bb; } bool remove_degenerate(Polylines &polylines) { bool modified = false; size_t j = 0; for (size_t i = 0; i < polylines.size(); ++ i) { if (polylines[i].points.size() >= 2) { if (j < i) std::swap(polylines[i].points, polylines[j].points); ++ j; } else modified = true; } if (j < polylines.size()) polylines.erase(polylines.begin() + j, polylines.end()); return modified; } ThickLines ThickPolyline::thicklines() const { ThickLines lines; if (this->points.size() >= 2) { lines.reserve(this->points.size() - 1); for (size_t i = 0; i < this->points.size()-1; ++i) { ThickLine line(this->points[i], this->points[i+1]); line.a_width = this->width[2*i]; line.b_width = this->width[2*i+1]; lines.push_back(line); } } return lines; } void ThickPolyline::reverse() { Polyline::reverse(); std::reverse(this->width.begin(), this->width.end()); std::swap(this->endpoints.first, this->endpoints.second); } } Slic3r-version_1.39.1/xs/src/libslic3r/Polyline.hpp000066400000000000000000000100061324354444700221330ustar00rootroot00000000000000#ifndef slic3r_Polyline_hpp_ #define slic3r_Polyline_hpp_ #include "libslic3r.h" #include "Line.hpp" #include "MultiPoint.hpp" #include #include namespace Slic3r { class Polyline; class ThickPolyline; typedef std::vector Polylines; typedef std::vector ThickPolylines; class Polyline : public MultiPoint { public: Polyline() {}; Polyline(const Polyline &other) : MultiPoint(other.points) {} Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {} Polyline& operator=(const Polyline &other) { points = other.points; return *this; } Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; } void append(const Point &point) { this->points.push_back(point); } void append(const Points &src) { this->append(src.begin(), src.end()); } void append(const Points::const_iterator &begin, const Points::const_iterator &end) { this->points.insert(this->points.end(), begin, end); } void append(Points &&src) { if (this->points.empty()) { this->points = std::move(src); } else { this->points.insert(this->points.end(), src.begin(), src.end()); src.clear(); } } void append(const Polyline &src) { points.insert(points.end(), src.points.begin(), src.points.end()); } void append(Polyline &&src) { if (this->points.empty()) { this->points = std::move(src.points); } else { this->points.insert(this->points.end(), src.points.begin(), src.points.end()); src.points.clear(); } } operator Polylines() const; operator Line() const; Point last_point() const; Point leftmost_point() const; virtual Lines lines() const; void clip_end(double distance); void clip_start(double distance); void extend_end(double distance); void extend_start(double distance); Points equally_spaced_points(double distance) const; void simplify(double tolerance); template void simplify_by_visibility(const T &area); void split_at(const Point &point, Polyline* p1, Polyline* p2) const; bool is_straight() const; std::string wkt() const; }; extern BoundingBox get_extents(const Polyline &polyline); extern BoundingBox get_extents(const Polylines &polylines); inline double total_length(const Polylines &polylines) { double total = 0; for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) total += it->length(); return total; } inline Lines to_lines(const Polyline &poly) { Lines lines; if (poly.points.size() >= 2) { lines.reserve(poly.points.size() - 1); for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) lines.push_back(Line(*it, *(it + 1))); } return lines; } inline Lines to_lines(const Polylines &polys) { size_t n_lines = 0; for (size_t i = 0; i < polys.size(); ++ i) if (polys[i].points.size() > 1) n_lines += polys[i].points.size() - 1; Lines lines; lines.reserve(n_lines); for (size_t i = 0; i < polys.size(); ++ i) { const Polyline &poly = polys[i]; for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) lines.push_back(Line(*it, *(it + 1))); } return lines; } inline void polylines_append(Polylines &dst, const Polylines &src) { dst.insert(dst.end(), src.begin(), src.end()); } inline void polylines_append(Polylines &dst, Polylines &&src) { if (dst.empty()) { dst = std::move(src); } else { std::move(std::begin(src), std::end(src), std::back_inserter(dst)); src.clear(); } } bool remove_degenerate(Polylines &polylines); class ThickPolyline : public Polyline { public: std::vector width; std::pair endpoints; ThickPolyline() : endpoints(std::make_pair(false, false)) {}; ThickLines thicklines() const; void reverse(); }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/PolylineCollection.cpp000066400000000000000000000052151324354444700241500ustar00rootroot00000000000000#include "PolylineCollection.hpp" namespace Slic3r { struct Chaining { Point first; Point last; size_t idx; }; template inline int nearest_point_index(const std::vector &pairs, const Point &start_near, bool no_reverse) { T dmin = std::numeric_limits::max(); int idx = 0; for (std::vector::const_iterator it = pairs.begin(); it != pairs.end(); ++it) { T d = sqr(T(start_near.x - it->first.x)); if (d <= dmin) { d += sqr(T(start_near.y - it->first.y)); if (d < dmin) { idx = (it - pairs.begin()) * 2; dmin = d; if (dmin < EPSILON) break; } } if (! no_reverse) { d = sqr(T(start_near.x - it->last.x)); if (d <= dmin) { d += sqr(T(start_near.y - it->last.y)); if (d < dmin) { idx = (it - pairs.begin()) * 2 + 1; dmin = d; if (dmin < EPSILON) break; } } } } return idx; } Polylines PolylineCollection::_chained_path_from( const Polylines &src, Point start_near, bool no_reverse, bool move_from_src) { std::vector endpoints; endpoints.reserve(src.size()); for (size_t i = 0; i < src.size(); ++ i) { Chaining c; c.first = src[i].first_point(); if (! no_reverse) c.last = src[i].last_point(); c.idx = i; endpoints.push_back(c); } Polylines retval; while (! endpoints.empty()) { // find nearest point int endpoint_index = nearest_point_index(endpoints, start_near, no_reverse); assert(endpoint_index >= 0 && endpoint_index < endpoints.size() * 2); if (move_from_src) { retval.push_back(std::move(src[endpoints[endpoint_index/2].idx])); } else { retval.push_back(src[endpoints[endpoint_index/2].idx]); } if (endpoint_index & 1) retval.back().reverse(); endpoints.erase(endpoints.begin() + endpoint_index/2); start_near = retval.back().last_point(); } return retval; } Point PolylineCollection::leftmost_point(const Polylines &polylines) { if (polylines.empty()) CONFESS("leftmost_point() called on empty PolylineCollection"); Polylines::const_iterator it = polylines.begin(); Point p = it->leftmost_point(); for (++ it; it != polylines.end(); ++it) { Point p2 = it->leftmost_point(); if (p2.x < p.x) p = p2; } return p; } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/PolylineCollection.hpp000066400000000000000000000035001324354444700241500ustar00rootroot00000000000000#ifndef slic3r_PolylineCollection_hpp_ #define slic3r_PolylineCollection_hpp_ #include "libslic3r.h" #include "Polyline.hpp" namespace Slic3r { class PolylineCollection { static Polylines _chained_path_from( const Polylines &src, Point start_near, bool no_reverse, bool move_from_src); public: Polylines polylines; void chained_path(PolylineCollection* retval, bool no_reverse = false) const { retval->polylines = chained_path(this->polylines, no_reverse); } void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const { retval->polylines = chained_path_from(this->polylines, start_near, no_reverse); } Point leftmost_point() const { return leftmost_point(polylines); } void append(const Polylines &polylines) { this->polylines.insert(this->polylines.end(), polylines.begin(), polylines.end()); } static Point leftmost_point(const Polylines &polylines); static Polylines chained_path(Polylines &&src, bool no_reverse = false) { return (src.empty() || src.front().points.empty()) ? Polylines() : _chained_path_from(src, src.front().first_point(), no_reverse, true); } static Polylines chained_path_from(Polylines &&src, Point start_near, bool no_reverse = false) { return _chained_path_from(src, start_near, no_reverse, true); } static Polylines chained_path(const Polylines &src, bool no_reverse = false) { return (src.empty() || src.front().points.empty()) ? Polylines() : _chained_path_from(src, src.front().first_point(), no_reverse, false); } static Polylines chained_path_from(const Polylines &src, Point start_near, bool no_reverse = false) { return _chained_path_from(src, start_near, no_reverse, false); } }; } #endif Slic3r-version_1.39.1/xs/src/libslic3r/Print.cpp000066400000000000000000001445351324354444700214460ustar00rootroot00000000000000#include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Extruder.hpp" #include "Flow.hpp" #include "Geometry.hpp" #include "SupportMaterial.hpp" #include "GCode/WipeTowerPrusaMM.hpp" #include #include #include #include namespace Slic3r { template class PrintState; template class PrintState; void Print::clear_objects() { for (int i = int(this->objects.size())-1; i >= 0; --i) this->delete_object(i); for (PrintRegion *region : this->regions) delete region; this->regions.clear(); } void Print::delete_object(size_t idx) { // destroy object and remove it from our container delete this->objects[idx]; this->objects.erase(this->objects.begin() + idx); this->invalidate_all_steps(); // TODO: purge unused regions } void Print::reload_object(size_t /* idx */) { /* TODO: this method should check whether the per-object config and per-material configs have changed in such a way that regions need to be rearranged or we can just apply the diff and invalidate something. Same logic as apply_config() For now we just re-add all objects since we haven't implemented this incremental logic yet. This should also check whether object volumes (parts) have changed. */ // collect all current model objects ModelObjectPtrs model_objects; model_objects.reserve(this->objects.size()); for (PrintObject *object : this->objects) model_objects.push_back(object->model_object()); // remove our print objects this->clear_objects(); // re-add model objects for (ModelObject *mo : model_objects) this->add_model_object(mo); } // Reloads the model instances into the print class. // The slicing shall not be running as the modified model instances at the print // are used for the brim & skirt calculation. // Returns true if the brim or skirt have been invalidated. bool Print::reload_model_instances() { bool invalidated = false; for (PrintObject *object : this->objects) invalidated |= object->reload_model_instances(); return invalidated; } PrintRegion* Print::add_region() { regions.push_back(new PrintRegion(this)); return regions.back(); } // Called by Print::apply_config(). // This method only accepts PrintConfig option keys. bool Print::invalidate_state_by_config_options(const std::vector &opt_keys) { if (opt_keys.empty()) return false; // Cache the plenty of parameters, which influence the G-code generator only, // or they are only notes not influencing the generated G-code. static std::unordered_set steps_ignore = { "avoid_crossing_perimeters", "bed_shape", "bed_temperature", "before_layer_gcode", "between_objects_gcode", "bridge_acceleration", "bridge_fan_speed", "cooling", "default_acceleration", "deretract_speed", "disable_fan_first_layers", "duplicate_distance", "end_gcode", "end_filament_gcode", "extrusion_axis", "extruder_clearance_height", "extruder_clearance_radius", "extruder_colour", "extruder_offset", "extrusion_multiplier", "fan_always_on", "fan_below_layer_time", "filament_colour", "filament_diameter", "filament_density", "filament_notes", "filament_cost", "filament_max_volumetric_speed", "first_layer_acceleration", "first_layer_bed_temperature", "first_layer_speed", "gcode_comments", "gcode_flavor", "infill_acceleration", "infill_first", "layer_gcode", "min_fan_speed", "max_fan_speed", "min_print_speed", "max_print_speed", "max_volumetric_speed", "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", "notes", "only_retract_when_crossing_perimeters", "output_filename_format", "perimeter_acceleration", "post_process", "printer_notes", "retract_before_travel", "retract_before_wipe", "retract_layer_change", "retract_length", "retract_length_toolchange", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_restart_extra", "retract_restart_extra_toolchange", "retract_speed", "slowdown_below_layer_time", "standby_temperature_delta", "start_gcode", "start_filament_gcode", "toolchange_gcode", "threads", "travel_speed", "use_firmware_retraction", "use_relative_e_distances", "use_volumetric_e", "variable_layer_height", "wipe" }; std::vector steps; std::vector osteps; bool invalidated = false; for (const t_config_option_key &opt_key : opt_keys) { if (steps_ignore.find(opt_key) != steps_ignore.end()) { // These options only affect G-code export or they are just notes without influence on the generated G-code, // so there is nothing to invalidate. } else if ( opt_key == "skirts" || opt_key == "skirt_height" || opt_key == "skirt_distance" || opt_key == "min_skirt_length" || opt_key == "ooze_prevention") { steps.emplace_back(psSkirt); } else if (opt_key == "brim_width") { steps.emplace_back(psBrim); steps.emplace_back(psSkirt); } else if ( opt_key == "nozzle_diameter" || opt_key == "resolution") { osteps.emplace_back(posSlice); } else if ( opt_key == "complete_objects" || opt_key == "filament_type" || opt_key == "filament_soluble" || opt_key == "first_layer_temperature" || opt_key == "gcode_flavor" || opt_key == "single_extruder_multi_material" || opt_key == "spiral_vase" || opt_key == "temperature" || opt_key == "wipe_tower" || opt_key == "wipe_tower_x" || opt_key == "wipe_tower_y" || opt_key == "wipe_tower_width" || opt_key == "wipe_tower_per_color_wipe" || opt_key == "z_offset") { steps.emplace_back(psWipeTower); } else if ( opt_key == "first_layer_extrusion_width" || opt_key == "min_layer_height" || opt_key == "max_layer_height") { osteps.emplace_back(posPerimeters); osteps.emplace_back(posInfill); osteps.emplace_back(posSupportMaterial); steps.emplace_back(psSkirt); steps.emplace_back(psBrim); steps.emplace_back(psWipeTower); } else { // for legacy, if we can't handle this option let's invalidate all steps //FIXME invalidate all steps of all objects as well? invalidated |= this->invalidate_all_steps(); // Continue with the other opt_keys to possibly invalidate any object specific steps. } } sort_remove_duplicates(steps); for (PrintStep step : steps) invalidated |= this->invalidate_step(step); sort_remove_duplicates(osteps); for (PrintObjectStep ostep : osteps) for (PrintObject *object : this->objects) invalidated |= object->invalidate_step(ostep); return invalidated; } bool Print::invalidate_step(PrintStep step) { bool invalidated = this->state.invalidate(step); // Propagate to dependent steps. //FIXME Why should skirt invalidate brim? Shouldn't it be vice versa? if (step == psSkirt) invalidated |= this->state.invalidate(psBrim); return invalidated; } // returns true if an object step is done on all objects // and there's at least one object bool Print::step_done(PrintObjectStep step) const { if (this->objects.empty()) return false; for (const PrintObject *object : this->objects) if (!object->state.is_done(step)) return false; return true; } // returns 0-based indices of used extruders std::vector Print::object_extruders() const { std::vector extruders; for (PrintRegion* region : this->regions) { // these checks reflect the same logic used in the GUI for enabling/disabling // extruder selection fields if (region->config.perimeters.value > 0 || this->config.brim_width.value > 0) extruders.push_back(region->config.perimeter_extruder - 1); if (region->config.fill_density.value > 0) extruders.push_back(region->config.infill_extruder - 1); if (region->config.top_solid_layers.value > 0 || region->config.bottom_solid_layers.value > 0) extruders.push_back(region->config.solid_infill_extruder - 1); } sort_remove_duplicates(extruders); return extruders; } // returns 0-based indices of used extruders std::vector Print::support_material_extruders() const { std::vector extruders; bool support_uses_current_extruder = false; for (PrintObject *object : this->objects) { if (object->has_support_material()) { if (object->config.support_material_extruder == 0) support_uses_current_extruder = true; else extruders.push_back(object->config.support_material_extruder - 1); if (object->config.support_material_interface_extruder == 0) support_uses_current_extruder = true; else extruders.push_back(object->config.support_material_interface_extruder - 1); } } if (support_uses_current_extruder) // Add all object extruders to the support extruders as it is not know which one will be used to print supports. append(extruders, this->object_extruders()); sort_remove_duplicates(extruders); return extruders; } // returns 0-based indices of used extruders std::vector Print::extruders() const { std::vector extruders = this->object_extruders(); append(extruders, this->support_material_extruders()); sort_remove_duplicates(extruders); return extruders; } void Print::_simplify_slices(double distance) { for (PrintObject *object : this->objects) { for (Layer *layer : object->layers) { layer->slices.simplify(distance); for (LayerRegion *layerm : layer->regions) layerm->slices.simplify(distance); } } } double Print::max_allowed_layer_height() const { double nozzle_diameter_max = 0.; for (unsigned int extruder_id : this->extruders()) nozzle_diameter_max = std::max(nozzle_diameter_max, this->config.nozzle_diameter.get_at(extruder_id)); return nozzle_diameter_max; } // Caller is responsible for supplying models whose objects don't collide // and have explicit instance positions. void Print::add_model_object(ModelObject* model_object, int idx) { // Initialize a new print object and store it at the given position. PrintObject *object = new PrintObject(this, model_object, model_object->raw_bounding_box()); if (idx != -1) { delete this->objects[idx]; this->objects[idx] = object; } else this->objects.emplace_back(object); // Invalidate all print steps. this->invalidate_all_steps(); for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) { // Get the config applied to this volume. PrintRegionConfig config = this->_region_config_from_model_volume(*model_object->volumes[volume_id]); // Find an existing print region with the same config. size_t region_id = size_t(-1); for (size_t i = 0; i < this->regions.size(); ++ i) if (config.equals(this->regions[i]->config)) { region_id = i; break; } // If no region exists with the same config, create a new one. if (region_id == size_t(-1)) { region_id = this->regions.size(); this->add_region()->config.apply(config); } // Assign volume to a region. object->add_region_volume(region_id, volume_id); } // Apply config to print object. object->config.apply(this->default_object_config); normalize_and_apply_config(object->config, model_object->config); // update placeholders { // get the first input file name std::string input_file; std::vector v_scale; for (const PrintObject *object : this->objects) { const ModelObject &mobj = *object->model_object(); v_scale.push_back(boost::lexical_cast(mobj.instances[0]->scaling_factor*100) + "%"); if (input_file.empty()) input_file = mobj.input_file; } PlaceholderParser &pp = this->placeholder_parser; pp.set("scale", v_scale); if (! input_file.empty()) { // get basename with and without suffix const std::string input_basename = boost::filesystem::path(input_file).filename().string(); pp.set("input_filename", input_basename); const std::string input_basename_base = input_basename.substr(0, input_basename.find_last_of(".")); pp.set("input_filename_base", input_basename_base); } } } bool Print::apply_config(DynamicPrintConfig config) { // we get a copy of the config object so we can modify it safely config.normalize(); // apply variables to placeholder parser this->placeholder_parser.apply_config(config); // handle changes to print config t_config_option_keys print_diff = this->config.diff(config); this->config.apply_only(config, print_diff, true); bool invalidated = this->invalidate_state_by_config_options(print_diff); // handle changes to object config defaults this->default_object_config.apply(config, true); for (PrintObject *object : this->objects) { // we don't assume that config contains a full ObjectConfig, // so we base it on the current print-wise default PrintObjectConfig new_config = this->default_object_config; // we override the new config with object-specific options normalize_and_apply_config(new_config, object->model_object()->config); // Force a refresh of a variable layer height profile at the PrintObject if it is not valid. if (! object->layer_height_profile_valid) { // The layer_height_profile is not valid for some reason (updated by the user or invalidated due to some option change). // Invalidate the slicing step, which in turn invalidates everything. object->invalidate_step(posSlice); // Trigger recalculation. invalidated = true; } // check whether the new config is different from the current one t_config_option_keys diff = object->config.diff(new_config); object->config.apply_only(new_config, diff, true); invalidated |= object->invalidate_state_by_config_options(diff); } // handle changes to regions config defaults this->default_region_config.apply(config, true); // All regions now have distinct settings. // Check whether applying the new region config defaults we'd get different regions. bool rearrange_regions = false; { // Collect the already visited region configs into other_region_configs, // so one may check for duplicates. std::vector other_region_configs; for (size_t region_id = 0; region_id < this->regions.size(); ++ region_id) { PrintRegion ®ion = *this->regions[region_id]; PrintRegionConfig this_region_config; bool this_region_config_set = false; for (PrintObject *object : this->objects) { if (region_id < object->region_volumes.size()) { for (int volume_id : object->region_volumes[region_id]) { const ModelVolume &volume = *object->model_object()->volumes[volume_id]; if (this_region_config_set) { // If the new config for this volume differs from the other // volume configs currently associated to this region, it means // the region subdivision does not make sense anymore. if (! this_region_config.equals(this->_region_config_from_model_volume(volume))) { rearrange_regions = true; goto exit_for_rearrange_regions; } } else { this_region_config = this->_region_config_from_model_volume(volume); this_region_config_set = true; } for (const PrintRegionConfig &cfg : other_region_configs) { // If the new config for this volume equals any of the other // volume configs that are not currently associated to this // region, it means the region subdivision does not make // sense anymore. if (cfg.equals(this_region_config)) { rearrange_regions = true; goto exit_for_rearrange_regions; } } } } } if (this_region_config_set) { t_config_option_keys diff = region.config.diff(this_region_config); if (! diff.empty()) { region.config.apply_only(this_region_config, diff); for (PrintObject *object : this->objects) if (region_id < object->region_volumes.size() && ! object->region_volumes[region_id].empty()) invalidated |= object->invalidate_state_by_config_options(diff); } other_region_configs.emplace_back(std::move(this_region_config)); } } } exit_for_rearrange_regions: if (rearrange_regions) { // The current subdivision of regions does not make sense anymore. // We need to remove all objects and re-add them. ModelObjectPtrs model_objects; model_objects.reserve(this->objects.size()); for (PrintObject *object : this->objects) model_objects.push_back(object->model_object()); this->clear_objects(); for (ModelObject *mo : model_objects) this->add_model_object(mo); invalidated = true; } // Always make sure that the layer_height_profiles are set, as they should not be modified from the worker threads. for (PrintObject *object : this->objects) if (! object->layer_height_profile_valid) object->update_layer_height_profile(); return invalidated; } bool Print::has_infinite_skirt() const { return (this->config.skirt_height == -1 && this->config.skirts > 0) || (this->config.ooze_prevention && this->extruders().size() > 1); } bool Print::has_skirt() const { return (this->config.skirt_height > 0 && this->config.skirts > 0) || this->has_infinite_skirt(); } std::string Print::validate() const { if (this->config.complete_objects) { // Check horizontal clearance. { Polygons convex_hulls_other; for (PrintObject *object : this->objects) { // Get convex hull of all meshes assigned to this print object. Polygon convex_hull; { Polygons mesh_convex_hulls; for (const std::vector &volumes : object->region_volumes) for (int volume_id : volumes) mesh_convex_hulls.emplace_back(object->model_object()->volumes[volume_id]->mesh.convex_hull()); // make a single convex hull for all of them convex_hull = Slic3r::Geometry::convex_hull(mesh_convex_hulls); } // Apply the same transformations we apply to the actual meshes when slicing them. object->model_object()->instances.front()->transform_polygon(&convex_hull); // Grow convex hull with the clearance margin. convex_hull = offset(convex_hull, scale_(this->config.extruder_clearance_radius.value)/2, jtRound, scale_(0.1)).front(); // Now we check that no instance of convex_hull intersects any of the previously checked object instances. for (const Point © : object->_shifted_copies) { Polygon p = convex_hull; p.translate(copy); if (! intersection(convex_hulls_other, p).empty()) return "Some objects are too close; your extruder will collide with them."; polygons_append(convex_hulls_other, p); } } } // Check vertical clearance. { std::vector object_height; for (const PrintObject *object : this->objects) object_height.insert(object_height.end(), object->copies().size(), object->size.z); std::sort(object_height.begin(), object_height.end()); // Ignore the tallest *copy* (this is why we repeat height for all of them): // it will be printed as last one so its height doesn't matter. object_height.pop_back(); if (! object_height.empty() && object_height.back() > scale_(this->config.extruder_clearance_height.value)) return "Some objects are too tall and cannot be printed without extruder collisions."; } } // end if (this->config.complete_objects) if (this->config.spiral_vase) { size_t total_copies_count = 0; for (const PrintObject *object : this->objects) total_copies_count += object->copies().size(); // #4043 if (total_copies_count > 1 && ! this->config.complete_objects.value) return "The Spiral Vase option can only be used when printing a single object."; if (this->regions.size() > 1) return "The Spiral Vase option can only be used when printing single material objects."; } if (this->has_wipe_tower() && ! this->objects.empty()) { #if 0 for (auto dmr : this->config.nozzle_diameter.values) if (std::abs(dmr - 0.4) > EPSILON) return "The Wipe Tower is currently only supported for the 0.4mm nozzle diameter."; #endif if (this->config.gcode_flavor != gcfRepRap && this->config.gcode_flavor != gcfMarlin) return "The Wipe Tower is currently only supported for the Marlin and RepRap/Sprinter G-code flavors."; if (! this->config.use_relative_e_distances) return "The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."; SlicingParameters slicing_params0 = this->objects.front()->slicing_parameters(); for (PrintObject *object : this->objects) { SlicingParameters slicing_params = object->slicing_parameters(); if (std::abs(slicing_params.first_print_layer_height - slicing_params0.first_print_layer_height) > EPSILON || std::abs(slicing_params.layer_height - slicing_params0.layer_height ) > EPSILON) return "The Wipe Tower is only supported for multiple objects if they have equal layer heigths"; if (slicing_params.raft_layers() != slicing_params0.raft_layers()) return "The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers"; if (object->config.support_material_contact_distance != this->objects.front()->config.support_material_contact_distance) return "The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance"; if (! equal_layering(slicing_params, slicing_params0)) return "The Wipe Tower is only supported for multiple objects if they are sliced equally."; bool was_layer_height_profile_valid = object->layer_height_profile_valid; object->update_layer_height_profile(); object->layer_height_profile_valid = was_layer_height_profile_valid; for (size_t i = 5; i < object->layer_height_profile.size(); i += 2) if (object->layer_height_profile[i-1] > slicing_params.object_print_z_min + EPSILON && std::abs(object->layer_height_profile[i] - object->config.layer_height) > EPSILON) return "The Wipe Tower is currently only supported with constant Z layer spacing. Layer editing is not allowed."; } } { // find the smallest nozzle diameter std::vector extruders = this->extruders(); if (extruders.empty()) return "The supplied settings will cause an empty print."; std::vector nozzle_diameters; for (unsigned int extruder_id : extruders) nozzle_diameters.push_back(this->config.nozzle_diameter.get_at(extruder_id)); double min_nozzle_diameter = *std::min_element(nozzle_diameters.begin(), nozzle_diameters.end()); for (PrintObject *object : this->objects) { if ((object->config.support_material_extruder == -1 || object->config.support_material_interface_extruder == -1) && (object->config.raft_layers > 0 || object->config.support_material.value)) { // The object has some form of support and either support_material_extruder or support_material_interface_extruder // will be printed with the current tool without a forced tool change. Play safe, assert that all object nozzles // are of the same diameter. if (nozzle_diameters.size() > 1) return "Printing with multiple extruders of differing nozzle diameters. " "If support is to be printed with the current extruder (support_material_extruder == 0 or support_material_interface_extruder == 0), " "all nozzles have to be of the same diameter."; } // validate first_layer_height double first_layer_height = object->config.get_abs_value("first_layer_height"); double first_layer_min_nozzle_diameter; if (object->config.raft_layers > 0) { // if we have raft layers, only support material extruder is used on first layer size_t first_layer_extruder = object->config.raft_layers == 1 ? object->config.support_material_interface_extruder-1 : object->config.support_material_extruder-1; first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ? min_nozzle_diameter : this->config.nozzle_diameter.get_at(first_layer_extruder); } else { // if we don't have raft layers, any nozzle diameter is potentially used in first layer first_layer_min_nozzle_diameter = min_nozzle_diameter; } if (first_layer_height > first_layer_min_nozzle_diameter) return "First layer height can't be greater than nozzle diameter"; // validate layer_height if (object->config.layer_height.value > min_nozzle_diameter) return "Layer height can't be greater than nozzle diameter"; } } return std::string(); } // the bounding box of objects placed in copies position // (without taking skirt/brim/support material into account) BoundingBox Print::bounding_box() const { BoundingBox bb; for (const PrintObject *object : this->objects) for (Point copy : object->_shifted_copies) { bb.merge(copy); copy.translate(object->size); bb.merge(copy); } return bb; } // the total bounding box of extrusions, including skirt/brim/support material // this methods needs to be called even when no steps were processed, so it should // only use configuration values BoundingBox Print::total_bounding_box() const { // get objects bounding box BoundingBox bb = this->bounding_box(); // we need to offset the objects bounding box by at least half the perimeters extrusion width Flow perimeter_flow = this->objects.front()->get_layer(0)->get_region(0)->flow(frPerimeter); double extra = perimeter_flow.width/2; // consider support material if (this->has_support_material()) { extra = std::max(extra, SUPPORT_MATERIAL_MARGIN); } // consider brim and skirt if (this->config.brim_width.value > 0) { Flow brim_flow = this->brim_flow(); extra = std::max(extra, this->config.brim_width.value + brim_flow.width/2); } if (this->has_skirt()) { int skirts = this->config.skirts.value; if (skirts == 0 && this->has_infinite_skirt()) skirts = 1; Flow skirt_flow = this->skirt_flow(); extra = std::max( extra, this->config.brim_width.value + this->config.skirt_distance.value + skirts * skirt_flow.spacing() + skirt_flow.width/2 ); } if (extra > 0) bb.offset(scale_(extra)); return bb; } double Print::skirt_first_layer_height() const { if (this->objects.empty()) CONFESS("skirt_first_layer_height() can't be called without PrintObjects"); return this->objects.front()->config.get_abs_value("first_layer_height"); } Flow Print::brim_flow() const { ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; if (width.value == 0) width = this->objects.front()->config.extrusion_width; /* We currently use a random region's perimeter extruder. While this works for most cases, we should probably consider all of the perimeter extruders and take the one with, say, the smallest index. The same logic should be applied to the code that selects the extruder during G-code generation as well. */ return Flow::new_from_config_width( frPerimeter, width, this->config.nozzle_diameter.get_at(this->regions.front()->config.perimeter_extruder-1), this->skirt_first_layer_height(), 0 ); } Flow Print::skirt_flow() const { ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; if (width.value == 0) width = this->objects.front()->config.extrusion_width; /* We currently use a random object's support material extruder. While this works for most cases, we should probably consider all of the support material extruders and take the one with, say, the smallest index; The same logic should be applied to the code that selects the extruder during G-code generation as well. */ return Flow::new_from_config_width( frPerimeter, width, this->config.nozzle_diameter.get_at(this->objects.front()->config.support_material_extruder-1), this->skirt_first_layer_height(), 0 ); } PrintRegionConfig Print::_region_config_from_model_volume(const ModelVolume &volume) { PrintRegionConfig config = this->default_region_config; normalize_and_apply_config(config, volume.get_object()->config); normalize_and_apply_config(config, volume.config); if (! volume.material_id().empty()) normalize_and_apply_config(config, volume.material()->config); return config; } bool Print::has_support_material() const { for (const PrintObject *object : this->objects) if (object->has_support_material()) return true; return false; } /* This method assigns extruders to the volumes having a material but not having extruders set in the volume config. */ void Print::auto_assign_extruders(ModelObject* model_object) const { // only assign extruders if object has more than one volume if (model_object->volumes.size() < 2) return; // size_t extruders = this->config.nozzle_diameter.values.size(); for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) { ModelVolume *volume = model_object->volumes[volume_id]; //FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned. if (! volume->material_id().empty() && ! volume->config.has("extruder")) volume->config.opt("extruder", true)->value = int(volume_id + 1); } } void Print::_make_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. coordf_t skirt_height_z = 0.; for (const PrintObject *object : this->objects) { size_t skirt_layers = this->has_infinite_skirt() ? object->layer_count() : std::min(size_t(this->config.skirt_height.value), object->layer_count()); skirt_height_z = std::max(skirt_height_z, object->layers[skirt_layers-1]->print_z); } // Collect points from all layers contained in skirt height. Points points; for (const PrintObject *object : this->objects) { Points object_points; // Get object layers up to skirt_height_z. for (const Layer *layer : object->layers) { if (layer->print_z > skirt_height_z) break; for (const ExPolygon &expoly : layer->slices.expolygons) // Collect the outer contour points only, ignore holes for the calculation of the convex hull. append(object_points, expoly.contour.points); } // Get support layers up to skirt_height_z. for (const SupportLayer *layer : object->support_layers) { if (layer->print_z > skirt_height_z) break; for (const ExtrusionEntity *extrusion_entity : layer->support_fills.entities) append(object_points, extrusion_entity->as_polyline().points); } // Repeat points for each object copy. for (const Point &shift : object->_shifted_copies) { Points copy_points = object_points; for (Point &pt : copy_points) pt.translate(shift); append(points, copy_points); } } if (points.size() < 3) // At least three points required for a convex hull. return; Polygon convex_hull = Slic3r::Geometry::convex_hull(points); // 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 double first_layer_height = this->skirt_first_layer_height(); Flow flow = this->skirt_flow(); float spacing = flow.spacing(); double mm3_per_mm = flow.mm3_per_mm(); std::vector extruders; std::vector extruders_e_per_mm; { auto set_extruders = this->extruders(); extruders.reserve(set_extruders.size()); extruders_e_per_mm.reserve(set_extruders.size()); for (auto &extruder_id : set_extruders) { extruders.push_back(extruder_id); extruders_e_per_mm.push_back(Extruder((unsigned int)extruder_id, &this->config).e_per_mm(mm3_per_mm)); } } // Number of skirt loops per skirt layer. int n_skirts = this->config.skirts.value; if (this->has_infinite_skirt() && n_skirts == 0) n_skirts = 1; // Initial offset of the brim inner edge from the object (possible with a support & raft). // The skirt will touch the brim if the brim is extruded. coord_t distance = scale_(std::max(this->config.skirt_distance.value, this->config.brim_width.value)); // 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. std::vector extruded_length(extruders.size(), 0.); for (int i = n_skirts, extruder_idx = 0; i > 0; -- i) { // Offset the skirt outside. distance += coord_t(scale_(spacing)); // Generate the skirt centerline. Polygon loop; { Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, scale_(0.1)); Geometry::simplify_polygons(loops, scale_(0.05), &loops); loop = loops.front(); } // Extrude the skirt loop. ExtrusionLoop eloop(elrSkirt); eloop.paths.emplace_back(ExtrusionPath( ExtrusionPath( erSkirt, mm3_per_mm, // this will be overridden at G-code export time flow.width, first_layer_height // this will be overridden at G-code export time ))); eloop.paths.back().polyline = loop.split_at_first_point(); this->skirt.append(eloop); if (this->config.min_skirt_length.value > 0) { // The skirt length is limited. Sum the total amount of filament length extruded, in mm. extruded_length[extruder_idx] += unscale(loop.length()) * extruders_e_per_mm[extruder_idx]; if (extruded_length[extruder_idx] < this->config.min_skirt_length.value) { // Not extruded enough yet with the current extruder. Add another loop. if (i == 1) ++ i; } else { assert(extruded_length[extruder_idx] >= this->config.min_skirt_length.value); // Enough extruded with the current extruder. Extrude with the next one, // until the prescribed number of skirt loops is extruded. if (extruder_idx + 1 < extruders.size()) ++ extruder_idx; } } else { // The skirt lenght is not limited, extrude the skirt with the 1st extruder only. } } // Brims were generated inside out, reverse to print the outmost contour first. this->skirt.reverse(); } void Print::_make_brim() { // Brim is only printed on first layer and uses perimeter extruder. Flow flow = this->brim_flow(); Polygons islands; for (PrintObject *object : this->objects) { Polygons object_islands; for (ExPolygon &expoly : object->layers.front()->slices.expolygons) object_islands.push_back(expoly.contour); if (! object->support_layers.empty()) object->support_layers.front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON)); islands.reserve(islands.size() + object_islands.size() * object->_shifted_copies.size()); for (const Point &pt : object->_shifted_copies) for (Polygon &poly : object_islands) { islands.push_back(poly); islands.back().translate(pt); } } Polygons loops; size_t num_loops = size_t(floor(this->config.brim_width.value / flow.width)); for (size_t i = 0; i < num_loops; ++ i) { islands = offset(islands, float(flow.scaled_spacing()), jtSquare); for (Polygon &poly : islands) { // poly.simplify(SCALED_RESOLUTION); poly.points.push_back(poly.points.front()); Points p = MultiPoint::_douglas_peucker(poly.points, SCALED_RESOLUTION); p.pop_back(); poly.points = std::move(p); } polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); } loops = union_pt_chained(loops, false); std::reverse(loops.begin(), loops.end()); extrusion_entities_append_loops(this->brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())); } // Wipe tower support. bool Print::has_wipe_tower() const { return this->config.single_extruder_multi_material.value && ! this->config.spiral_vase.value && this->config.wipe_tower.value && this->config.nozzle_diameter.values.size() > 1; } void Print::_clear_wipe_tower() { m_tool_ordering.clear(); m_wipe_tower_priming.reset(nullptr); m_wipe_tower_tool_changes.clear(); m_wipe_tower_final_purge.reset(nullptr); } void Print::_make_wipe_tower() { this->_clear_wipe_tower(); if (! this->has_wipe_tower()) return; // Let the ToolOrdering class know there will be initial priming extrusions at the start of the print. m_tool_ordering = ToolOrdering(*this, (unsigned int)-1, true); if (! m_tool_ordering.has_wipe_tower()) // Don't generate any wipe tower. return; // Check whether there are any layers in m_tool_ordering, which are marked with has_wipe_tower, // they print neither object, nor support. These layers are above the raft and below the object, and they // shall be added to the support layers to be printed. // see https://github.com/prusa3d/Slic3r/issues/607 { size_t idx_begin = size_t(-1); size_t idx_end = m_tool_ordering.layer_tools().size(); // Find the first wipe tower layer, which does not have a counterpart in an object or a support layer. for (size_t i = 0; i < idx_end; ++ i) { const ToolOrdering::LayerTools < = m_tool_ordering.layer_tools()[i]; if (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support) { idx_begin = i; break; } } if (idx_begin != size_t(-1)) { // Find the position in this->objects.first()->support_layers to insert these new support layers. double wipe_tower_new_layer_print_z_first = m_tool_ordering.layer_tools()[idx_begin].print_z; SupportLayerPtrs::iterator it_layer = this->objects.front()->support_layers.begin(); SupportLayerPtrs::iterator it_end = this->objects.front()->support_layers.end(); for (; it_layer != it_end && (*it_layer)->print_z - EPSILON < wipe_tower_new_layer_print_z_first; ++ it_layer); // Find the stopper of the sequence of wipe tower layers, which do not have a counterpart in an object or a support layer. for (size_t i = idx_begin; i < idx_end; ++ i) { ToolOrdering::LayerTools < = const_cast(m_tool_ordering.layer_tools()[i]); if (! (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support)) break; lt.has_support = true; // Insert the new support layer. double height = lt.print_z - m_tool_ordering.layer_tools()[i-1].print_z; //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway. auto *new_layer = new SupportLayer(size_t(-1), this->objects.front(), height, lt.print_z, lt.print_z - 0.5 * height); it_layer = this->objects.front()->support_layers.insert(it_layer, new_layer); ++ it_layer; } } } // Initialize the wipe tower. WipeTowerPrusaMM wipe_tower( float(this->config.wipe_tower_x.value), float(this->config.wipe_tower_y.value), float(this->config.wipe_tower_width.value), float(this->config.wipe_tower_per_color_wipe.value), m_tool_ordering.first_extruder()); //wipe_tower.set_retract(); //wipe_tower.set_zhop(); // Set the extruder & material properties at the wipe tower object. for (size_t i = 0; i < 4; ++ i) wipe_tower.set_extruder( i, WipeTowerPrusaMM::parse_material(this->config.filament_type.get_at(i).c_str()), this->config.temperature.get_at(i), this->config.first_layer_temperature.get_at(i)); // When printing the first layer's wipe tower, the first extruder is expected to be active and primed. // Therefore the number of wipe sections at the wipe tower will be (m_tool_ordering.front().extruders-1) at the 1st layer. // The following variable is true if the last priming section cannot be squeezed inside the wipe tower. bool last_priming_wipe_full = m_tool_ordering.front().extruders.size() > m_tool_ordering.front().wipe_tower_partitions; m_wipe_tower_priming = Slic3r::make_unique( wipe_tower.prime(this->skirt_first_layer_height(), m_tool_ordering.all_extruders(), ! last_priming_wipe_full, WipeTower::PURPOSE_EXTRUDE)); // Generate the wipe tower layers. m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size()); // Set current_extruder_id to the last extruder primed. unsigned int current_extruder_id = m_tool_ordering.all_extruders().back(); for (const ToolOrdering::LayerTools &layer_tools : m_tool_ordering.layer_tools()) { if (! layer_tools.has_wipe_tower) // This is a support only layer, or the wipe tower does not reach to this height. continue; bool first_layer = &layer_tools == &m_tool_ordering.front(); bool last_layer = &layer_tools == &m_tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0; wipe_tower.set_layer( float(layer_tools.print_z), float(layer_tools.wipe_tower_layer_height), layer_tools.wipe_tower_partitions, first_layer, last_layer); std::vector tool_changes; for (unsigned int extruder_id : layer_tools.extruders) // Call the wipe_tower.tool_change() at the first layer for the initial extruder // to extrude the wipe tower brim, if ((first_layer && extruder_id == m_tool_ordering.all_extruders().back()) || // or when an extruder shall be switched. extruder_id != current_extruder_id) { tool_changes.emplace_back(wipe_tower.tool_change(extruder_id, extruder_id == layer_tools.extruders.back(), WipeTower::PURPOSE_EXTRUDE)); current_extruder_id = extruder_id; } if (! wipe_tower.layer_finished()) { tool_changes.emplace_back(wipe_tower.finish_layer(WipeTower::PURPOSE_EXTRUDE)); if (tool_changes.size() > 1) { // Merge the two last tool changes into one. WipeTower::ToolChangeResult &tc1 = tool_changes[tool_changes.size() - 2]; WipeTower::ToolChangeResult &tc2 = tool_changes.back(); if (tc1.end_pos != tc2.start_pos) { // Add a travel move from tc1.end_pos to tc2.start_pos. char buf[2048]; sprintf(buf, "G1 X%.3f Y%.3f F7200\n", tc2.start_pos.x, tc2.start_pos.y); tc1.gcode += buf; } tc1.gcode += tc2.gcode; append(tc1.extrusions, tc2.extrusions); tc1.end_pos = tc2.end_pos; tool_changes.pop_back(); } } m_wipe_tower_tool_changes.emplace_back(std::move(tool_changes)); if (last_layer) break; } // Unload the current filament over the purge tower. coordf_t layer_height = this->objects.front()->config.layer_height.value; if (m_tool_ordering.back().wipe_tower_partitions > 0) { // The wipe tower goes up to the last layer of the print. if (wipe_tower.layer_finished()) { // The wipe tower is printed to the top of the print and it has no space left for the final extruder purge. // Lift Z to the next layer. wipe_tower.set_layer(float(m_tool_ordering.back().print_z + layer_height), float(layer_height), 0, false, true); } else { // There is yet enough space at this layer of the wipe tower for the final purge. } } else { // The wipe tower does not reach the last print layer, perform the pruge at the last print layer. assert(m_tool_ordering.back().wipe_tower_partitions == 0); wipe_tower.set_layer(float(m_tool_ordering.back().print_z), float(layer_height), 0, false, true); } m_wipe_tower_final_purge = Slic3r::make_unique( wipe_tower.tool_change((unsigned int)-1, false, WipeTower::PURPOSE_EXTRUDE)); } std::string Print::output_filename() { this->placeholder_parser.update_timestamp(); try { return this->placeholder_parser.process(this->config.output_filename_format.value, 0); } catch (std::runtime_error &err) { throw std::runtime_error(std::string("Failed processing of the output_filename_format template.\n") + err.what()); } } std::string Print::output_filepath(const std::string &path) { // if we were supplied no path, generate an automatic one based on our first object's input file if (path.empty()) { // get the first input file name std::string input_file; for (const PrintObject *object : this->objects) { input_file = object->model_object()->input_file; if (! input_file.empty()) break; } return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).make_preferred().string(); } // if we were supplied a directory, use it and append our automatically generated filename boost::filesystem::path p(path); if (boost::filesystem::is_directory(p)) return (p / this->output_filename()).make_preferred().string(); // if we were supplied a file which is not a directory, use it return path; } void Print::set_status(int percent, const std::string &message) { printf("Print::status %d => %s\n", percent, message.c_str()); } } Slic3r-version_1.39.1/xs/src/libslic3r/Print.hpp000066400000000000000000000313341324354444700214430ustar00rootroot00000000000000#ifndef slic3r_Print_hpp_ #define slic3r_Print_hpp_ #include "libslic3r.h" #include #include #include #include "BoundingBox.hpp" #include "Flow.hpp" #include "PrintConfig.hpp" #include "Point.hpp" #include "Layer.hpp" #include "Model.hpp" #include "PlaceholderParser.hpp" #include "Slicing.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" #include "tbb/atomic.h" namespace Slic3r { class Print; class PrintObject; class ModelObject; // Print step IDs for keeping track of the print state. enum PrintStep { psSkirt, psBrim, psWipeTower, psCount, }; enum PrintObjectStep { posSlice, posPerimeters, posPrepareInfill, posInfill, posSupportMaterial, posCount, }; // To be instantiated over PrintStep or PrintObjectStep enums. template class PrintState { public: PrintState() { memset(state, 0, sizeof(state)); } enum State { INVALID, STARTED, DONE, }; State state[COUNT]; bool is_started(StepType step) const { return this->state[step] == STARTED; } bool is_done(StepType step) const { return this->state[step] == DONE; } void set_started(StepType step) { this->state[step] = STARTED; } void set_done(StepType step) { this->state[step] = DONE; } bool invalidate(StepType step) { bool invalidated = this->state[step] != INVALID; this->state[step] = INVALID; return invalidated; } bool invalidate_all() { bool invalidated = false; for (size_t i = 0; i < COUNT; ++ i) if (this->state[i] != INVALID) { invalidated = true; break; } memset(state, 0, sizeof(state)); return invalidated; } }; // A PrintRegion object represents a group of volumes to print // sharing the same config (including the same assigned extruder(s)) class PrintRegion { friend class Print; public: PrintRegionConfig config; Print* print() { return this->_print; } Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const; coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const; private: Print* _print; PrintRegion(Print* print) : _print(print) {} ~PrintRegion() {} }; typedef std::vector LayerPtrs; typedef std::vector SupportLayerPtrs; class BoundingBoxf3; // TODO: for temporary constructor parameter class PrintObject { friend class Print; public: // vector of (vectors of volume ids), indexed by region_id std::vector> region_volumes; PrintObjectConfig config; t_layer_height_ranges layer_height_ranges; // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. // The pairs of are packed into a 1D array to simplify handling by the Perl XS. // layer_height_profile must not be set by the background thread. std::vector layer_height_profile; // There is a layer_height_profile at both PrintObject and ModelObject. The layer_height_profile at the ModelObject // is used for interactive editing and for loading / storing into a project file (AMF file as of today). // This flag indicates that the layer_height_profile at the UI has been updated, therefore the backend needs to get it. // This flag is necessary as we cannot safely clear the layer_height_profile if the background calculation is running. bool layer_height_profile_valid; // this is set to true when LayerRegion->slices is split in top/internal/bottom // so that next call to make_perimeters() performs a union() before computing loops bool typed_slices; Point3 size; // XYZ in scaled coordinates // scaled coordinates to add to copies (to compensate for the alignment // operated when creating the object but still preserving a coherent API // for external callers) Point _copies_shift; // Slic3r::Point objects in scaled G-code coordinates in our coordinates Points _shifted_copies; LayerPtrs layers; SupportLayerPtrs support_layers; PrintState state; Print* print() { return this->_print; } const Print* print() const { return this->_print; } ModelObject* model_object() { return this->_model_object; } const ModelObject* model_object() const { return this->_model_object; } const Points& copies() const { return this->_copies; } bool add_copy(const Pointf &point); bool delete_last_copy(); bool delete_all_copies() { return this->set_copies(Points()); } bool set_copies(const Points &points); bool reload_model_instances(); // since the object is aligned to origin, bounding box coincides with size BoundingBox bounding_box() const { return BoundingBox(Point(0,0), this->size); } // adds region_id, too, if necessary void add_region_volume(unsigned int region_id, int volume_id) { if (region_id >= region_volumes.size()) region_volumes.resize(region_id + 1); region_volumes[region_id].push_back(volume_id); } // This is the *total* layer count (including support layers) // this value is not supposed to be compared with Layer::id // since they have different semantics. size_t total_layer_count() const { return this->layer_count() + this->support_layer_count(); } size_t layer_count() const { return this->layers.size(); } void clear_layers(); Layer* get_layer(int idx) { return this->layers.at(idx); } const Layer* get_layer(int idx) const { return this->layers.at(idx); } // print_z: top of the layer; slice_z: center of the layer. Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z); size_t support_layer_count() const { return this->support_layers.size(); } void clear_support_layers(); SupportLayer* get_support_layer(int idx) { return this->support_layers.at(idx); } SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z); void delete_support_layer(int idx); // methods for handling state bool invalidate_state_by_config_options(const std::vector &opt_keys); bool invalidate_step(PrintObjectStep step); bool invalidate_all_steps() { return this->state.invalidate_all(); } // To be used over the layer_height_profile of both the PrintObject and ModelObject // to initialize the height profile with the height ranges. bool update_layer_height_profile(std::vector &layer_height_profile) const; // Process layer_height_ranges, the raft layers and first layer thickness into layer_height_profile. // The layer_height_profile may be later modified interactively by the user to refine layers at sloping surfaces. bool update_layer_height_profile(); void reset_layer_height_profile(); // Collect the slicing parameters, to be used by variable layer thickness algorithm, // by the interactive layer height editor and by the printing process itself. // The slicing parameters are dependent on various configuration values // (layer height, first layer height, raft settings, print nozzle diameter etc). SlicingParameters slicing_parameters() const; void _slice(); std::string _fix_slicing_errors(); void _simplify_slices(double distance); void _prepare_infill(); bool has_support_material() const; void detect_surfaces_type(); void process_external_surfaces(); void discover_vertical_shells(); void bridge_over_infill(); void _make_perimeters(); void _infill(); void clip_fill_surfaces(); void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); private: Print* _print; ModelObject* _model_object; Points _copies; // Slic3r::Point objects in scaled G-code coordinates // TODO: call model_object->get_bounding_box() instead of accepting // parameter PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox); ~PrintObject() {} std::vector _slice_region(size_t region_id, const std::vector &z, bool modifier); }; typedef std::vector PrintObjectPtrs; typedef std::vector PrintRegionPtrs; // The complete print tray with possibly multiple objects. class Print { public: PrintConfig config; PrintObjectConfig default_object_config; PrintRegionConfig default_region_config; PrintObjectPtrs objects; PrintRegionPtrs regions; PlaceholderParser placeholder_parser; // TODO: status_cb std::string estimated_print_time; double total_used_filament, total_extruded_volume, total_cost, total_weight; std::map filament_stats; PrintState state; // ordered collections of extrusion paths to build skirt loops and brim ExtrusionEntityCollection skirt, brim; Print() : total_used_filament(0), total_extruded_volume(0) { restart(); } ~Print() { clear_objects(); } // methods for handling objects void clear_objects(); PrintObject* get_object(size_t idx) { return objects.at(idx); } const PrintObject* get_object(size_t idx) const { return objects.at(idx); } void delete_object(size_t idx); void reload_object(size_t idx); bool reload_model_instances(); // methods for handling regions PrintRegion* get_region(size_t idx) { return regions.at(idx); } const PrintRegion* get_region(size_t idx) const { return regions.at(idx); } PrintRegion* add_region(); // methods for handling state bool invalidate_step(PrintStep step); bool invalidate_all_steps() { return this->state.invalidate_all(); } bool step_done(PrintObjectStep step) const; void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig config); bool has_infinite_skirt() const; bool has_skirt() const; // Returns an empty string if valid, otherwise returns an error message. std::string validate() const; BoundingBox bounding_box() const; BoundingBox total_bounding_box() const; double skirt_first_layer_height() const; Flow brim_flow() const; Flow skirt_flow() const; std::vector object_extruders() const; std::vector support_material_extruders() const; std::vector extruders() const; void _simplify_slices(double distance); double max_allowed_layer_height() const; bool has_support_material() const; void auto_assign_extruders(ModelObject* model_object) const; void _make_skirt(); void _make_brim(); // Wipe tower support. bool has_wipe_tower() const; void _clear_wipe_tower(); void _make_wipe_tower(); // Tool ordering of a non-sequential print has to be known to calculate the wipe tower. // Cache it here, so it does not need to be recalculated during the G-code generation. ToolOrdering m_tool_ordering; // Cache of tool changes per print layer. std::unique_ptr m_wipe_tower_priming; std::vector> m_wipe_tower_tool_changes; std::unique_ptr m_wipe_tower_final_purge; std::string output_filename(); std::string output_filepath(const std::string &path); // Calls a registered callback to update the status. void set_status(int percent, const std::string &message); // Cancel the running computation. Stop execution of all the background threads. void cancel() { m_canceled = true; } // Cancel the running computation. Stop execution of all the background threads. void restart() { m_canceled = false; } // Has the calculation been canceled? bool canceled() { return m_canceled; } private: bool invalidate_state_by_config_options(const std::vector &opt_keys); PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume); // Has the calculation been canceled? tbb::atomic m_canceled; }; #define FOREACH_BASE(type, container, iterator) for (type::const_iterator iterator = (container).begin(); iterator != (container).end(); ++iterator) #define FOREACH_REGION(print, region) FOREACH_BASE(PrintRegionPtrs, (print)->regions, region) #define FOREACH_OBJECT(print, object) FOREACH_BASE(PrintObjectPtrs, (print)->objects, object) #define FOREACH_LAYER(object, layer) FOREACH_BASE(LayerPtrs, (object)->layers, layer) #define FOREACH_LAYERREGION(layer, layerm) FOREACH_BASE(LayerRegionPtrs, (layer)->regions, layerm) } #endif Slic3r-version_1.39.1/xs/src/libslic3r/PrintConfig.cpp000066400000000000000000002773361324354444700226020ustar00rootroot00000000000000#include "PrintConfig.hpp" #include #include #include #include #include namespace Slic3r { PrintConfigDef::PrintConfigDef() { t_optiondef_map &Options = this->options; ConfigOptionDef* def; // Maximum extruder temperature, bumped to 1500 to support printing of glass. const int max_temp = 1500; def = this->add("avoid_crossing_perimeters", coBool); def->label = "Avoid crossing perimeters"; def->tooltip = "Optimize travel moves in order to minimize the crossing of perimeters. " "This is mostly useful with Bowden extruders which suffer from oozing. " "This feature slows down both the print and the G-code generation."; def->cli = "avoid-crossing-perimeters!"; def->default_value = new ConfigOptionBool(false); def = this->add("bed_shape", coPoints); def->label = "Bed shape"; def->default_value = new ConfigOptionPoints { Pointf(0,0), Pointf(200,0), Pointf(200,200), Pointf(0,200) }; def = this->add("bed_temperature", coInts); def->label = "Other layers"; def->tooltip = "Bed temperature for layers after the first one. " "Set this to zero to disable bed temperature control commands in the output."; def->cli = "bed-temperature=i@"; def->full_label = "Bed temperature"; def->min = 0; def->max = 300; def->default_value = new ConfigOptionInts { 0 }; def = this->add("before_layer_gcode", coString); def->label = "Before layer change G-code"; def->tooltip = "This custom code is inserted at every layer change, right before the Z move. " "Note that you can use placeholder variables for all Slic3r settings as well " "as [layer_num] and [layer_z]."; def->cli = "before-layer-gcode=s"; def->multiline = true; def->full_width = true; def->height = 50; def->default_value = new ConfigOptionString(""); def = this->add("between_objects_gcode", coString); def->label = "Between objects G-code"; def->tooltip = "This code is inserted between objects when using sequential printing. By default extruder and bed temperature are reset using non-wait command; however if M104, M109, M140 or M190 are detected in this custom code, Slic3r will not add temperature commands. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want."; def->cli = "between-objects-gcode=s"; def->multiline = true; def->full_width = true; def->height = 120; def->default_value = new ConfigOptionString(""); def = this->add("bottom_solid_layers", coInt); def->label = "Bottom"; def->category = "Layers and Perimeters"; def->tooltip = "Number of solid layers to generate on bottom surfaces."; def->cli = "bottom-solid-layers=i"; def->full_label = "Bottom solid layers"; def->min = 0; def->default_value = new ConfigOptionInt(3); def = this->add("bridge_acceleration", coFloat); def->label = "Bridge"; def->tooltip = "This is the acceleration your printer will use for bridges. " "Set zero to disable acceleration control for bridges."; def->sidetext = "mm/s²"; def->cli = "bridge-acceleration=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); def = this->add("bridge_angle", coFloat); def->label = "Bridging angle"; def->category = "Infill"; def->tooltip = "Bridging angle override. If left to zero, the bridging angle will be calculated " "automatically. Otherwise the provided angle will be used for all bridges. " "Use 180° for zero angle."; def->sidetext = "°"; def->cli = "bridge-angle=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0.); def = this->add("bridge_fan_speed", coInts); def->label = "Bridges fan speed"; def->tooltip = "This fan speed is enforced during all bridges and overhangs."; def->sidetext = "%"; def->cli = "bridge-fan-speed=i@"; def->min = 0; def->max = 100; def->default_value = new ConfigOptionInts { 100 }; def = this->add("bridge_flow_ratio", coFloat); def->label = "Bridge flow ratio"; def->category = "Advanced"; def->tooltip = "This factor affects the amount of plastic for bridging. " "You can decrease it slightly to pull the extrudates and prevent sagging, " "although default settings are usually good and you should experiment " "with cooling (use a fan) before tweaking this."; def->cli = "bridge-flow-ratio=f"; def->min = 0; def->default_value = new ConfigOptionFloat(1); def = this->add("bridge_speed", coFloat); def->label = "Bridges"; def->category = "Speed"; def->tooltip = "Speed for printing bridges."; def->sidetext = "mm/s"; def->cli = "bridge-speed=f"; def->aliases.push_back("bridge_feed_rate"); def->min = 0; def->default_value = new ConfigOptionFloat(60); def = this->add("brim_width", coFloat); def->label = "Brim width"; def->tooltip = "Horizontal width of the brim that will be printed around each object on the first layer."; def->sidetext = "mm"; def->cli = "brim-width=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); def = this->add("clip_multipart_objects", coBool); def->label = "Clip multi-part objects"; def->tooltip = "When printing multi-material objects, this settings will make slic3r " "to clip the overlapping object parts one by the other " "(2nd part will be clipped by the 1st, 3rd part will be clipped by the 1st and 2nd etc)."; def->cli = "clip-multipart-objects!"; def->default_value = new ConfigOptionBool(false); def = this->add("compatible_printers", coStrings); def->label = "Compatible printers"; def->default_value = new ConfigOptionStrings(); def = this->add("compatible_printers_condition", coString); def->label = "Compatible printers condition"; def->tooltip = "A boolean expression using the configuration values of an active printer profile. " "If this expression evaluates to true, this profile is considered compatible " "with the active printer profile."; def->default_value = new ConfigOptionString(); def = this->add("complete_objects", coBool); def->label = "Complete individual objects"; def->tooltip = "When printing multiple objects or copies, this feature will complete " "each object before moving onto next one (and starting it from its bottom layer). " "This feature is useful to avoid the risk of ruined prints. " "Slic3r should warn and prevent you from extruder collisions, but beware."; def->cli = "complete-objects!"; def->default_value = new ConfigOptionBool(false); def = this->add("cooling", coBools); def->label = "Enable auto cooling"; def->tooltip = "This flag enables the automatic cooling logic that adjusts print speed " "and fan speed according to layer printing time."; def->cli = "cooling!"; def->default_value = new ConfigOptionBools { true }; def = this->add("default_acceleration", coFloat); def->label = "Default"; def->tooltip = "This is the acceleration your printer will be reset to after " "the role-specific acceleration values are used (perimeter/infill). " "Set zero to prevent resetting acceleration at all."; def->sidetext = "mm/s²"; def->cli = "default-acceleration=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); def = this->add("disable_fan_first_layers", coInts); def->label = "Disable fan for the first"; def->tooltip = "You can set this to a positive value to disable fan at all " "during the first layers, so that it does not make adhesion worse."; def->sidetext = "layers"; def->cli = "disable-fan-first-layers=i@"; def->min = 0; def->max = 1000; def->default_value = new ConfigOptionInts { 3 }; def = this->add("dont_support_bridges", coBool); def->label = "Don't support bridges"; def->category = "Support material"; def->tooltip = "Experimental option for preventing support material from being generated " "under bridged areas."; def->cli = "dont-support-bridges!"; def->default_value = new ConfigOptionBool(true); def = this->add("duplicate_distance", coFloat); def->label = "Distance between copies"; def->tooltip = "Distance used for the auto-arrange feature of the plater."; def->sidetext = "mm"; def->cli = "duplicate-distance=f"; def->aliases.push_back("multiply_distance"); def->min = 0; def->default_value = new ConfigOptionFloat(6); def = this->add("elefant_foot_compensation", coFloat); def->label = "Elephant foot compensation"; def->category = "Advanced"; def->tooltip = "The first layer will be shrunk in the XY plane by the configured value " "to compensate for the 1st layer squish aka an Elephant Foot effect."; def->sidetext = "mm"; def->cli = "elefant-foot-compensation=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); def = this->add("end_gcode", coString); def->label = "End G-code"; def->tooltip = "This end procedure is inserted at the end of the output file. " "Note that you can use placeholder variables for all Slic3r settings."; def->cli = "end-gcode=s"; def->multiline = true; def->full_width = true; def->height = 120; def->default_value = new ConfigOptionString("M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n"); def = this->add("end_filament_gcode", coStrings); def->label = "End G-code"; def->tooltip = "This end procedure is inserted at the end of the output file, before the printer end gcode. " "Note that you can use placeholder variables for all Slic3r settings. " "If you have multiple extruders, the gcode is processed in extruder order."; def->cli = "end-filament-gcode=s@"; def->multiline = true; def->full_width = true; def->height = 120; def->default_value = new ConfigOptionStrings { "; Filament-specific end gcode \n;END gcode for filament\n" }; def = this->add("ensure_vertical_shell_thickness", coBool); def->label = "Ensure vertical shell thickness"; def->category = "Layers and Perimeters"; def->tooltip = "Add solid infill near sloping surfaces to guarantee the vertical shell thickness " "(top+bottom solid layers)."; def->cli = "ensure-vertical-shell-thickness!"; def->default_value = new ConfigOptionBool(false); def = this->add("external_fill_pattern", coEnum); def->label = "Top/bottom fill pattern"; def->category = "Infill"; def->tooltip = "Fill pattern for top/bottom infill. This only affects the external visible layer, " "and not its adjacent solid shells."; def->cli = "external-fill-pattern|solid-fill-pattern=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("rectilinear"); def->enum_values.push_back("concentric"); def->enum_values.push_back("hilbertcurve"); def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); def->enum_labels.push_back("Rectilinear"); def->enum_labels.push_back("Concentric"); def->enum_labels.push_back("Hilbert Curve"); def->enum_labels.push_back("Archimedean Chords"); def->enum_labels.push_back("Octagram Spiral"); // solid_fill_pattern is an obsolete equivalent to external_fill_pattern. def->aliases.push_back("solid_fill_pattern"); def->default_value = new ConfigOptionEnum(ipRectilinear); def = this->add("external_perimeter_extrusion_width", coFloatOrPercent); def->label = "External perimeters"; def->category = "Extrusion Width"; def->tooltip = "Set this to a non-zero value to set a manual extrusion width for external perimeters. " "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " "If expressed as percentage (for example 200%), it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "external-perimeter-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("external_perimeter_speed", coFloatOrPercent); def->label = "External perimeters"; def->category = "Speed"; def->tooltip = "This separate setting will affect the speed of external perimeters (the visible ones). " "If expressed as percentage (for example: 80%) it will be calculated " "on the perimeters speed setting above. Set to zero for auto."; def->sidetext = "mm/s or %"; def->cli = "external-perimeter-speed=s"; def->ratio_over = "perimeter_speed"; def->min = 0; def->default_value = new ConfigOptionFloatOrPercent(50, true); def = this->add("external_perimeters_first", coBool); def->label = "External perimeters first"; def->category = "Layers and Perimeters"; def->tooltip = "Print contour perimeters from the outermost one to the innermost one " "instead of the default inverse order."; def->cli = "external-perimeters-first!"; def->default_value = new ConfigOptionBool(false); def = this->add("extra_perimeters", coBool); def->label = "Extra perimeters if needed"; def->category = "Layers and Perimeters"; def->tooltip = "Add more perimeters when needed for avoiding gaps in sloping walls. " "Slic3r keeps adding perimeters, until more than 70% of the loop immediately above " "is supported."; def->cli = "extra-perimeters!"; def->default_value = new ConfigOptionBool(true); def = this->add("extruder", coInt); def->gui_type = "i_enum_open"; def->label = "Extruder"; def->category = "Extruders"; def->tooltip = "The extruder to use (unless more specific extruder settings are specified). " "This value overrides perimeter and infill extruders, but not the support extruders."; def->cli = "extruder=i"; def->min = 0; // 0 = inherit defaults def->enum_labels.push_back("default"); // override label for item 0 def->enum_labels.push_back("1"); def->enum_labels.push_back("2"); def->enum_labels.push_back("3"); def->enum_labels.push_back("4"); def = this->add("extruder_clearance_height", coFloat); def->label = "Height"; def->tooltip = "Set this to the vertical distance between your nozzle tip and (usually) the X carriage rods. " "In other words, this is the height of the clearance cylinder around your extruder, " "and it represents the maximum depth the extruder can peek before colliding with " "other printed objects."; def->sidetext = "mm"; def->cli = "extruder-clearance-height=f"; def->min = 0; def->default_value = new ConfigOptionFloat(20); def = this->add("extruder_clearance_radius", coFloat); def->label = "Radius"; def->tooltip = "Set this to the clearance radius around your extruder. " "If the extruder is not centered, choose the largest value for safety. " "This setting is used to check for collisions and to display the graphical preview " "in the plater."; def->sidetext = "mm"; def->cli = "extruder-clearance-radius=f"; def->min = 0; def->default_value = new ConfigOptionFloat(20); def = this->add("extruder_colour", coStrings); def->label = "Extruder Color"; def->tooltip = "This is only used in the Slic3r interface as a visual help."; def->cli = "extruder-color=s@"; def->gui_type = "color"; // Empty string means no color assigned yet. def->default_value = new ConfigOptionStrings { "" }; def = this->add("extruder_offset", coPoints); def->label = "Extruder offset"; def->tooltip = "If your firmware doesn't handle the extruder displacement you need the G-code " "to take it into account. This option lets you specify the displacement of each extruder " "with respect to the first one. It expects positive coordinates (they will be subtracted " "from the XY coordinate)."; def->sidetext = "mm"; def->cli = "extruder-offset=s@"; def->default_value = new ConfigOptionPoints { Pointf(0,0) }; def = this->add("extrusion_axis", coString); def->label = "Extrusion axis"; def->tooltip = "Use this option to set the axis letter associated to your printer's extruder " "(usually E but some printers use A)."; def->cli = "extrusion-axis=s"; def->default_value = new ConfigOptionString("E"); def = this->add("extrusion_multiplier", coFloats); def->label = "Extrusion multiplier"; def->tooltip = "This factor changes the amount of flow proportionally. You may need to tweak " "this setting to get nice surface finish and correct single wall widths. " "Usual values are between 0.9 and 1.1. If you think you need to change this more, " "check filament diameter and your firmware E steps."; def->cli = "extrusion-multiplier=f@"; def->default_value = new ConfigOptionFloats { 1. }; def = this->add("extrusion_width", coFloatOrPercent); def->label = "Default extrusion width"; def->category = "Extrusion Width"; def->tooltip = "Set this to a non-zero value to allow a manual extrusion width. " "If left to zero, Slic3r derives extrusion widths from the nozzle diameter " "(see the tooltips for perimeter extrusion width, infill extrusion width etc). " "If expressed as percentage (for example: 230%), it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for auto)"; def->cli = "extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("fan_always_on", coBools); def->label = "Keep fan always on"; def->tooltip = "If this is enabled, fan will never be disabled and will be kept running at least " "at its minimum speed. Useful for PLA, harmful for ABS."; def->cli = "fan-always-on!"; def->default_value = new ConfigOptionBools { false }; def = this->add("fan_below_layer_time", coInts); def->label = "Enable fan if layer print time is below"; def->tooltip = "If layer print time is estimated below this number of seconds, fan will be enabled " "and its speed will be calculated by interpolating the minimum and maximum speeds."; def->sidetext = "approximate seconds"; def->cli = "fan-below-layer-time=i@"; def->width = 60; def->min = 0; def->max = 1000; def->default_value = new ConfigOptionInts { 60 }; def = this->add("filament_colour", coStrings); def->label = "Color"; def->tooltip = "This is only used in the Slic3r interface as a visual help."; def->cli = "filament-color=s@"; def->gui_type = "color"; def->default_value = new ConfigOptionStrings { "#29b2b2" }; def = this->add("filament_notes", coStrings); def->label = "Filament notes"; def->tooltip = "You can put your notes regarding the filament here."; def->cli = "filament-notes=s@"; def->multiline = true; def->full_width = true; def->height = 130; def->default_value = new ConfigOptionStrings { "" }; def = this->add("filament_max_volumetric_speed", coFloats); def->label = "Max volumetric speed"; def->tooltip = "Maximum volumetric speed allowed for this filament. Limits the maximum volumetric " "speed of a print to the minimum of print and filament volumetric speed. " "Set to zero for no limit."; def->sidetext = "mm³/s"; def->cli = "filament-max-volumetric-speed=f@"; def->min = 0; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("filament_diameter", coFloats); def->label = "Diameter"; def->tooltip = "Enter your filament diameter here. Good precision is required, so use a caliper " "and do multiple measurements along the filament, then compute the average."; def->sidetext = "mm"; def->cli = "filament-diameter=f@"; def->min = 0; def->default_value = new ConfigOptionFloats { 3. }; def = this->add("filament_density", coFloats); def->label = "Density"; def->tooltip = "Enter your filament density here. This is only for statistical information. " "A decent way is to weigh a known length of filament and compute the ratio " "of the length to volume. Better is to calculate the volume directly through displacement."; def->sidetext = "g/cm^3"; def->cli = "filament-density=f@"; def->min = 0; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("filament_type", coStrings); def->label = "Filament type"; def->tooltip = "If you want to process the output G-code through custom scripts, just list their " "absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed " "the absolute path to the G-code file as the first argument, and they can access " "the Slic3r config settings by reading environment variables."; def->cli = "filament_type=s@"; def->gui_type = "f_enum_open"; def->gui_flags = "show_value"; def->enum_values.push_back("PLA"); def->enum_values.push_back("ABS"); def->enum_values.push_back("PET"); def->enum_values.push_back("HIPS"); def->enum_values.push_back("FLEX"); def->enum_values.push_back("SCAFF"); def->enum_values.push_back("EDGE"); def->enum_values.push_back("NGEN"); def->enum_values.push_back("PVA"); def->default_value = new ConfigOptionStrings { "PLA" }; def = this->add("filament_soluble", coBools); def->label = "Soluble material"; def->tooltip = "Soluble material is most likely used for a soluble support."; def->cli = "filament-soluble!"; def->default_value = new ConfigOptionBools { false }; def = this->add("filament_cost", coFloats); def->label = "Cost"; def->tooltip = "Enter your filament cost per kg here. This is only for statistical information."; def->sidetext = "money/kg"; def->cli = "filament-cost=f@"; def->min = 0; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("filament_settings_id", coStrings); def->default_value = new ConfigOptionStrings { "" }; def = this->add("fill_angle", coFloat); def->label = "Fill angle"; def->category = "Infill"; def->tooltip = "Default base angle for infill orientation. Cross-hatching will be applied to this. " "Bridges will be infilled using the best direction Slic3r can detect, so this setting " "does not affect them."; def->sidetext = "°"; def->cli = "fill-angle=f"; def->min = 0; def->max = 360; def->default_value = new ConfigOptionFloat(45); def = this->add("fill_density", coPercent); def->gui_type = "f_enum_open"; def->gui_flags = "show_value"; def->label = "Fill density"; def->category = "Infill"; def->tooltip = "Density of internal infill, expressed in the range 0% - 100%."; def->sidetext = "%"; def->cli = "fill-density=s"; def->min = 0; def->max = 100; def->enum_values.push_back("0"); def->enum_values.push_back("5"); def->enum_values.push_back("10"); def->enum_values.push_back("15"); def->enum_values.push_back("20"); def->enum_values.push_back("25"); def->enum_values.push_back("30"); def->enum_values.push_back("40"); def->enum_values.push_back("50"); def->enum_values.push_back("60"); def->enum_values.push_back("70"); def->enum_values.push_back("80"); def->enum_values.push_back("90"); def->enum_values.push_back("100"); def->enum_labels.push_back("0%"); def->enum_labels.push_back("5%"); def->enum_labels.push_back("10%"); def->enum_labels.push_back("15%"); def->enum_labels.push_back("20%"); def->enum_labels.push_back("25%"); def->enum_labels.push_back("30%"); def->enum_labels.push_back("40%"); def->enum_labels.push_back("50%"); def->enum_labels.push_back("60%"); def->enum_labels.push_back("70%"); def->enum_labels.push_back("80%"); def->enum_labels.push_back("90%"); def->enum_labels.push_back("100%"); def->default_value = new ConfigOptionPercent(20); def = this->add("fill_pattern", coEnum); def->label = "Fill pattern"; def->category = "Infill"; def->tooltip = "Fill pattern for general low-density infill."; def->cli = "fill-pattern=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("rectilinear"); def->enum_values.push_back("grid"); def->enum_values.push_back("triangles"); def->enum_values.push_back("stars"); def->enum_values.push_back("cubic"); def->enum_values.push_back("line"); def->enum_values.push_back("concentric"); def->enum_values.push_back("honeycomb"); def->enum_values.push_back("3dhoneycomb"); def->enum_values.push_back("hilbertcurve"); def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); def->enum_labels.push_back("Rectilinear"); def->enum_labels.push_back("Grid"); def->enum_labels.push_back("Triangles"); def->enum_labels.push_back("Stars"); def->enum_labels.push_back("Cubic"); def->enum_labels.push_back("Line"); def->enum_labels.push_back("Concentric"); def->enum_labels.push_back("Honeycomb"); def->enum_labels.push_back("3D Honeycomb"); def->enum_labels.push_back("Hilbert Curve"); def->enum_labels.push_back("Archimedean Chords"); def->enum_labels.push_back("Octagram Spiral"); def->default_value = new ConfigOptionEnum(ipStars); def = this->add("first_layer_acceleration", coFloat); def->label = "First layer"; def->tooltip = "This is the acceleration your printer will use for first layer. Set zero " "to disable acceleration control for first layer."; def->sidetext = "mm/s²"; def->cli = "first-layer-acceleration=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); def = this->add("first_layer_bed_temperature", coInts); def->label = "First layer"; def->tooltip = "Heated build plate temperature for the first layer. Set this to zero to disable " "bed temperature control commands in the output."; def->cli = "first-layer-bed-temperature=i@"; def->max = 0; def->max = 300; def->default_value = new ConfigOptionInts { 0 }; def = this->add("first_layer_extrusion_width", coFloatOrPercent); def->label = "First layer"; def->category = "Extrusion Width"; def->tooltip = "Set this to a non-zero value to set a manual extrusion width for first layer. " "You can use this to force fatter extrudates for better adhesion. If expressed " "as percentage (for example 120%) it will be computed over first layer height. " "If set to zero, it will use the default extrusion width."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "first-layer-extrusion-width=s"; def->ratio_over = "first_layer_height"; def->default_value = new ConfigOptionFloatOrPercent(200, true); def = this->add("first_layer_height", coFloatOrPercent); def->label = "First layer height"; def->category = "Layers and Perimeters"; def->tooltip = "When printing with very low layer heights, you might still want to print a thicker " "bottom layer to improve adhesion and tolerance for non perfect build plates. " "This can be expressed as an absolute value or as a percentage (for example: 150%) " "over the default layer height."; def->sidetext = "mm or %"; def->cli = "first-layer-height=s"; def->ratio_over = "layer_height"; def->default_value = new ConfigOptionFloatOrPercent(0.35, false); def = this->add("first_layer_speed", coFloatOrPercent); def->label = "First layer speed"; def->tooltip = "If expressed as absolute value in mm/s, this speed will be applied to all the print moves " "of the first layer, regardless of their type. If expressed as a percentage " "(for example: 40%) it will scale the default speeds."; def->sidetext = "mm/s or %"; def->cli = "first-layer-speed=s"; def->min = 0; def->default_value = new ConfigOptionFloatOrPercent(30, false); def = this->add("first_layer_temperature", coInts); def->label = "First layer"; def->tooltip = "Extruder temperature for first layer. If you want to control temperature manually " "during print, set this to zero to disable temperature control commands in the output file."; def->cli = "first-layer-temperature=i@"; def->min = 0; def->max = max_temp; def->default_value = new ConfigOptionInts { 200 }; def = this->add("gap_fill_speed", coFloat); def->label = "Gap fill"; def->category = "Speed"; def->tooltip = "Speed for filling small gaps using short zigzag moves. Keep this reasonably low " "to avoid too much shaking and resonance issues. Set zero to disable gaps filling."; def->sidetext = "mm/s"; def->cli = "gap-fill-speed=f"; def->min = 0; def->default_value = new ConfigOptionFloat(20); def = this->add("gcode_comments", coBool); def->label = "Verbose G-code"; def->tooltip = "Enable this to get a commented G-code file, with each line explained by a descriptive text. " "If you print from SD card, the additional weight of the file could make your firmware " "slow down."; def->cli = "gcode-comments!"; def->default_value = new ConfigOptionBool(0); def = this->add("gcode_flavor", coEnum); def->label = "G-code flavor"; def->tooltip = "Some G/M-code commands, including temperature control and others, are not universal. " "Set this option to your printer's firmware to get a compatible output. " "The \"No extrusion\" flavor prevents Slic3r from exporting any extrusion value at all."; def->cli = "gcode-flavor=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("reprap"); def->enum_values.push_back("repetier"); def->enum_values.push_back("teacup"); def->enum_values.push_back("makerware"); def->enum_values.push_back("marlin"); def->enum_values.push_back("sailfish"); def->enum_values.push_back("mach3"); def->enum_values.push_back("machinekit"); def->enum_values.push_back("smoothie"); def->enum_values.push_back("no-extrusion"); def->enum_labels.push_back("RepRap/Sprinter"); def->enum_labels.push_back("Repetier"); def->enum_labels.push_back("Teacup"); def->enum_labels.push_back("MakerWare (MakerBot)"); def->enum_labels.push_back("Marlin"); def->enum_labels.push_back("Sailfish (MakerBot)"); def->enum_labels.push_back("Mach3/LinuxCNC"); def->enum_labels.push_back("Machinekit"); def->enum_labels.push_back("Smoothie"); def->enum_labels.push_back("No extrusion"); def->default_value = new ConfigOptionEnum(gcfMarlin); def = this->add("infill_acceleration", coFloat); def->label = "Infill"; def->tooltip = "This is the acceleration your printer will use for infill. Set zero to disable " "acceleration control for infill."; def->sidetext = "mm/s²"; def->cli = "infill-acceleration=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); def = this->add("infill_every_layers", coInt); def->label = "Combine infill every"; def->category = "Infill"; def->tooltip = "This feature allows to combine infill and speed up your print by extruding thicker " "infill layers while preserving thin perimeters, thus accuracy."; def->sidetext = "layers"; def->cli = "infill-every-layers=i"; def->full_label = "Combine infill every n layers"; def->min = 1; def->default_value = new ConfigOptionInt(1); def = this->add("infill_extruder", coInt); def->label = "Infill extruder"; def->category = "Extruders"; def->tooltip = "The extruder to use when printing infill."; def->cli = "infill-extruder=i"; def->min = 1; def->default_value = new ConfigOptionInt(1); def = this->add("infill_extrusion_width", coFloatOrPercent); def->label = "Infill"; def->category = "Extrusion Width"; def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill. " "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " "You may want to use fatter extrudates to speed up the infill and make your parts stronger. " "If expressed as percentage (for example 90%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "infill-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("infill_first", coBool); def->label = "Infill before perimeters"; def->tooltip = "This option will switch the print order of perimeters and infill, making the latter first."; def->cli = "infill-first!"; def->default_value = new ConfigOptionBool(false); def = this->add("infill_only_where_needed", coBool); def->label = "Only infill where needed"; def->category = "Infill"; def->tooltip = "This option will limit infill to the areas actually needed for supporting ceilings " "(it will act as internal support material). If enabled, slows down the G-code generation " "due to the multiple checks involved."; def->cli = "infill-only-where-needed!"; def->default_value = new ConfigOptionBool(false); def = this->add("infill_overlap", coFloatOrPercent); def->label = "Infill/perimeters overlap"; def->category = "Advanced"; def->tooltip = "This setting applies an additional overlap between infill and perimeters for better bonding. " "Theoretically this shouldn't be needed, but backlash might cause gaps. If expressed " "as percentage (example: 15%) it is calculated over perimeter extrusion width."; def->sidetext = "mm or %"; def->cli = "infill-overlap=s"; def->ratio_over = "perimeter_extrusion_width"; def->default_value = new ConfigOptionFloatOrPercent(25, true); def = this->add("infill_speed", coFloat); def->label = "Infill"; def->category = "Speed"; def->tooltip = "Speed for printing the internal fill. Set to zero for auto."; def->sidetext = "mm/s"; def->cli = "infill-speed=f"; def->aliases.push_back("print_feed_rate"); def->aliases.push_back("infill_feed_rate"); def->min = 0; def->default_value = new ConfigOptionFloat(80); def = this->add("interface_shells", coBool); def->label = "Interface shells"; def->tooltip = "Force the generation of solid shells between adjacent materials/volumes. " "Useful for multi-extruder prints with translucent materials or manual soluble " "support material."; def->cli = "interface-shells!"; def->category = "Layers and Perimeters"; def->default_value = new ConfigOptionBool(false); def = this->add("layer_gcode", coString); def->label = "After layer change G-code"; def->tooltip = "This custom code is inserted at every layer change, right after the Z move " "and before the extruder moves to the first layer point. Note that you can use " "placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]."; def->cli = "after-layer-gcode|layer-gcode=s"; def->multiline = true; def->full_width = true; def->height = 50; def->default_value = new ConfigOptionString(""); def = this->add("layer_height", coFloat); def->label = "Layer height"; def->category = "Layers and Perimeters"; def->tooltip = "This setting controls the height (and thus the total number) of the slices/layers. " "Thinner layers give better accuracy but take more time to print."; def->sidetext = "mm"; def->cli = "layer-height=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0.3); def = this->add("max_fan_speed", coInts); def->label = "Max"; def->tooltip = "This setting represents the maximum speed of your fan."; def->sidetext = "%"; def->cli = "max-fan-speed=i@"; def->min = 0; def->max = 100; def->default_value = new ConfigOptionInts { 100 }; def = this->add("max_layer_height", coFloats); def->label = "Max"; def->tooltip = "This is the highest printable layer height for this extruder, used to cap " "the variable layer height and support layer height. Maximum recommended layer height " "is 75% of the extrusion width to achieve reasonable inter-layer adhesion. " "If set to 0, layer height is limited to 75% of the nozzle diameter."; def->sidetext = "mm"; def->cli = "max-layer-height=f@"; def->min = 0; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("max_print_speed", coFloat); def->label = "Max print speed"; def->tooltip = "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed " "in order to keep constant extruder pressure. This experimental setting is used " "to set the highest print speed you want to allow."; def->sidetext = "mm/s"; def->cli = "max-print-speed=f"; def->min = 1; def->default_value = new ConfigOptionFloat(80); def = this->add("max_volumetric_speed", coFloat); def->label = "Max volumetric speed"; def->tooltip = "This experimental setting is used to set the maximum volumetric speed your " "extruder supports."; def->sidetext = "mm³/s"; def->cli = "max-volumetric-speed=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); def = this->add("max_volumetric_extrusion_rate_slope_positive", coFloat); def->label = "Max volumetric slope positive"; def->tooltip = "This experimental setting is used to limit the speed of change in extrusion rate. " "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) " "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."; def->sidetext = "mm³/s²"; def->cli = "max-volumetric-extrusion-rate-slope-positive=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); def = this->add("max_volumetric_extrusion_rate_slope_negative", coFloat); def->label = "Max volumetric slope negative"; def->tooltip = "This experimental setting is used to limit the speed of change in extrusion rate. " "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) " "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."; def->sidetext = "mm³/s²"; def->cli = "max-volumetric-extrusion-rate-slope-negative=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); def = this->add("min_fan_speed", coInts); def->label = "Min"; def->tooltip = "This setting represents the minimum PWM your fan needs to work."; def->sidetext = "%"; def->cli = "min-fan-speed=i@"; def->min = 0; def->max = 100; def->default_value = new ConfigOptionInts { 35 }; def = this->add("min_layer_height", coFloats); def->label = "Min"; def->tooltip = "This is the lowest printable layer height for this extruder and limits " "the resolution for variable layer height. Typical values are between 0.05 mm and 0.1 mm."; def->sidetext = "mm"; def->cli = "min-layer-height=f@"; def->min = 0; def->default_value = new ConfigOptionFloats { 0.07 }; def = this->add("min_print_speed", coFloats); def->label = "Min print speed"; def->tooltip = "Slic3r will not scale speed down below this speed."; def->sidetext = "mm/s"; def->cli = "min-print-speed=f@"; def->min = 0; def->default_value = new ConfigOptionFloats { 10. }; def = this->add("min_skirt_length", coFloat); def->label = "Minimum extrusion length"; def->tooltip = "Generate no less than the number of skirt loops required to consume " "the specified amount of filament on the bottom layer. For multi-extruder machines, " "this minimum applies to each extruder."; def->sidetext = "mm"; def->cli = "min-skirt-length=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); def = this->add("notes", coString); def->label = "Configuration notes"; def->tooltip = "You can put here your personal notes. This text will be added to the G-code " "header comments."; def->cli = "notes=s"; def->multiline = true; def->full_width = true; def->height = 130; def->default_value = new ConfigOptionString(""); def = this->add("nozzle_diameter", coFloats); def->label = "Nozzle diameter"; def->tooltip = "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)"; def->sidetext = "mm"; def->cli = "nozzle-diameter=f@"; def->default_value = new ConfigOptionFloats { 0.5 }; def = this->add("octoprint_apikey", coString); def->label = "API Key"; def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain " "the API Key required for authentication."; def->cli = "octoprint-apikey=s"; def->default_value = new ConfigOptionString(""); def = this->add("octoprint_host", coString); def->label = "Host or IP"; def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain " "the hostname or IP address of the OctoPrint instance."; def->cli = "octoprint-host=s"; def->default_value = new ConfigOptionString(""); def = this->add("only_retract_when_crossing_perimeters", coBool); def->label = "Only retract when crossing perimeters"; def->tooltip = "Disables retraction when the travel path does not exceed the upper layer's perimeters " "(and thus any ooze will be probably invisible)."; def->cli = "only-retract-when-crossing-perimeters!"; def->default_value = new ConfigOptionBool(true); def = this->add("ooze_prevention", coBool); def->label = "Enable"; def->tooltip = "This option will drop the temperature of the inactive extruders to prevent oozing. " "It will enable a tall skirt automatically and move extruders outside such " "skirt when changing temperatures."; def->cli = "ooze-prevention!"; def->default_value = new ConfigOptionBool(false); def = this->add("output_filename_format", coString); def->label = "Output filename format"; def->tooltip = "You can use all configuration options as variables inside this template. " "For example: [layer_height], [fill_density] etc. You can also use [timestamp], " "[year], [month], [day], [hour], [minute], [second], [version], [input_filename], " "[input_filename_base]."; def->cli = "output-filename-format=s"; def->full_width = true; def->default_value = new ConfigOptionString("[input_filename_base].gcode"); def = this->add("overhangs", coBool); def->label = "Detect bridging perimeters"; def->category = "Layers and Perimeters"; def->tooltip = "Experimental option to adjust flow for overhangs (bridge flow will be used), " "to apply bridge speed to them and enable fan."; def->cli = "overhangs!"; def->default_value = new ConfigOptionBool(true); def = this->add("perimeter_acceleration", coFloat); def->label = "Perimeters"; def->tooltip = "This is the acceleration your printer will use for perimeters. " "A high value like 9000 usually gives good results if your hardware is up to the job. " "Set zero to disable acceleration control for perimeters."; def->sidetext = "mm/s²"; def->cli = "perimeter-acceleration=f"; def->default_value = new ConfigOptionFloat(0); def = this->add("perimeter_extruder", coInt); def->label = "Perimeter extruder"; def->category = "Extruders"; def->tooltip = "The extruder to use when printing perimeters and brim. First extruder is 1."; def->cli = "perimeter-extruder=i"; def->aliases.push_back("perimeters_extruder"); def->min = 1; def->default_value = new ConfigOptionInt(1); def = this->add("perimeter_extrusion_width", coFloatOrPercent); def->label = "Perimeters"; def->category = "Extrusion Width"; def->tooltip = "Set this to a non-zero value to set a manual extrusion width for perimeters. " "You may want to use thinner extrudates to get more accurate surfaces. " "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " "If expressed as percentage (for example 200%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "perimeter-extrusion-width=s"; def->aliases.push_back("perimeters_extrusion_width"); def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("perimeter_speed", coFloat); def->label = "Perimeters"; def->category = "Speed"; def->tooltip = "Speed for perimeters (contours, aka vertical shells). Set to zero for auto."; def->sidetext = "mm/s"; def->cli = "perimeter-speed=f"; def->aliases.push_back("perimeter_feed_rate"); def->min = 0; def->default_value = new ConfigOptionFloat(60); def = this->add("perimeters", coInt); def->label = "Perimeters"; def->category = "Layers and Perimeters"; def->tooltip = "This option sets the number of perimeters to generate for each layer. " "Note that Slic3r may increase this number automatically when it detects " "sloping surfaces which benefit from a higher number of perimeters " "if the Extra Perimeters option is enabled."; def->sidetext = "(minimum)"; def->cli = "perimeters=i"; def->aliases.push_back("perimeter_offsets"); def->min = 0; def->default_value = new ConfigOptionInt(3); def = this->add("post_process", coStrings); def->label = "Post-processing scripts"; def->tooltip = "If you want to process the output G-code through custom scripts, " "just list their absolute paths here. Separate multiple scripts with a semicolon. " "Scripts will be passed the absolute path to the G-code file as the first argument, " "and they can access the Slic3r config settings by reading environment variables."; def->cli = "post-process=s@"; def->gui_flags = "serialized"; def->multiline = true; def->full_width = true; def->height = 60; def = this->add("printer_notes", coString); def->label = "Printer notes"; def->tooltip = "You can put your notes regarding the printer here."; def->cli = "printer-notes=s"; def->multiline = true; def->full_width = true; def->height = 130; def->default_value = new ConfigOptionString(""); def = this->add("print_settings_id", coString); def->default_value = new ConfigOptionString(""); def = this->add("printer_settings_id", coString); def->default_value = new ConfigOptionString(""); def = this->add("raft_layers", coInt); def->label = "Raft layers"; def->category = "Support material"; def->tooltip = "The object will be raised by this number of layers, and support material " "will be generated under it."; def->sidetext = "layers"; def->cli = "raft-layers=i"; def->min = 0; def->default_value = new ConfigOptionInt(0); def = this->add("resolution", coFloat); def->label = "Resolution"; def->tooltip = "Minimum detail resolution, used to simplify the input file for speeding up " "the slicing job and reducing memory usage. High-resolution models often carry " "more detail than printers can render. Set to zero to disable any simplification " "and use full resolution from input."; def->sidetext = "mm"; def->cli = "resolution=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); def = this->add("retract_before_travel", coFloats); def->label = "Minimum travel after retraction"; def->tooltip = "Retraction is not triggered when travel moves are shorter than this length."; def->sidetext = "mm"; def->cli = "retract-before-travel=f@"; def->default_value = new ConfigOptionFloats { 2. }; def = this->add("retract_before_wipe", coPercents); def->label = "Retract amount before wipe"; def->tooltip = "With bowden extruders, it may be wise to do some amount of quick retract " "before doing the wipe movement."; def->sidetext = "%"; def->cli = "retract-before-wipe=s@"; def->default_value = new ConfigOptionPercents { 0. }; def = this->add("retract_layer_change", coBools); def->label = "Retract on layer change"; def->tooltip = "This flag enforces a retraction whenever a Z move is done."; def->cli = "retract-layer-change!"; def->default_value = new ConfigOptionBools { false }; def = this->add("retract_length", coFloats); def->label = "Length"; def->full_label = "Retraction Length"; def->tooltip = "When retraction is triggered, filament is pulled back by the specified amount " "(the length is measured on raw filament, before it enters the extruder)."; def->sidetext = "mm (zero to disable)"; def->cli = "retract-length=f@"; def->default_value = new ConfigOptionFloats { 2. }; def = this->add("retract_length_toolchange", coFloats); def->label = "Length"; def->full_label = "Retraction Length (Toolchange)"; def->tooltip = "When retraction is triggered before changing tool, filament is pulled back " "by the specified amount (the length is measured on raw filament, before it enters " "the extruder)."; def->sidetext = "mm (zero to disable)"; def->cli = "retract-length-toolchange=f@"; def->default_value = new ConfigOptionFloats { 10. }; def = this->add("retract_lift", coFloats); def->label = "Lift Z"; def->tooltip = "If you set this to a positive value, Z is quickly raised every time a retraction " "is triggered. When using multiple extruders, only the setting for the first extruder " "will be considered."; def->sidetext = "mm"; def->cli = "retract-lift=f@"; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_lift_above", coFloats); def->label = "Above Z"; def->full_label = "Only lift Z above"; def->tooltip = "If you set this to a positive value, Z lift will only take place above the specified " "absolute Z. You can tune this setting for skipping lift on the first layers."; def->sidetext = "mm"; def->cli = "retract-lift-above=f@"; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_lift_below", coFloats); def->label = "Below Z"; def->full_label = "Only lift Z below"; def->tooltip = "If you set this to a positive value, Z lift will only take place below " "the specified absolute Z. You can tune this setting for limiting lift " "to the first layers."; def->sidetext = "mm"; def->cli = "retract-lift-below=f@"; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_restart_extra", coFloats); def->label = "Extra length on restart"; def->tooltip = "When the retraction is compensated after the travel move, the extruder will push " "this additional amount of filament. This setting is rarely needed."; def->sidetext = "mm"; def->cli = "retract-restart-extra=f@"; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_restart_extra_toolchange", coFloats); def->label = "Extra length on restart"; def->tooltip = "When the retraction is compensated after changing tool, the extruder will push " "this additional amount of filament."; def->sidetext = "mm"; def->cli = "retract-restart-extra-toolchange=f@"; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_speed", coFloats); def->label = "Retraction Speed"; def->full_label = "Retraction Speed"; def->tooltip = "The speed for retractions (it only applies to the extruder motor)."; def->sidetext = "mm/s"; def->cli = "retract-speed=f@"; def->default_value = new ConfigOptionFloats { 40. }; def = this->add("deretract_speed", coFloats); def->label = "Deretraction Speed"; def->full_label = "Deretraction Speed"; def->tooltip = "The speed for loading of a filament into extruder after retraction " "(it only applies to the extruder motor). If left to zero, the retraction speed is used."; def->sidetext = "mm/s"; def->cli = "retract-speed=f@"; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("seam_position", coEnum); def->label = "Seam position"; def->category = "Layers and Perimeters"; def->tooltip = "Position of perimeters starting points."; def->cli = "seam-position=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("random"); def->enum_values.push_back("nearest"); def->enum_values.push_back("aligned"); def->enum_values.push_back("rear"); def->enum_labels.push_back("Random"); def->enum_labels.push_back("Nearest"); def->enum_labels.push_back("Aligned"); def->enum_labels.push_back("Rear"); def->default_value = new ConfigOptionEnum(spAligned); #if 0 def = this->add("seam_preferred_direction", coFloat); // def->gui_type = "slider"; def->label = "Direction"; def->sidetext = "°"; def->full_label = "Preferred direction of the seam"; def->tooltip = "Seam preferred direction"; def->cli = "seam-preferred-direction=f"; def->min = 0; def->max = 360; def->default_value = new ConfigOptionFloat(0); def = this->add("seam_preferred_direction_jitter", coFloat); // def->gui_type = "slider"; def->label = "Jitter"; def->sidetext = "°"; def->full_label = "Seam preferred direction jitter"; def->tooltip = "Preferred direction of the seam - jitter"; def->cli = "seam-preferred-direction-jitter=f"; def->min = 0; def->max = 360; def->default_value = new ConfigOptionFloat(30); #endif def = this->add("serial_port", coString); def->gui_type = "select_open"; def->label = ""; def->full_label = "Serial port"; def->tooltip = "USB/serial port for printer connection."; def->cli = "serial-port=s"; def->width = 200; def->default_value = new ConfigOptionString(""); def = this->add("serial_speed", coInt); def->gui_type = "i_enum_open"; def->label = "Speed"; def->full_label = "Serial port speed"; def->tooltip = "Speed (baud) of USB/serial port for printer connection."; def->cli = "serial-speed=i"; def->min = 1; def->max = 300000; def->enum_values.push_back("115200"); def->enum_values.push_back("250000"); def->default_value = new ConfigOptionInt(250000); def = this->add("skirt_distance", coFloat); def->label = "Distance from object"; def->tooltip = "Distance between skirt and object(s). Set this to zero to attach the skirt " "to the object(s) and get a brim for better adhesion."; def->sidetext = "mm"; def->cli = "skirt-distance=f"; def->min = 0; def->default_value = new ConfigOptionFloat(6); def = this->add("skirt_height", coInt); def->label = "Skirt height"; def->tooltip = "Height of skirt expressed in layers. Set this to a tall value to use skirt " "as a shield against drafts."; def->sidetext = "layers"; def->cli = "skirt-height=i"; def->default_value = new ConfigOptionInt(1); def = this->add("skirts", coInt); def->label = "Loops (minimum)"; def->full_label = "Skirt Loops"; def->tooltip = "Number of loops for the skirt. If the Minimum Extrusion Length option is set, " "the number of loops might be greater than the one configured here. Set this to zero " "to disable skirt completely."; def->cli = "skirts=i"; def->min = 0; def->default_value = new ConfigOptionInt(1); def = this->add("slowdown_below_layer_time", coInts); def->label = "Slow down if layer print time is below"; def->tooltip = "If layer print time is estimated below this number of seconds, print moves " "speed will be scaled down to extend duration to this value."; def->sidetext = "approximate seconds"; def->cli = "slowdown-below-layer-time=i@"; def->width = 60; def->min = 0; def->max = 1000; def->default_value = new ConfigOptionInts { 5 }; def = this->add("small_perimeter_speed", coFloatOrPercent); def->label = "Small perimeters"; def->category = "Speed"; def->tooltip = "This separate setting will affect the speed of perimeters having radius <= 6.5mm " "(usually holes). If expressed as percentage (for example: 80%) it will be calculated " "on the perimeters speed setting above. Set to zero for auto."; def->sidetext = "mm/s or %"; def->cli = "small-perimeter-speed=s"; def->ratio_over = "perimeter_speed"; def->min = 0; def->default_value = new ConfigOptionFloatOrPercent(15, false); def = this->add("solid_infill_below_area", coFloat); def->label = "Solid infill threshold area"; def->category = "Infill"; def->tooltip = "Force solid infill for regions having a smaller area than the specified threshold."; def->sidetext = "mm²"; def->cli = "solid-infill-below-area=f"; def->min = 0; def->default_value = new ConfigOptionFloat(70); def = this->add("solid_infill_extruder", coInt); def->label = "Solid infill extruder"; def->category = "Extruders"; def->tooltip = "The extruder to use when printing solid infill."; def->cli = "solid-infill-extruder=i"; def->min = 1; def->default_value = new ConfigOptionInt(1); def = this->add("solid_infill_every_layers", coInt); def->label = "Solid infill every"; def->category = "Infill"; def->tooltip = "This feature allows to force a solid layer every given number of layers. " "Zero to disable. You can set this to any value (for example 9999); " "Slic3r will automatically choose the maximum possible number of layers " "to combine according to nozzle diameter and layer height."; def->sidetext = "layers"; def->cli = "solid-infill-every-layers=i"; def->min = 0; def->default_value = new ConfigOptionInt(0); def = this->add("solid_infill_extrusion_width", coFloatOrPercent); def->label = "Solid infill"; def->category = "Extrusion Width"; def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for solid surfaces. " "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " "If expressed as percentage (for example 90%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "solid-infill-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("solid_infill_speed", coFloatOrPercent); def->label = "Solid infill"; def->category = "Speed"; def->tooltip = "Speed for printing solid regions (top/bottom/internal horizontal shells). " "This can be expressed as a percentage (for example: 80%) over the default " "infill speed above. Set to zero for auto."; def->sidetext = "mm/s or %"; def->cli = "solid-infill-speed=s"; def->ratio_over = "infill_speed"; def->aliases.push_back("solid_infill_feed_rate"); def->min = 0; def->default_value = new ConfigOptionFloatOrPercent(20, false); def = this->add("solid_layers", coInt); def->label = "Solid layers"; def->tooltip = "Number of solid layers to generate on top and bottom surfaces."; def->cli = "solid-layers=i"; def->shortcut.push_back("top_solid_layers"); def->shortcut.push_back("bottom_solid_layers"); def->min = 0; def = this->add("spiral_vase", coBool); def->label = "Spiral vase"; def->tooltip = "This feature will raise Z gradually while printing a single-walled object " "in order to remove any visible seam. This option requires a single perimeter, " "no infill, no top solid layers and no support material. You can still set " "any number of bottom solid layers as well as skirt/brim loops. " "It won't work when printing more than an object."; def->cli = "spiral-vase!"; def->default_value = new ConfigOptionBool(false); def = this->add("standby_temperature_delta", coInt); def->label = "Temperature variation"; def->tooltip = "Temperature difference to be applied when an extruder is not active. " "Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."; def->sidetext = "∆°C"; def->cli = "standby-temperature-delta=i"; def->min = -max_temp; def->max = max_temp; def->default_value = new ConfigOptionInt(-5); def = this->add("start_gcode", coString); def->label = "Start G-code"; def->tooltip = "This start procedure is inserted at the beginning, after bed has reached " "the target temperature and extruder just started heating, and before extruder " "has finished heating. If Slic3r detects M104 or M190 in your custom codes, " "such commands will not be prepended automatically so you're free to customize " "the order of heating commands and other custom actions. Note that you can use " "placeholder variables for all Slic3r settings, so you can put " "a \"M109 S[first_layer_temperature]\" command wherever you want."; def->cli = "start-gcode=s"; def->multiline = true; def->full_width = true; def->height = 120; def->default_value = new ConfigOptionString("G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n"); def = this->add("start_filament_gcode", coStrings); def->label = "Start G-code"; def->tooltip = "This start procedure is inserted at the beginning, after any printer start gcode. " "This is used to override settings for a specific filament. If Slic3r detects " "M104, M109, M140 or M190 in your custom codes, such commands will " "not be prepended automatically so you're free to customize the order " "of heating commands and other custom actions. Note that you can use placeholder variables " "for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command " "wherever you want. If you have multiple extruders, the gcode is processed " "in extruder order."; def->cli = "start-filament-gcode=s@"; def->multiline = true; def->full_width = true; def->height = 120; def->default_value = new ConfigOptionStrings { "; Filament gcode\n" }; def = this->add("single_extruder_multi_material", coBool); def->label = "Single Extruder Multi Material"; def->tooltip = "The printer multiplexes filaments into a single hot end."; def->cli = "single-extruder-multi-material!"; def->default_value = new ConfigOptionBool(false); def = this->add("support_material", coBool); def->label = "Generate support material"; def->category = "Support material"; def->tooltip = "Enable support material generation."; def->cli = "support-material!"; def->default_value = new ConfigOptionBool(false); def = this->add("support_material_xy_spacing", coFloatOrPercent); def->label = "XY separation between an object and its support"; def->category = "Support material"; def->tooltip = "XY separation between an object and its support. If expressed as percentage " "(for example 50%), it will be calculated over external perimeter width."; def->sidetext = "mm or %"; def->cli = "support-material-xy-spacing=s"; def->ratio_over = "external_perimeter_extrusion_width"; def->min = 0; // Default is half the external perimeter width. def->default_value = new ConfigOptionFloatOrPercent(50, true); def = this->add("support_material_angle", coFloat); def->label = "Pattern angle"; def->category = "Support material"; def->tooltip = "Use this setting to rotate the support material pattern on the horizontal plane."; def->sidetext = "°"; def->cli = "support-material-angle=f"; def->min = 0; def->max = 359; def->default_value = new ConfigOptionFloat(0); def = this->add("support_material_buildplate_only", coBool); def->label = "Support on build plate only"; def->category = "Support material"; def->tooltip = "Only create support if it lies on a build plate. Don't create support on a print."; def->cli = "support-material-buildplate-only!"; def->default_value = new ConfigOptionBool(false); def = this->add("support_material_contact_distance", coFloat); def->gui_type = "f_enum_open"; def->label = "Contact Z distance"; def->category = "Support material"; def->tooltip = "The vertical distance between object and support material interface. " "Setting this to 0 will also prevent Slic3r from using bridge flow and speed " "for the first object layer."; def->sidetext = "mm"; def->cli = "support-material-contact-distance=f"; def->min = 0; def->enum_values.push_back("0"); def->enum_values.push_back("0.2"); def->enum_labels.push_back("0 (soluble)"); def->enum_labels.push_back("0.2 (detachable)"); def->default_value = new ConfigOptionFloat(0.2); def = this->add("support_material_enforce_layers", coInt); def->label = "Enforce support for the first"; def->category = "Support material"; def->tooltip = "Generate support material for the specified number of layers counting from bottom, " "regardless of whether normal support material is enabled or not and regardless " "of any angle threshold. This is useful for getting more adhesion of objects " "having a very thin or poor footprint on the build plate."; def->sidetext = "layers"; def->cli = "support-material-enforce-layers=f"; def->full_label = "Enforce support for the first n layers"; def->min = 0; def->default_value = new ConfigOptionInt(0); def = this->add("support_material_extruder", coInt); def->label = "Support material/raft/skirt extruder"; def->category = "Extruders"; def->tooltip = "The extruder to use when printing support material, raft and skirt " "(1+, 0 to use the current extruder to minimize tool changes)."; def->cli = "support-material-extruder=i"; def->min = 0; def->default_value = new ConfigOptionInt(1); def = this->add("support_material_extrusion_width", coFloatOrPercent); def->label = "Support material"; def->category = "Extrusion Width"; def->tooltip = "Set this to a non-zero value to set a manual extrusion width for support material. " "If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. " "If expressed as percentage (for example 90%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "support-material-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("support_material_interface_contact_loops", coBool); def->label = "Interface loops"; def->category = "Support material"; def->tooltip = "Cover the top contact layer of the supports with loops. Disabled by default."; def->cli = "support-material-interface-contact-loops!"; def->default_value = new ConfigOptionBool(false); def = this->add("support_material_interface_extruder", coInt); def->label = "Support material/raft interface extruder"; def->category = "Extruders"; def->tooltip = "The extruder to use when printing support material interface " "(1+, 0 to use the current extruder to minimize tool changes). This affects raft too."; def->cli = "support-material-interface-extruder=i"; def->min = 0; def->default_value = new ConfigOptionInt(1); def = this->add("support_material_interface_layers", coInt); def->label = "Interface layers"; def->category = "Support material"; def->tooltip = "Number of interface layers to insert between the object(s) and support material."; def->sidetext = "layers"; def->cli = "support-material-interface-layers=i"; def->min = 0; def->default_value = new ConfigOptionInt(3); def = this->add("support_material_interface_spacing", coFloat); def->label = "Interface pattern spacing"; def->category = "Support material"; def->tooltip = "Spacing between interface lines. Set zero to get a solid interface."; def->sidetext = "mm"; def->cli = "support-material-interface-spacing=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0); def = this->add("support_material_interface_speed", coFloatOrPercent); def->label = "Support material interface"; def->category = "Support material"; def->tooltip = "Speed for printing support material interface layers. If expressed as percentage " "(for example 50%) it will be calculated over support material speed."; def->sidetext = "mm/s or %"; def->cli = "support-material-interface-speed=s"; def->ratio_over = "support_material_speed"; def->min = 0; def->default_value = new ConfigOptionFloatOrPercent(100, true); def = this->add("support_material_pattern", coEnum); def->label = "Pattern"; def->category = "Support material"; def->tooltip = "Pattern used to generate support material."; def->cli = "support-material-pattern=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("rectilinear"); def->enum_values.push_back("rectilinear-grid"); def->enum_values.push_back("honeycomb"); def->enum_values.push_back("pillars"); def->enum_labels.push_back("rectilinear"); def->enum_labels.push_back("rectilinear grid"); def->enum_labels.push_back("honeycomb"); def->enum_labels.push_back("pillars"); def->default_value = new ConfigOptionEnum(smpPillars); def = this->add("support_material_spacing", coFloat); def->label = "Pattern spacing"; def->category = "Support material"; def->tooltip = "Spacing between support material lines."; def->sidetext = "mm"; def->cli = "support-material-spacing=f"; def->min = 0; def->default_value = new ConfigOptionFloat(2.5); def = this->add("support_material_speed", coFloat); def->label = "Support material"; def->category = "Support material"; def->tooltip = "Speed for printing support material."; def->sidetext = "mm/s"; def->cli = "support-material-speed=f"; def->min = 0; def->default_value = new ConfigOptionFloat(60); def = this->add("support_material_synchronize_layers", coBool); def->label = "Synchronize with object layers"; def->category = "Support material"; def->tooltip = "Synchronize support layers with the object print layers. This is useful " "with multi-material printers, where the extruder switch is expensive."; def->cli = "support-material-synchronize-layers!"; def->default_value = new ConfigOptionBool(false); def = this->add("support_material_threshold", coInt); def->label = "Overhang threshold"; def->category = "Support material"; def->tooltip = "Support material will not be generated for overhangs whose slope angle " "(90° = vertical) is above the given threshold. In other words, this value " "represent the most horizontal slope (measured from the horizontal plane) " "that you can print without support material. Set to zero for automatic detection " "(recommended)."; def->sidetext = "°"; def->cli = "support-material-threshold=i"; def->min = 0; def->max = 90; def->default_value = new ConfigOptionInt(0); def = this->add("support_material_with_sheath", coBool); def->label = "With sheath around the support"; def->category = "Support material"; def->tooltip = "Add a sheath (a single perimeter line) around the base support. This makes " "the support more reliable, but also more difficult to remove."; def->cli = "support-material-with-sheath!"; def->default_value = new ConfigOptionBool(true); def = this->add("temperature", coInts); def->label = "Other layers"; def->tooltip = "Extruder temperature for layers after the first one. Set this to zero to disable " "temperature control commands in the output."; def->cli = "temperature=i@"; def->full_label = "Temperature"; def->max = 0; def->max = max_temp; def->default_value = new ConfigOptionInts { 200 }; def = this->add("thin_walls", coBool); def->label = "Detect thin walls"; def->category = "Layers and Perimeters"; def->tooltip = "Detect single-width walls (parts where two extrusions don't fit and we need " "to collapse them into a single trace)."; def->cli = "thin-walls!"; def->default_value = new ConfigOptionBool(true); def = this->add("threads", coInt); def->label = "Threads"; def->tooltip = "Threads are used to parallelize long-running tasks. Optimal threads number " "is slightly above the number of available cores/processors."; def->cli = "threads|j=i"; def->readonly = true; def->min = 1; { int threads = (unsigned int)boost::thread::hardware_concurrency(); def->default_value = new ConfigOptionInt(threads > 0 ? threads : 2); } def = this->add("toolchange_gcode", coString); def->label = "Tool change G-code"; def->tooltip = "This custom code is inserted right before every extruder change. " "Note that you can use placeholder variables for all Slic3r settings as well " "as [previous_extruder] and [next_extruder]."; def->cli = "toolchange-gcode=s"; def->multiline = true; def->full_width = true; def->height = 50; def->default_value = new ConfigOptionString(""); def = this->add("top_infill_extrusion_width", coFloatOrPercent); def->label = "Top solid infill"; def->category = "Extrusion Width"; def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. " "You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. " "If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. " "If expressed as percentage (for example 90%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "top-infill-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("top_solid_infill_speed", coFloatOrPercent); def->label = "Top solid infill"; def->category = "Speed"; def->tooltip = "Speed for printing top solid layers (it only applies to the uppermost " "external layers and not to their internal solid layers). You may want " "to slow down this to get a nicer surface finish. This can be expressed " "as a percentage (for example: 80%) over the solid infill speed above. " "Set to zero for auto."; def->sidetext = "mm/s or %"; def->cli = "top-solid-infill-speed=s"; def->ratio_over = "solid_infill_speed"; def->min = 0; def->default_value = new ConfigOptionFloatOrPercent(15, false); def = this->add("top_solid_layers", coInt); def->label = "Top"; def->category = "Layers and Perimeters"; def->tooltip = "Number of solid layers to generate on top surfaces."; def->cli = "top-solid-layers=i"; def->full_label = "Top solid layers"; def->min = 0; def->default_value = new ConfigOptionInt(3); def = this->add("travel_speed", coFloat); def->label = "Travel"; def->tooltip = "Speed for travel moves (jumps between distant extrusion points)."; def->sidetext = "mm/s"; def->cli = "travel-speed=f"; def->aliases.push_back("travel_feed_rate"); def->min = 1; def->default_value = new ConfigOptionFloat(130); def = this->add("use_firmware_retraction", coBool); def->label = "Use firmware retraction"; def->tooltip = "This experimental setting uses G10 and G11 commands to have the firmware " "handle the retraction. This is only supported in recent Marlin."; def->cli = "use-firmware-retraction!"; def->default_value = new ConfigOptionBool(false); def = this->add("use_relative_e_distances", coBool); def->label = "Use relative E distances"; def->tooltip = "If your firmware requires relative E values, check this, " "otherwise leave it unchecked. Most firmwares use absolute values."; def->cli = "use-relative-e-distances!"; def->default_value = new ConfigOptionBool(false); def = this->add("use_volumetric_e", coBool); def->label = "Use volumetric E"; def->tooltip = "This experimental setting uses outputs the E values in cubic millimeters " "instead of linear millimeters. If your firmware doesn't already know " "filament diameter(s), you can put commands like 'M200 D[filament_diameter_0] T0' " "in your start G-code in order to turn volumetric mode on and use the filament " "diameter associated to the filament selected in Slic3r. This is only supported " "in recent Marlin."; def->cli = "use-volumetric-e!"; def->default_value = new ConfigOptionBool(false); def = this->add("variable_layer_height", coBool); def->label = "Enable variable layer height feature"; def->tooltip = "Some printers or printer setups may have difficulties printing " "with a variable layer height. Enabled by default."; def->cli = "variable-layer-height!"; def->default_value = new ConfigOptionBool(true); def = this->add("wipe", coBools); def->label = "Wipe while retracting"; def->tooltip = "This flag will move the nozzle while retracting to minimize the possible blob " "on leaky extruders."; def->cli = "wipe!"; def->default_value = new ConfigOptionBools { false }; def = this->add("wipe_tower", coBool); def->label = "Enable"; def->tooltip = "Multi material printers may need to prime or purge extruders on tool changes. " "Extrude the excess material into the wipe tower."; def->cli = "wipe-tower!"; def->default_value = new ConfigOptionBool(false); def = this->add("wipe_tower_x", coFloat); def->label = "Position X"; def->tooltip = "X coordinate of the left front corner of a wipe tower"; def->sidetext = "mm"; def->cli = "wipe-tower-x=f"; def->default_value = new ConfigOptionFloat(180.); def = this->add("wipe_tower_y", coFloat); def->label = "Position Y"; def->tooltip = "Y coordinate of the left front corner of a wipe tower"; def->sidetext = "mm"; def->cli = "wipe-tower-y=f"; def->default_value = new ConfigOptionFloat(140.); def = this->add("wipe_tower_width", coFloat); def->label = "Width"; def->tooltip = "Width of a wipe tower"; def->sidetext = "mm"; def->cli = "wipe-tower-width=f"; def->default_value = new ConfigOptionFloat(60.); def = this->add("wipe_tower_per_color_wipe", coFloat); def->label = "Per color change depth"; def->tooltip = "Depth of a wipe color per color change. For N colors, there will be " "maximum (N-1) tool switches performed, therefore the total depth " "of the wipe tower will be (N-1) times this value."; def->sidetext = "mm"; def->cli = "wipe-tower-per-color-wipe=f"; def->default_value = new ConfigOptionFloat(15.); def = this->add("xy_size_compensation", coFloat); def->label = "XY Size Compensation"; def->category = "Advanced"; def->tooltip = "The object will be grown/shrunk in the XY plane by the configured value " "(negative = inwards, positive = outwards). This might be useful " "for fine-tuning hole sizes."; def->sidetext = "mm"; def->cli = "xy-size-compensation=f"; def->default_value = new ConfigOptionFloat(0); def = this->add("z_offset", coFloat); def->label = "Z offset"; def->tooltip = "This value will be added (or subtracted) from all the Z coordinates " "in the output G-code. It is used to compensate for bad Z endstop position: " "for example, if your endstop zero actually leaves the nozzle 0.3mm far " "from the print bed, set this to -0.3 (or fix your endstop)."; def->sidetext = "mm"; def->cli = "z-offset=f"; def->default_value = new ConfigOptionFloat(0); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) { // handle legacy options if (opt_key == "extrusion_width_ratio" || opt_key == "bottom_layer_speed_ratio" || opt_key == "first_layer_height_ratio") { boost::replace_first(opt_key, "_ratio", ""); if (opt_key == "bottom_layer_speed") opt_key = "first_layer_speed"; try { float v = boost::lexical_cast(value); if (v != 0) value = boost::lexical_cast(v*100) + "%"; } catch (boost::bad_lexical_cast &) { value = "0"; } } else if (opt_key == "gcode_flavor" && value == "makerbot") { value = "makerware"; } else if (opt_key == "fill_density" && value.find("%") == std::string::npos) { try { // fill_density was turned into a percent value float v = boost::lexical_cast(value); value = boost::lexical_cast(v*100) + "%"; } catch (boost::bad_lexical_cast &) {} } else if (opt_key == "randomize_start" && value == "1") { opt_key = "seam_position"; value = "random"; } else if (opt_key == "bed_size" && !value.empty()) { opt_key = "bed_shape"; ConfigOptionPoint p; p.deserialize(value); std::ostringstream oss; oss << "0x0," << p.value.x << "x0," << p.value.x << "x" << p.value.y << ",0x" << p.value.y; value = oss.str(); } else if (opt_key == "octoprint_host" && !value.empty()) { opt_key = "print_host"; } else if ((opt_key == "perimeter_acceleration" && value == "25") || (opt_key == "infill_acceleration" && value == "50")) { /* For historical reasons, the world's full of configs having these very low values; to avoid unexpected behavior we need to ignore them. Banning these two hard-coded values is a dirty hack and will need to be removed sometime in the future, but it will avoid lots of complaints for now. */ value = "0"; } else if (opt_key == "support_material_threshold" && value == "0") { // 0 used to be automatic threshold, but we introduced percent values so let's // transform it into the default value value = "60%"; } // Ignore the following obsolete configuration keys: static std::set ignore = { "duplicate_x", "duplicate_y", "gcode_arcs", "multiply_x", "multiply_y", "support_material_tool", "acceleration", "adjust_overhang_flow", "standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid", "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", "seal_position", "vibration_limit", "bed_size", "octoprint_host", "print_center", "g0", "threads", "pressure_advance" }; if (ignore.find(opt_key) != ignore.end()) { opt_key = ""; return; } if (! print_config_def.has(opt_key)) { //printf("Unknown option %s\n", opt_key.c_str()); opt_key = ""; return; } } PrintConfigDef print_config_def; DynamicPrintConfig* DynamicPrintConfig::new_from_defaults() { return new_from_defaults_keys(FullPrintConfig::defaults().keys()); } DynamicPrintConfig* DynamicPrintConfig::new_from_defaults_keys(const std::vector &keys) { auto *out = new DynamicPrintConfig(); out->apply_only(FullPrintConfig::defaults(), keys); return out; } void DynamicPrintConfig::normalize() { if (this->has("extruder")) { int extruder = this->option("extruder")->getInt(); this->erase("extruder"); if (extruder != 0) { if (!this->has("infill_extruder")) this->option("infill_extruder", true)->setInt(extruder); if (!this->has("perimeter_extruder")) this->option("perimeter_extruder", true)->setInt(extruder); // Don't propagate the current extruder to support. // For non-soluble supports, the default "0" extruder means to use the active extruder, // for soluble supports one certainly does not want to set the extruder to non-soluble. // if (!this->has("support_material_extruder")) // this->option("support_material_extruder", true)->setInt(extruder); // if (!this->has("support_material_interface_extruder")) // this->option("support_material_interface_extruder", true)->setInt(extruder); } } if (!this->has("solid_infill_extruder") && this->has("infill_extruder")) this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt()); if (this->has("spiral_vase") && this->opt("spiral_vase", true)->value) { { // this should be actually done only on the spiral layers instead of all ConfigOptionBools* opt = this->opt("retract_layer_change", true); opt->values.assign(opt->values.size(), false); // set all values to false } { this->opt("perimeters", true)->value = 1; this->opt("top_solid_layers", true)->value = 0; this->opt("fill_density", true)->value = 0; } } } std::string DynamicPrintConfig::validate() { // Full print config is initialized from the defaults. FullPrintConfig fpc; fpc.apply(*this, true); // Verify this print options through the FullPrintConfig. return fpc.validate(); } double PrintConfig::min_object_distance() const { return PrintConfig::min_object_distance(static_cast(this)); } double PrintConfig::min_object_distance(const ConfigBase *config) { double extruder_clearance_radius = config->option("extruder_clearance_radius")->getFloat(); double duplicate_distance = config->option("duplicate_distance")->getFloat(); // min object distance is max(duplicate_distance, clearance_radius) return (config->option("complete_objects")->getBool() && extruder_clearance_radius > duplicate_distance) ? extruder_clearance_radius : duplicate_distance; } std::string FullPrintConfig::validate() { // --layer-height if (this->get_abs_value("layer_height") <= 0) return "Invalid value for --layer-height"; if (fabs(fmod(this->get_abs_value("layer_height"), SCALING_FACTOR)) > 1e-4) return "--layer-height must be a multiple of print resolution"; // --first-layer-height if (this->get_abs_value("first_layer_height") <= 0) return "Invalid value for --first-layer-height"; // --filament-diameter for (double fd : this->filament_diameter.values) if (fd < 1) return "Invalid value for --filament-diameter"; // --nozzle-diameter for (double nd : this->nozzle_diameter.values) if (nd < 0.005) return "Invalid value for --nozzle-diameter"; // --perimeters if (this->perimeters.value < 0) return "Invalid value for --perimeters"; // --solid-layers if (this->top_solid_layers < 0) return "Invalid value for --top-solid-layers"; if (this->bottom_solid_layers < 0) return "Invalid value for --bottom-solid-layers"; if (this->use_firmware_retraction.value && this->gcode_flavor.value != gcfSmoothie && this->gcode_flavor.value != gcfRepRap && this->gcode_flavor.value != gcfMarlin && this->gcode_flavor.value != gcfMachinekit && this->gcode_flavor.value != gcfRepetier) return "--use-firmware-retraction is only supported by Marlin, Smoothie, Repetier and Machinekit firmware"; if (this->use_firmware_retraction.value) for (bool wipe : this->wipe.values) if (wipe) return "--use-firmware-retraction is not compatible with --wipe"; // --gcode-flavor if (! print_config_def.get("gcode_flavor")->has_enum_value(this->gcode_flavor.serialize())) return "Invalid value for --gcode-flavor"; // --fill-pattern if (! print_config_def.get("fill_pattern")->has_enum_value(this->fill_pattern.serialize())) return "Invalid value for --fill-pattern"; // --external-fill-pattern if (! print_config_def.get("external_fill_pattern")->has_enum_value(this->external_fill_pattern.serialize())) return "Invalid value for --external-fill-pattern"; // --fill-density if (fabs(this->fill_density.value - 100.) < EPSILON && ! print_config_def.get("external_fill_pattern")->has_enum_value(this->fill_pattern.serialize())) return "The selected fill pattern is not supposed to work at 100% density"; // --infill-every-layers if (this->infill_every_layers < 1) return "Invalid value for --infill-every-layers"; // --skirt-height if (this->skirt_height < -1) // -1 means as tall as the object return "Invalid value for --skirt-height"; // --bridge-flow-ratio if (this->bridge_flow_ratio <= 0) return "Invalid value for --bridge-flow-ratio"; // extruder clearance if (this->extruder_clearance_radius <= 0) return "Invalid value for --extruder-clearance-radius"; if (this->extruder_clearance_height <= 0) return "Invalid value for --extruder-clearance-height"; // --extrusion-multiplier for (float em : this->extrusion_multiplier.values) if (em <= 0) return "Invalid value for --extrusion-multiplier"; // --default-acceleration if ((this->perimeter_acceleration != 0. || this->infill_acceleration != 0. || this->bridge_acceleration != 0. || this->first_layer_acceleration != 0.) && this->default_acceleration == 0.) return "Invalid zero value for --default-acceleration when using other acceleration settings"; // --spiral-vase if (this->spiral_vase) { // Note that we might want to have more than one perimeter on the bottom // solid layers. if (this->perimeters > 1) return "Can't make more than one perimeter when spiral vase mode is enabled"; else if (this->perimeters < 1) return "Can't make less than one perimeter when spiral vase mode is enabled"; if (this->fill_density > 0) return "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0"; if (this->top_solid_layers > 0) return "Spiral vase mode is not compatible with top solid layers"; if (this->support_material || this->support_material_enforce_layers > 0) return "Spiral vase mode is not compatible with support material"; } // extrusion widths { double max_nozzle_diameter = 0.; for (double dmr : this->nozzle_diameter.values) max_nozzle_diameter = std::max(max_nozzle_diameter, dmr); const char *widths[] = { "external_perimeter", "perimeter", "infill", "solid_infill", "top_infill", "support_material", "first_layer" }; for (size_t i = 0; i < sizeof(widths) / sizeof(widths[i]); ++ i) { std::string key(widths[i]); key += "_extrusion_width"; if (this->get_abs_value(key, max_nozzle_diameter) > 10. * max_nozzle_diameter) return std::string("Invalid extrusion width (too large): ") + key; } } // Out of range validation of numeric values. for (const std::string &opt_key : this->keys()) { const ConfigOption *opt = this->optptr(opt_key); assert(opt != nullptr); const ConfigOptionDef *optdef = print_config_def.get(opt_key); assert(optdef != nullptr); bool out_of_range = false; switch (opt->type()) { case coFloat: case coPercent: case coFloatOrPercent: { auto *fopt = static_cast(opt); out_of_range = fopt->value < optdef->min || fopt->value > optdef->max; break; } case coFloats: case coPercents: for (double v : static_cast(opt)->values) if (v < optdef->min || v > optdef->max) { out_of_range = true; break; } break; case coInt: { auto *iopt = static_cast(opt); out_of_range = iopt->value < optdef->min || iopt->value > optdef->max; break; } case coInts: for (int v : static_cast(opt)->values) if (v < optdef->min || v > optdef->max) { out_of_range = true; break; } break; default:; } if (out_of_range) return std::string("Value out of range: " + opt_key); } // The configuration is valid. return ""; } // Declare the static caches for each StaticPrintConfig derived class. StaticPrintConfig::StaticCache PrintObjectConfig::s_cache_PrintObjectConfig; StaticPrintConfig::StaticCache PrintRegionConfig::s_cache_PrintRegionConfig; StaticPrintConfig::StaticCache GCodeConfig::s_cache_GCodeConfig; StaticPrintConfig::StaticCache PrintConfig::s_cache_PrintConfig; StaticPrintConfig::StaticCache HostConfig::s_cache_HostConfig; StaticPrintConfig::StaticCache FullPrintConfig::s_cache_FullPrintConfig; } Slic3r-version_1.39.1/xs/src/libslic3r/PrintConfig.hpp000066400000000000000000000742171324354444700226000ustar00rootroot00000000000000// Configuration store of Slic3r. // // The configuration store is either static or dynamic. // DynamicPrintConfig is used mainly at the user interface. while the StaticPrintConfig is used // during the slicing and the g-code generation. // // The classes derived from StaticPrintConfig form a following hierarchy. // // FullPrintConfig // PrintObjectConfig // PrintRegionConfig // PrintConfig // GCodeConfig // HostConfig // #ifndef slic3r_PrintConfig_hpp_ #define slic3r_PrintConfig_hpp_ #include "libslic3r.h" #include "Config.hpp" namespace Slic3r { enum GCodeFlavor { gcfRepRap, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit, gcfSmoothie, gcfNoExtrusion, }; enum InfillPattern { ipRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, }; enum SupportMaterialPattern { smpRectilinear, smpRectilinearGrid, smpHoneycomb, smpPillars, }; enum SeamPosition { spRandom, spNearest, spAligned, spRear }; enum FilamentType { ftPLA, ftABS, ftPET, ftHIPS, ftFLEX, ftSCAFF, ftEDGE, ftNGEN, ftPVA }; template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["reprap"] = gcfRepRap; keys_map["repetier"] = gcfRepetier; keys_map["teacup"] = gcfTeacup; keys_map["makerware"] = gcfMakerWare; keys_map["marlin"] = gcfMarlin; keys_map["sailfish"] = gcfSailfish; keys_map["smoothie"] = gcfSmoothie; keys_map["mach3"] = gcfMach3; keys_map["machinekit"] = gcfMachinekit; keys_map["no-extrusion"] = gcfNoExtrusion; } return keys_map; } template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["rectilinear"] = ipRectilinear; keys_map["grid"] = ipGrid; keys_map["triangles"] = ipTriangles; keys_map["stars"] = ipStars; keys_map["cubic"] = ipCubic; keys_map["line"] = ipLine; keys_map["concentric"] = ipConcentric; keys_map["honeycomb"] = ipHoneycomb; keys_map["3dhoneycomb"] = ip3DHoneycomb; keys_map["hilbertcurve"] = ipHilbertCurve; keys_map["archimedeanchords"] = ipArchimedeanChords; keys_map["octagramspiral"] = ipOctagramSpiral; } return keys_map; } template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["rectilinear"] = smpRectilinear; keys_map["rectilinear-grid"] = smpRectilinearGrid; keys_map["honeycomb"] = smpHoneycomb; keys_map["pillars"] = smpPillars; } return keys_map; } template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["random"] = spRandom; keys_map["nearest"] = spNearest; keys_map["aligned"] = spAligned; keys_map["rear"] = spRear; } return keys_map; } template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { keys_map["PLA"] = ftPLA; keys_map["ABS"] = ftABS; keys_map["PET"] = ftPET; keys_map["HIPS"] = ftHIPS; keys_map["FLEX"] = ftFLEX; keys_map["SCAFF"] = ftSCAFF; keys_map["EDGE"] = ftEDGE; keys_map["NGEN"] = ftNGEN; keys_map["PVA"] = ftPVA; } return keys_map; } // Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs. // Does not store the actual values, but defines default values. class PrintConfigDef : public ConfigDef { public: PrintConfigDef(); static void handle_legacy(t_config_option_key &opt_key, std::string &value); }; // The one and only global definition of SLic3r configuration options. // This definition is constant. extern PrintConfigDef print_config_def; // Slic3r dynamic configuration, used to override the configuration // per object, per modification volume or per printing material. // The dynamic configuration is also used to store user modifications of the print global parameters, // so the modified configuration values may be diffed against the active configuration // to invalidate the proper slicing resp. g-code generation processing steps. // This object is mapped to Perl as Slic3r::Config. class DynamicPrintConfig : public DynamicConfig { public: DynamicPrintConfig() {} DynamicPrintConfig(const DynamicPrintConfig &other) : DynamicConfig(other) {} static DynamicPrintConfig* new_from_defaults(); static DynamicPrintConfig* new_from_defaults_keys(const std::vector &keys); // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. const ConfigDef* def() const override { return &print_config_def; } void normalize(); // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned. std::string validate(); }; template void normalize_and_apply_config(CONFIG &dst, const DynamicPrintConfig &src) { DynamicPrintConfig src_normalized(src); src_normalized.normalize(); dst.apply(src_normalized, true); } class StaticPrintConfig : public StaticConfig { public: StaticPrintConfig() {} // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. const ConfigDef* def() const override { return &print_config_def; } protected: // Verify whether the opt_key has not been obsoleted or renamed. // Both opt_key and value may be modified by handle_legacy(). // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). // handle_legacy() is called internally by set_deserialize(). void handle_legacy(t_config_option_key &opt_key, std::string &value) const override { PrintConfigDef::handle_legacy(opt_key, value); } // Internal class for keeping a dynamic map to static options. class StaticCacheBase { public: // To be called during the StaticCache setup. // Add one ConfigOption into m_map_name_to_offset. template void opt_add(const std::string &name, const char *base_ptr, const T &opt) { assert(m_map_name_to_offset.find(name) == m_map_name_to_offset.end()); m_map_name_to_offset[name] = (const char*)&opt - base_ptr; } protected: std::map m_map_name_to_offset; }; // Parametrized by the type of the topmost class owning the options. template class StaticCache : public StaticCacheBase { public: // Calling the constructor of m_defaults with 0 forces m_defaults to not run the initialization. StaticCache() : m_defaults(nullptr) {} ~StaticCache() { delete m_defaults; m_defaults = nullptr; } bool initialized() const { return ! m_keys.empty(); } ConfigOption* optptr(const std::string &name, T *owner) const { const auto it = m_map_name_to_offset.find(name); return (it == m_map_name_to_offset.end()) ? nullptr : reinterpret_cast((char*)owner + it->second); } const ConfigOption* optptr(const std::string &name, const T *owner) const { const auto it = m_map_name_to_offset.find(name); return (it == m_map_name_to_offset.end()) ? nullptr : reinterpret_cast((const char*)owner + it->second); } const std::vector& keys() const { return m_keys; } const T& defaults() const { return *m_defaults; } // To be called during the StaticCache setup. // Collect option keys from m_map_name_to_offset, // assign default values to m_defaults. void finalize(T *defaults, const ConfigDef *defs) { assert(defs != nullptr); m_defaults = defaults; m_keys.clear(); m_keys.reserve(m_map_name_to_offset.size()); for (const auto &kvp : defs->options) { // Find the option given the option name kvp.first by an offset from (char*)m_defaults. ConfigOption *opt = this->optptr(kvp.first, m_defaults); if (opt == nullptr) // This option is not defined by the ConfigBase of type T. continue; m_keys.emplace_back(kvp.first); const ConfigOptionDef *def = defs->get(kvp.first); assert(def != nullptr); if (def->default_value != nullptr) opt->set(def->default_value); } } private: T *m_defaults; std::vector m_keys; }; }; #define STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ public: \ /* Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. */ \ ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override \ { return s_cache_##CLASS_NAME.optptr(opt_key, this); } \ /* Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. */ \ t_config_option_keys keys() const override { return s_cache_##CLASS_NAME.keys(); } \ static const CLASS_NAME& defaults() { initialize_cache(); return s_cache_##CLASS_NAME.defaults(); } \ private: \ static void initialize_cache() \ { \ if (! s_cache_##CLASS_NAME.initialized()) { \ CLASS_NAME *inst = new CLASS_NAME(1); \ inst->initialize(s_cache_##CLASS_NAME, (const char*)inst); \ s_cache_##CLASS_NAME.finalize(inst, inst->def()); \ } \ } \ /* Cache object holding a key/option map, a list of option keys and a copy of this static config initialized with the defaults. */ \ static StaticPrintConfig::StaticCache s_cache_##CLASS_NAME; #define STATIC_PRINT_CONFIG_CACHE(CLASS_NAME) \ STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ public: \ /* Public default constructor will initialize the key/option cache and the default object copy if needed. */ \ CLASS_NAME() { initialize_cache(); *this = s_cache_##CLASS_NAME.defaults(); } \ protected: \ /* Protected constructor to be called when compounded. */ \ CLASS_NAME(int) {} #define STATIC_PRINT_CONFIG_CACHE_DERIVED(CLASS_NAME) \ STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ public: \ /* Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. */ \ const ConfigDef* def() const override { return &print_config_def; } \ /* Handle legacy and obsoleted config keys */ \ void handle_legacy(t_config_option_key &opt_key, std::string &value) const override \ { PrintConfigDef::handle_legacy(opt_key, value); } #define OPT_PTR(KEY) cache.opt_add(#KEY, base_ptr, this->KEY) // This object is mapped to Perl as Slic3r::Config::PrintObject. class PrintObjectConfig : public StaticPrintConfig { STATIC_PRINT_CONFIG_CACHE(PrintObjectConfig) public: ConfigOptionBool clip_multipart_objects; ConfigOptionBool dont_support_bridges; ConfigOptionFloat elefant_foot_compensation; ConfigOptionFloatOrPercent extrusion_width; ConfigOptionFloatOrPercent first_layer_height; ConfigOptionBool infill_only_where_needed; ConfigOptionBool interface_shells; ConfigOptionFloat layer_height; ConfigOptionInt raft_layers; ConfigOptionEnum seam_position; // ConfigOptionFloat seam_preferred_direction; // ConfigOptionFloat seam_preferred_direction_jitter; ConfigOptionBool support_material; ConfigOptionFloat support_material_angle; ConfigOptionBool support_material_buildplate_only; ConfigOptionFloat support_material_contact_distance; ConfigOptionInt support_material_enforce_layers; ConfigOptionInt support_material_extruder; ConfigOptionFloatOrPercent support_material_extrusion_width; ConfigOptionBool support_material_interface_contact_loops; ConfigOptionInt support_material_interface_extruder; ConfigOptionInt support_material_interface_layers; ConfigOptionFloat support_material_interface_spacing; ConfigOptionFloatOrPercent support_material_interface_speed; ConfigOptionEnum support_material_pattern; ConfigOptionFloat support_material_spacing; ConfigOptionFloat support_material_speed; ConfigOptionBool support_material_synchronize_layers; ConfigOptionInt support_material_threshold; ConfigOptionBool support_material_with_sheath; ConfigOptionFloatOrPercent support_material_xy_spacing; ConfigOptionFloat xy_size_compensation; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { OPT_PTR(clip_multipart_objects); OPT_PTR(dont_support_bridges); OPT_PTR(elefant_foot_compensation); OPT_PTR(extrusion_width); OPT_PTR(first_layer_height); OPT_PTR(infill_only_where_needed); OPT_PTR(interface_shells); OPT_PTR(layer_height); OPT_PTR(raft_layers); OPT_PTR(seam_position); // OPT_PTR(seam_preferred_direction); // OPT_PTR(seam_preferred_direction_jitter); OPT_PTR(support_material); OPT_PTR(support_material_angle); OPT_PTR(support_material_buildplate_only); OPT_PTR(support_material_contact_distance); OPT_PTR(support_material_enforce_layers); OPT_PTR(support_material_interface_contact_loops); OPT_PTR(support_material_extruder); OPT_PTR(support_material_extrusion_width); OPT_PTR(support_material_interface_extruder); OPT_PTR(support_material_interface_layers); OPT_PTR(support_material_interface_spacing); OPT_PTR(support_material_interface_speed); OPT_PTR(support_material_pattern); OPT_PTR(support_material_spacing); OPT_PTR(support_material_speed); OPT_PTR(support_material_synchronize_layers); OPT_PTR(support_material_xy_spacing); OPT_PTR(support_material_threshold); OPT_PTR(support_material_with_sheath); OPT_PTR(xy_size_compensation); } }; // This object is mapped to Perl as Slic3r::Config::PrintRegion. class PrintRegionConfig : public StaticPrintConfig { STATIC_PRINT_CONFIG_CACHE(PrintRegionConfig) public: ConfigOptionFloat bridge_angle; ConfigOptionInt bottom_solid_layers; ConfigOptionFloat bridge_flow_ratio; ConfigOptionFloat bridge_speed; ConfigOptionBool ensure_vertical_shell_thickness; ConfigOptionEnum external_fill_pattern; ConfigOptionFloatOrPercent external_perimeter_extrusion_width; ConfigOptionFloatOrPercent external_perimeter_speed; ConfigOptionBool external_perimeters_first; ConfigOptionBool extra_perimeters; ConfigOptionFloat fill_angle; ConfigOptionPercent fill_density; ConfigOptionEnum fill_pattern; ConfigOptionFloat gap_fill_speed; ConfigOptionInt infill_extruder; ConfigOptionFloatOrPercent infill_extrusion_width; ConfigOptionInt infill_every_layers; ConfigOptionFloatOrPercent infill_overlap; ConfigOptionFloat infill_speed; ConfigOptionBool overhangs; ConfigOptionInt perimeter_extruder; ConfigOptionFloatOrPercent perimeter_extrusion_width; ConfigOptionFloat perimeter_speed; ConfigOptionInt perimeters; ConfigOptionFloatOrPercent small_perimeter_speed; ConfigOptionFloat solid_infill_below_area; ConfigOptionInt solid_infill_extruder; ConfigOptionFloatOrPercent solid_infill_extrusion_width; ConfigOptionInt solid_infill_every_layers; ConfigOptionFloatOrPercent solid_infill_speed; ConfigOptionBool thin_walls; ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionInt top_solid_layers; ConfigOptionFloatOrPercent top_solid_infill_speed; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { OPT_PTR(bridge_angle); OPT_PTR(bottom_solid_layers); OPT_PTR(bridge_flow_ratio); OPT_PTR(bridge_speed); OPT_PTR(ensure_vertical_shell_thickness); OPT_PTR(external_fill_pattern); OPT_PTR(external_perimeter_extrusion_width); OPT_PTR(external_perimeter_speed); OPT_PTR(external_perimeters_first); OPT_PTR(extra_perimeters); OPT_PTR(fill_angle); OPT_PTR(fill_density); OPT_PTR(fill_pattern); OPT_PTR(gap_fill_speed); OPT_PTR(infill_extruder); OPT_PTR(infill_extrusion_width); OPT_PTR(infill_every_layers); OPT_PTR(infill_overlap); OPT_PTR(infill_speed); OPT_PTR(overhangs); OPT_PTR(perimeter_extruder); OPT_PTR(perimeter_extrusion_width); OPT_PTR(perimeter_speed); OPT_PTR(perimeters); OPT_PTR(small_perimeter_speed); OPT_PTR(solid_infill_below_area); OPT_PTR(solid_infill_extruder); OPT_PTR(solid_infill_extrusion_width); OPT_PTR(solid_infill_every_layers); OPT_PTR(solid_infill_speed); OPT_PTR(thin_walls); OPT_PTR(top_infill_extrusion_width); OPT_PTR(top_solid_infill_speed); OPT_PTR(top_solid_layers); } }; // This object is mapped to Perl as Slic3r::Config::GCode. class GCodeConfig : public StaticPrintConfig { STATIC_PRINT_CONFIG_CACHE(GCodeConfig) public: ConfigOptionString before_layer_gcode; ConfigOptionString between_objects_gcode; ConfigOptionFloats deretract_speed; ConfigOptionString end_gcode; ConfigOptionStrings end_filament_gcode; ConfigOptionString extrusion_axis; ConfigOptionFloats extrusion_multiplier; ConfigOptionFloats filament_diameter; ConfigOptionFloats filament_density; ConfigOptionStrings filament_type; ConfigOptionBools filament_soluble; ConfigOptionFloats filament_cost; ConfigOptionFloats filament_max_volumetric_speed; ConfigOptionBool gcode_comments; ConfigOptionEnum gcode_flavor; ConfigOptionString layer_gcode; ConfigOptionFloat max_print_speed; ConfigOptionFloat max_volumetric_speed; ConfigOptionFloat max_volumetric_extrusion_rate_slope_positive; ConfigOptionFloat max_volumetric_extrusion_rate_slope_negative; ConfigOptionPercents retract_before_wipe; ConfigOptionFloats retract_length; ConfigOptionFloats retract_length_toolchange; ConfigOptionFloats retract_lift; ConfigOptionFloats retract_lift_above; ConfigOptionFloats retract_lift_below; ConfigOptionFloats retract_restart_extra; ConfigOptionFloats retract_restart_extra_toolchange; ConfigOptionFloats retract_speed; ConfigOptionString start_gcode; ConfigOptionStrings start_filament_gcode; ConfigOptionBool single_extruder_multi_material; ConfigOptionString toolchange_gcode; ConfigOptionFloat travel_speed; ConfigOptionBool use_firmware_retraction; ConfigOptionBool use_relative_e_distances; ConfigOptionBool use_volumetric_e; ConfigOptionBool variable_layer_height; std::string get_extrusion_axis() const { return ((this->gcode_flavor.value == gcfMach3) || (this->gcode_flavor.value == gcfMachinekit)) ? "A" : (this->gcode_flavor.value == gcfNoExtrusion) ? "" : this->extrusion_axis.value; } protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { OPT_PTR(before_layer_gcode); OPT_PTR(between_objects_gcode); OPT_PTR(deretract_speed); OPT_PTR(end_gcode); OPT_PTR(end_filament_gcode); OPT_PTR(extrusion_axis); OPT_PTR(extrusion_multiplier); OPT_PTR(filament_diameter); OPT_PTR(filament_density); OPT_PTR(filament_type); OPT_PTR(filament_soluble); OPT_PTR(filament_cost); OPT_PTR(filament_max_volumetric_speed); OPT_PTR(gcode_comments); OPT_PTR(gcode_flavor); OPT_PTR(layer_gcode); OPT_PTR(max_print_speed); OPT_PTR(max_volumetric_speed); OPT_PTR(max_volumetric_extrusion_rate_slope_positive); OPT_PTR(max_volumetric_extrusion_rate_slope_negative); OPT_PTR(retract_before_wipe); OPT_PTR(retract_length); OPT_PTR(retract_length_toolchange); OPT_PTR(retract_lift); OPT_PTR(retract_lift_above); OPT_PTR(retract_lift_below); OPT_PTR(retract_restart_extra); OPT_PTR(retract_restart_extra_toolchange); OPT_PTR(retract_speed); OPT_PTR(single_extruder_multi_material); OPT_PTR(start_gcode); OPT_PTR(start_filament_gcode); OPT_PTR(toolchange_gcode); OPT_PTR(travel_speed); OPT_PTR(use_firmware_retraction); OPT_PTR(use_relative_e_distances); OPT_PTR(use_volumetric_e); OPT_PTR(variable_layer_height); } }; // This object is mapped to Perl as Slic3r::Config::Print. class PrintConfig : public GCodeConfig { STATIC_PRINT_CONFIG_CACHE_DERIVED(PrintConfig) PrintConfig() : GCodeConfig(0) { initialize_cache(); *this = s_cache_PrintConfig.defaults(); } public: double min_object_distance() const; static double min_object_distance(const ConfigBase *config); ConfigOptionBool avoid_crossing_perimeters; ConfigOptionPoints bed_shape; ConfigOptionInts bed_temperature; ConfigOptionFloat bridge_acceleration; ConfigOptionInts bridge_fan_speed; ConfigOptionFloat brim_width; ConfigOptionBool complete_objects; ConfigOptionBools cooling; ConfigOptionFloat default_acceleration; ConfigOptionInts disable_fan_first_layers; ConfigOptionFloat duplicate_distance; ConfigOptionFloat extruder_clearance_height; ConfigOptionFloat extruder_clearance_radius; ConfigOptionStrings extruder_colour; ConfigOptionPoints extruder_offset; ConfigOptionBools fan_always_on; ConfigOptionInts fan_below_layer_time; ConfigOptionStrings filament_colour; ConfigOptionStrings filament_notes; ConfigOptionFloat first_layer_acceleration; ConfigOptionInts first_layer_bed_temperature; ConfigOptionFloatOrPercent first_layer_extrusion_width; ConfigOptionFloatOrPercent first_layer_speed; ConfigOptionInts first_layer_temperature; ConfigOptionFloat infill_acceleration; ConfigOptionBool infill_first; ConfigOptionInts max_fan_speed; ConfigOptionFloats max_layer_height; ConfigOptionInts min_fan_speed; ConfigOptionFloats min_layer_height; ConfigOptionFloats min_print_speed; ConfigOptionFloat min_skirt_length; ConfigOptionString notes; ConfigOptionFloats nozzle_diameter; ConfigOptionBool only_retract_when_crossing_perimeters; ConfigOptionBool ooze_prevention; ConfigOptionString output_filename_format; ConfigOptionFloat perimeter_acceleration; ConfigOptionStrings post_process; ConfigOptionString printer_notes; ConfigOptionFloat resolution; ConfigOptionFloats retract_before_travel; ConfigOptionBools retract_layer_change; ConfigOptionFloat skirt_distance; ConfigOptionInt skirt_height; ConfigOptionInt skirts; ConfigOptionInts slowdown_below_layer_time; ConfigOptionBool spiral_vase; ConfigOptionInt standby_temperature_delta; ConfigOptionInts temperature; ConfigOptionInt threads; ConfigOptionBools wipe; ConfigOptionBool wipe_tower; ConfigOptionFloat wipe_tower_x; ConfigOptionFloat wipe_tower_y; ConfigOptionFloat wipe_tower_width; ConfigOptionFloat wipe_tower_per_color_wipe; ConfigOptionFloat z_offset; protected: PrintConfig(int) : GCodeConfig(1) {} void initialize(StaticCacheBase &cache, const char *base_ptr) { this->GCodeConfig::initialize(cache, base_ptr); OPT_PTR(avoid_crossing_perimeters); OPT_PTR(bed_shape); OPT_PTR(bed_temperature); OPT_PTR(bridge_acceleration); OPT_PTR(bridge_fan_speed); OPT_PTR(brim_width); OPT_PTR(complete_objects); OPT_PTR(cooling); OPT_PTR(default_acceleration); OPT_PTR(disable_fan_first_layers); OPT_PTR(duplicate_distance); OPT_PTR(extruder_clearance_height); OPT_PTR(extruder_clearance_radius); OPT_PTR(extruder_colour); OPT_PTR(extruder_offset); OPT_PTR(fan_always_on); OPT_PTR(fan_below_layer_time); OPT_PTR(filament_colour); OPT_PTR(filament_notes); OPT_PTR(first_layer_acceleration); OPT_PTR(first_layer_bed_temperature); OPT_PTR(first_layer_extrusion_width); OPT_PTR(first_layer_speed); OPT_PTR(first_layer_temperature); OPT_PTR(infill_acceleration); OPT_PTR(infill_first); OPT_PTR(max_fan_speed); OPT_PTR(max_layer_height); OPT_PTR(min_fan_speed); OPT_PTR(min_layer_height); OPT_PTR(min_print_speed); OPT_PTR(min_skirt_length); OPT_PTR(notes); OPT_PTR(nozzle_diameter); OPT_PTR(only_retract_when_crossing_perimeters); OPT_PTR(ooze_prevention); OPT_PTR(output_filename_format); OPT_PTR(perimeter_acceleration); OPT_PTR(post_process); OPT_PTR(printer_notes); OPT_PTR(resolution); OPT_PTR(retract_before_travel); OPT_PTR(retract_layer_change); OPT_PTR(skirt_distance); OPT_PTR(skirt_height); OPT_PTR(skirts); OPT_PTR(slowdown_below_layer_time); OPT_PTR(spiral_vase); OPT_PTR(standby_temperature_delta); OPT_PTR(temperature); OPT_PTR(threads); OPT_PTR(wipe); OPT_PTR(wipe_tower); OPT_PTR(wipe_tower_x); OPT_PTR(wipe_tower_y); OPT_PTR(wipe_tower_width); OPT_PTR(wipe_tower_per_color_wipe); OPT_PTR(z_offset); } }; class HostConfig : public StaticPrintConfig { STATIC_PRINT_CONFIG_CACHE(HostConfig) public: ConfigOptionString octoprint_host; ConfigOptionString octoprint_apikey; ConfigOptionString serial_port; ConfigOptionInt serial_speed; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { OPT_PTR(octoprint_host); OPT_PTR(octoprint_apikey); OPT_PTR(serial_port); OPT_PTR(serial_speed); } }; // This object is mapped to Perl as Slic3r::Config::Full. class FullPrintConfig : public PrintObjectConfig, public PrintRegionConfig, public PrintConfig, public HostConfig { STATIC_PRINT_CONFIG_CACHE_DERIVED(FullPrintConfig) FullPrintConfig() : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) { initialize_cache(); *this = s_cache_FullPrintConfig.defaults(); } public: // Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned. std::string validate(); protected: // Protected constructor to be called to initialize ConfigCache::m_default. FullPrintConfig(int) : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) {} void initialize(StaticCacheBase &cache, const char *base_ptr) { this->PrintObjectConfig::initialize(cache, base_ptr); this->PrintRegionConfig::initialize(cache, base_ptr); this->PrintConfig ::initialize(cache, base_ptr); this->HostConfig ::initialize(cache, base_ptr); } }; #undef STATIC_PRINT_CONFIG_CACHE #undef STATIC_PRINT_CONFIG_CACHE_BASE #undef STATIC_PRINT_CONFIG_CACHE_DERIVED #undef OPT_PTR } #endif Slic3r-version_1.39.1/xs/src/libslic3r/PrintObject.cpp000066400000000000000000003265041324354444700225730ustar00rootroot00000000000000#include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "SupportMaterial.hpp" #include "Surface.hpp" #include #include #include #include #include #include #include #ifdef SLIC3R_DEBUG_SLICE_PROCESSING #define SLIC3R_DEBUG #endif // #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG #ifdef SLIC3R_DEBUG #undef NDEBUG #define DEBUG #define _DEBUG #include "SVG.hpp" #undef assert #include #endif namespace Slic3r { PrintObject::PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox) : typed_slices(false), _print(print), _model_object(model_object), layer_height_profile_valid(false) { // Compute the translation to be applied to our meshes so that we work with smaller coordinates { // Translate meshes so that our toolpath generation algorithms work with smaller // XY coordinates; this translation is an optimization and not strictly required. // A cloned mesh will be aligned to 0 before slicing in _slice_region() since we // don't assume it's already aligned and we don't alter the original position in model. // We store the XY translation so that we can place copies correctly in the output G-code // (copies are expressed in G-code coordinates and this translation is not publicly exposed). this->_copies_shift = Point::new_scale(modobj_bbox.min.x, modobj_bbox.min.y); // Scale the object size and store it Pointf3 size = modobj_bbox.size(); this->size = Point3::new_scale(size.x, size.y, size.z); } this->reload_model_instances(); this->layer_height_ranges = model_object->layer_height_ranges; this->layer_height_profile = model_object->layer_height_profile; } bool PrintObject::add_copy(const Pointf &point) { Points points = this->_copies; points.push_back(Point::new_scale(point.x, point.y)); return this->set_copies(points); } bool PrintObject::delete_last_copy() { Points points = this->_copies; points.pop_back(); return this->set_copies(points); } bool PrintObject::set_copies(const Points &points) { this->_copies = points; // order copies with a nearest neighbor search and translate them by _copies_shift this->_shifted_copies.clear(); this->_shifted_copies.reserve(points.size()); // order copies with a nearest-neighbor search std::vector ordered_copies; Slic3r::Geometry::chained_path(points, ordered_copies); for (size_t point_idx : ordered_copies) { Point copy = points[point_idx]; copy.translate(this->_copies_shift); this->_shifted_copies.push_back(copy); } bool invalidated = this->_print->invalidate_step(psSkirt); invalidated |= this->_print->invalidate_step(psBrim); return invalidated; } bool PrintObject::reload_model_instances() { Points copies; copies.reserve(this->_model_object->instances.size()); for (const ModelInstance *mi : this->_model_object->instances) copies.emplace_back(Point::new_scale(mi->offset.x, mi->offset.y)); return this->set_copies(copies); } void PrintObject::clear_layers() { for (Layer *l : this->layers) delete l; this->layers.clear(); } Layer* PrintObject::add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z) { layers.push_back(new Layer(id, this, height, print_z, slice_z)); return layers.back(); } void PrintObject::clear_support_layers() { for (Layer *l : this->support_layers) delete l; this->support_layers.clear(); } SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t print_z) { support_layers.emplace_back(new SupportLayer(id, this, height, print_z, -1)); return support_layers.back(); } // Called by Print::apply_config(). // This method only accepts PrintObjectConfig and PrintRegionConfig option keys. bool PrintObject::invalidate_state_by_config_options(const std::vector &opt_keys) { if (opt_keys.empty()) return false; std::vector steps; bool invalidated = false; for (const t_config_option_key &opt_key : opt_keys) { if ( opt_key == "perimeters" || opt_key == "extra_perimeters" || opt_key == "gap_fill_speed" || opt_key == "overhangs" || opt_key == "first_layer_extrusion_width" || opt_key == "perimeter_extrusion_width" || opt_key == "infill_overlap" || opt_key == "thin_walls" || opt_key == "external_perimeters_first") { steps.emplace_back(posPerimeters); } else if ( opt_key == "layer_height" || opt_key == "first_layer_height" || opt_key == "raft_layers") { steps.emplace_back(posSlice); this->reset_layer_height_profile(); } else if ( opt_key == "clip_multipart_objects" || opt_key == "elefant_foot_compensation" || opt_key == "support_material_contact_distance" || opt_key == "xy_size_compensation") { steps.emplace_back(posSlice); } else if ( opt_key == "support_material" || opt_key == "support_material_angle" || opt_key == "support_material_buildplate_only" || opt_key == "support_material_enforce_layers" || opt_key == "support_material_extruder" || opt_key == "support_material_extrusion_width" || opt_key == "support_material_interface_layers" || opt_key == "support_material_interface_contact_loops" || opt_key == "support_material_interface_extruder" || opt_key == "support_material_interface_spacing" || opt_key == "support_material_pattern" || opt_key == "support_material_xy_spacing" || opt_key == "support_material_spacing" || opt_key == "support_material_synchronize_layers" || opt_key == "support_material_threshold" || opt_key == "support_material_with_sheath" || opt_key == "dont_support_bridges" || opt_key == "first_layer_extrusion_width") { steps.emplace_back(posSupportMaterial); } else if ( opt_key == "interface_shells" || opt_key == "infill_only_where_needed" || opt_key == "infill_every_layers" || opt_key == "solid_infill_every_layers" || opt_key == "bottom_solid_layers" || opt_key == "top_solid_layers" || opt_key == "solid_infill_below_area" || opt_key == "infill_extruder" || opt_key == "solid_infill_extruder" || opt_key == "infill_extrusion_width" || opt_key == "ensure_vertical_shell_thickness" || opt_key == "bridge_angle") { steps.emplace_back(posPrepareInfill); } else if ( opt_key == "external_fill_pattern" || opt_key == "external_fill_link_max_length" || opt_key == "fill_angle" || opt_key == "fill_pattern" || opt_key == "fill_link_max_length" || opt_key == "top_infill_extrusion_width" || opt_key == "first_layer_extrusion_width") { steps.emplace_back(posInfill); } else if ( opt_key == "fill_density" || opt_key == "solid_infill_extrusion_width") { steps.emplace_back(posPerimeters); steps.emplace_back(posPrepareInfill); } else if ( opt_key == "external_perimeter_extrusion_width" || opt_key == "perimeter_extruder") { steps.emplace_back(posPerimeters); steps.emplace_back(posSupportMaterial); } else if (opt_key == "bridge_flow_ratio") { steps.emplace_back(posPerimeters); steps.emplace_back(posInfill); } else if ( opt_key == "seam_position" || opt_key == "seam_preferred_direction" || opt_key == "seam_preferred_direction_jitter" || opt_key == "support_material_speed" || opt_key == "support_material_interface_speed" || opt_key == "bridge_speed" || opt_key == "external_perimeter_speed" || opt_key == "infill_speed" || opt_key == "perimeter_speed" || opt_key == "small_perimeter_speed" || opt_key == "solid_infill_speed" || opt_key == "top_solid_infill_speed") { // these options only affect G-code export, so nothing to invalidate } else { // for legacy, if we can't handle this option let's invalidate all steps this->reset_layer_height_profile(); this->invalidate_all_steps(); invalidated = true; } } sort_remove_duplicates(steps); for (PrintObjectStep step : steps) invalidated |= this->invalidate_step(step); return invalidated; } bool PrintObject::invalidate_step(PrintObjectStep step) { bool invalidated = this->state.invalidate(step); // propagate to dependent steps if (step == posPerimeters) { invalidated |= this->invalidate_step(posPrepareInfill); invalidated |= this->_print->invalidate_step(psSkirt); invalidated |= this->_print->invalidate_step(psBrim); } else if (step == posPrepareInfill) { invalidated |= this->invalidate_step(posInfill); } else if (step == posInfill) { invalidated |= this->_print->invalidate_step(psSkirt); invalidated |= this->_print->invalidate_step(psBrim); } else if (step == posSlice) { invalidated |= this->invalidate_step(posPerimeters); invalidated |= this->invalidate_step(posSupportMaterial); invalidated |= this->_print->invalidate_step(psWipeTower); } else if (step == posSupportMaterial) { invalidated |= this->_print->invalidate_step(psSkirt); invalidated |= this->_print->invalidate_step(psBrim); } // Wipe tower depends on the ordering of extruders, which in turn depends on everything. invalidated |= this->_print->invalidate_step(psWipeTower); return invalidated; } bool PrintObject::has_support_material() const { return this->config.support_material || this->config.raft_layers > 0 || this->config.support_material_enforce_layers > 0; } void PrintObject::_prepare_infill() { // This will assign a type (top/bottom/internal) to $layerm->slices. // Then the classifcation of $layerm->slices is transfered onto // the $layerm->fill_surfaces by clipping $layerm->fill_surfaces // by the cummulative area of the previous $layerm->fill_surfaces. this->detect_surfaces_type(); // Decide what surfaces are to be filled. // Here the S_TYPE_TOP / S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is turned to just S_TYPE_INTERNAL if zero top / bottom infill layers are configured. // Also tiny S_TYPE_INTERNAL surfaces are turned to S_TYPE_INTERNAL_SOLID. BOOST_LOG_TRIVIAL(info) << "Preparing fill surfaces..."; for (auto *layer : this->layers) for (auto *region : layer->regions) region->prepare_fill_surfaces(); // this will detect bridges and reverse bridges // and rearrange top/bottom/internal surfaces // It produces enlarged overlapping bridging areas. // // 1) S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is grown by 3mm and clipped by the total infill area. Bridges are detected. The areas may overlap. // 2) S_TYPE_TOP is grown by 3mm and clipped by the grown bottom areas. The areas may overlap. // 3) Clip the internal surfaces by the grown top/bottom surfaces. // 4) Merge surfaces with the same style. This will mostly get rid of the overlaps. //FIXME This does not likely merge surfaces, which are supported by a material with different colors, but same properties. this->process_external_surfaces(); // Add solid fills to ensure the shell vertical thickness. this->discover_vertical_shells(); // Debugging output. #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) { for (const Layer *layer : this->layers) { LayerRegion *layerm = layer->regions[region_id]; layerm->export_region_slices_to_svg_debug("6_discover_vertical_shells-final"); layerm->export_region_fill_surfaces_to_svg_debug("6_discover_vertical_shells-final"); } // for each layer } // for each region #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Detect, which fill surfaces are near external layers. // They will be split in internal and internal-solid surfaces. // The purpose is to add a configurable number of solid layers to support the TOP surfaces // and to add a configurable number of solid layers above the BOTTOM / BOTTOMBRIDGE surfaces // to close these surfaces reliably. //FIXME Vojtech: Is this a good place to add supporting infills below sloping perimeters? this->discover_horizontal_shells(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) { for (const Layer *layer : this->layers) { LayerRegion *layerm = layer->regions[region_id]; layerm->export_region_slices_to_svg_debug("7_discover_horizontal_shells-final"); layerm->export_region_fill_surfaces_to_svg_debug("7_discover_horizontal_shells-final"); } // for each layer } // for each region #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Only active if config->infill_only_where_needed. This step trims the sparse infill, // so it acts as an internal support. It maintains all other infill types intact. // Here the internal surfaces and perimeters have to be supported by the sparse infill. //FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support. // Likely the sparse infill will not be anchored correctly, so it will not work as intended. // Also one wishes the perimeters to be supported by a full infill. this->clip_fill_surfaces(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) { for (const Layer *layer : this->layers) { LayerRegion *layerm = layer->regions[region_id]; layerm->export_region_slices_to_svg_debug("8_clip_surfaces-final"); layerm->export_region_fill_surfaces_to_svg_debug("8_clip_surfaces-final"); } // for each layer } // for each region #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // the following step needs to be done before combination because it may need // to remove only half of the combined infill this->bridge_over_infill(); // combine fill surfaces to honor the "infill every N layers" option this->combine_infill(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) { for (const Layer *layer : this->layers) { LayerRegion *layerm = layer->regions[region_id]; layerm->export_region_slices_to_svg_debug("9_prepare_infill-final"); layerm->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final"); } // for each layer } // for each region for (const Layer *layer : this->layers) { layer->export_region_slices_to_svg_debug("9_prepare_infill-final"); layer->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final"); } // for each layer #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } // This function analyzes slices of a region (SurfaceCollection slices). // Each region slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface. // Initially all slices are of type stInternal. // Slices are compared against the top / bottom slices and regions and classified to the following groups: // stTop - Part of a region, which is not covered by any upper layer. This surface will be filled with a top solid infill. // stBottomBridge - Part of a region, which is not fully supported, but it hangs in the air, or it hangs losely on a support or a raft. // stBottom - Part of a region, which is not supported by the same region, but it is supported either by another region, or by a soluble interface layer. // stInternal - Part of a region, which is supported by the same region type. // If a part of a region is of stBottom and stTop, the stBottom wins. void PrintObject::detect_surfaces_type() { BOOST_LOG_TRIVIAL(info) << "Detecting solid surfaces..."; // Interface shells: the intersecting parts are treated as self standing objects supporting each other. // Each of the objects will have a full number of top / bottom layers, even if these top / bottom layers // are completely hidden inside a collective body of intersecting parts. // This is useful if one of the parts is to be dissolved, or if it is transparent and the internal shells // should be visible. bool interface_shells = this->config.interface_shells.value; for (int idx_region = 0; idx_region < this->_print->regions.size(); ++ idx_region) { BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " in parallel - start"; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (Layer *layer : this->layers) layer->regions[idx_region]->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // If interface shells are allowed, the region->surfaces cannot be overwritten as they may be used by other threads. // Cache the result of the following parallel_loop. std::vector surfaces_new; if (interface_shells) surfaces_new.assign(this->layers.size(), Surfaces()); tbb::parallel_for( tbb::blocked_range(0, this->layers.size()), [this, idx_region, interface_shells, &surfaces_new](const tbb::blocked_range& range) { // If we have raft layers, consider bottom layer as a bridge just like any other bottom surface lying on the void. SurfaceType surface_type_bottom_1st = (this->config.raft_layers.value > 0 && this->config.support_material_contact_distance.value > 0) ? stBottomBridge : stBottom; // If we have soluble support material, don't bridge. The overhang will be squished against a soluble layer separating // the support from the print. SurfaceType surface_type_bottom_other = (this->config.support_material.value && this->config.support_material_contact_distance.value == 0) ? stBottom : stBottomBridge; for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { // BOOST_LOG_TRIVIAL(trace) << "Detecting solid surfaces for region " << idx_region << " and layer " << layer->print_z; Layer *layer = this->layers[idx_layer]; LayerRegion *layerm = layer->get_region(idx_region); // comparison happens against the *full* slices (considering all regions) // unless internal shells are requested Layer *upper_layer = (idx_layer + 1 < this->layer_count()) ? this->layers[idx_layer + 1] : nullptr; Layer *lower_layer = (idx_layer > 0) ? this->layers[idx_layer - 1] : nullptr; // collapse very narrow parts (using the safety offset in the diff is not enough) float offset = layerm->flow(frExternalPerimeter).scaled_width() / 10.f; Polygons layerm_slices_surfaces = to_polygons(layerm->slices.surfaces); // find top surfaces (difference between current surfaces // of current layer and upper one) Surfaces top; if (upper_layer) { Polygons upper_slices = interface_shells ? to_polygons(upper_layer->get_region(idx_region)->slices.surfaces) : to_polygons(upper_layer->slices); surfaces_append(top, //FIXME implement offset2_ex working over ExPolygons, that should be a bit more efficient than calling offset_ex twice. offset_ex(offset_ex(diff_ex(layerm_slices_surfaces, upper_slices, true), -offset), offset), stTop); } else { // if no upper layer, all surfaces of this one are solid // we clone surfaces because we're going to clear the slices collection top = layerm->slices.surfaces; for (Surface &surface : top) surface.surface_type = stTop; } // Find bottom surfaces (difference between current surfaces of current layer and lower one). Surfaces bottom; if (lower_layer) { #if 0 //FIXME Why is this branch failing t\multi.t ? Polygons lower_slices = interface_shells ? to_polygons(lower_layer->get_region(idx_region)->slices.surfaces) : to_polygons(lower_layer->slices); surfaces_append(bottom, offset2_ex(diff(layerm_slices_surfaces, lower_slices, true), -offset, offset), surface_type_bottom_other); #else // Any surface lying on the void is a true bottom bridge (an overhang) surfaces_append( bottom, offset2_ex( diff(layerm_slices_surfaces, to_polygons(lower_layer->slices), true), -offset, offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces // lying on other slices not belonging to this region if (interface_shells) { // non-bridging bottom surfaces: any part of this layer lying // on something else, excluding those lying on our own region surfaces_append( bottom, offset2_ex( diff( intersection(layerm_slices_surfaces, to_polygons(lower_layer->slices)), // supported to_polygons(lower_layer->get_region(idx_region)->slices.surfaces), true), -offset, offset), stBottom); } #endif } else { // if no lower layer, all surfaces of this one are solid // we clone surfaces because we're going to clear the slices collection bottom = layerm->slices.surfaces; for (Surface &surface : bottom) surface.surface_type = surface_type_bottom_1st; } // now, if the object contained a thin membrane, we could have overlapping bottom // and top surfaces; let's do an intersection to discover them and consider them // as bottom surfaces (to allow for bridge detection) if (! top.empty() && ! bottom.empty()) { // Polygons overlapping = intersection(to_polygons(top), to_polygons(bottom)); // Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->layer->id, scalar(@$overlapping) // if $Slic3r::debug; Polygons top_polygons = to_polygons(std::move(top)); top.clear(); surfaces_append(top, diff_ex(top_polygons, to_polygons(bottom), false), stTop); } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { static int iRun = 0; std::vector> expolygons_with_attributes; expolygons_with_attributes.emplace_back(std::make_pair(union_ex(top), SVG::ExPolygonAttributes("green"))); expolygons_with_attributes.emplace_back(std::make_pair(union_ex(bottom), SVG::ExPolygonAttributes("brown"))); expolygons_with_attributes.emplace_back(std::make_pair(to_expolygons(layerm->slices.surfaces), SVG::ExPolygonAttributes("black"))); SVG::export_expolygons(debug_out_path("1_detect_surfaces_type_%d_region%d-layer_%f.svg", iRun ++, idx_region, layer->print_z).c_str(), expolygons_with_attributes); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // save surfaces to layer Surfaces &surfaces_out = interface_shells ? surfaces_new[idx_layer] : layerm->slices.surfaces; surfaces_out.clear(); // find internal surfaces (difference between top/bottom surfaces and others) { Polygons topbottom = to_polygons(top); polygons_append(topbottom, to_polygons(bottom)); surfaces_append(surfaces_out, diff_ex(layerm_slices_surfaces, topbottom, false), stInternal); } surfaces_append(surfaces_out, std::move(top)); surfaces_append(surfaces_out, std::move(bottom)); // Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", // $layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_slices_to_svg_debug("detect_surfaces_type-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } } ); // for each layer of a region if (interface_shells) { // Move surfaces_new to layerm->slices.surfaces for (size_t idx_layer = 0; idx_layer < this->layers.size(); ++ idx_layer) this->layers[idx_layer]->get_region(idx_region)->slices.surfaces = std::move(surfaces_new[idx_layer]); } BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " - clipping in parallel - start"; // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. tbb::parallel_for( tbb::blocked_range(0, this->layers.size()), [this, idx_region, interface_shells, &surfaces_new](const tbb::blocked_range& range) { for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { LayerRegion *layerm = this->layers[idx_layer]->get_region(idx_region); layerm->slices_to_fill_surfaces_clipped(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-final"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } // for each layer of a region }); BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " - clipping in parallel - end"; } // for each this->print->region_count // Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.) this->typed_slices = true; } void PrintObject::process_external_surfaces() { BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..."; FOREACH_REGION(this->_print, region) { int region_id = int(region - this->_print->regions.begin()); BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, this->layers.size()), [this, region_id](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { // BOOST_LOG_TRIVIAL(trace) << "Processing external surface, layer" << this->layers[layer_idx]->print_z; this->layers[layer_idx]->get_region(region_id)->process_external_surfaces((layer_idx == 0) ? NULL : this->layers[layer_idx - 1]); } } ); BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end"; } } void PrintObject::discover_vertical_shells() { PROFILE_FUNC(); BOOST_LOG_TRIVIAL(info) << "Discovering vertical shells..."; struct DiscoverVerticalShellsCacheEntry { // Collected polygons, offsetted Polygons top_surfaces; Polygons bottom_surfaces; Polygons holes; }; std::vector cache_top_botom_regions(this->layers.size(), DiscoverVerticalShellsCacheEntry()); bool top_bottom_surfaces_all_regions = this->_print->regions.size() > 1 && ! this->config.interface_shells.value; if (top_bottom_surfaces_all_regions) { // This is a multi-material print and interface_shells are disabled, meaning that the vertical shell thickness // is calculated over all materials. // Is the "ensure vertical wall thickness" applicable to any region? bool has_extra_layers = false; for (size_t idx_region = 0; idx_region < this->_print->regions.size(); ++ idx_region) { const PrintRegion ®ion = *this->_print->get_region(idx_region); if (region.config.ensure_vertical_shell_thickness.value && (region.config.top_solid_layers.value > 1 || region.config.bottom_solid_layers.value > 1)) { has_extra_layers = true; } } if (! has_extra_layers) // The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit. return; BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom"; //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(this->layers.size() / 16, size_t(1)); tbb::parallel_for( tbb::blocked_range(0, this->layers.size(), grain_size), [this, &cache_top_botom_regions](const tbb::blocked_range& range) { const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; const size_t num_regions = this->_print->regions.size(); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { const Layer &layer = *this->layers[idx_layer]; DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer]; // Simulate single set of perimeters over all merged regions. float perimeter_offset = 0.f; float perimeter_min_spacing = FLT_MAX; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING static size_t debug_idx = 0; ++ debug_idx; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ for (size_t idx_region = 0; idx_region < num_regions; ++ idx_region) { LayerRegion &layerm = *layer.regions[idx_region]; float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; // Top surfaces. append(cache.top_surfaces, offset(to_expolygons(layerm.slices.filter_by_type(stTop)), min_perimeter_infill_spacing)); append(cache.top_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stTop)), min_perimeter_infill_spacing)); // Bottom surfaces. append(cache.bottom_surfaces, offset(to_expolygons(layerm.slices.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing)); append(cache.bottom_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing)); // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only. // First find the maxium number of perimeters per region slice. unsigned int perimeters = 0; for (Surface &s : layerm.slices.surfaces) perimeters = std::max(perimeters, s.extra_perimeters); perimeters += layerm.region()->config.perimeters.value; // Then calculate the infill offset. if (perimeters > 0) { Flow extflow = layerm.flow(frExternalPerimeter); Flow flow = layerm.flow(frPerimeter); perimeter_offset = std::max(perimeter_offset, 0.5f * float(extflow.scaled_width() + extflow.scaled_spacing()) + (float(perimeters) - 1.f) * flow.scaled_spacing()); perimeter_min_spacing = std::min(perimeter_min_spacing, float(std::min(extflow.scaled_spacing(), flow.scaled_spacing()))); } polygons_append(cache.holes, to_polygons(layerm.fill_expolygons)); } // Save some computing time by reducing the number of polygons. cache.top_surfaces = union_(cache.top_surfaces, false); cache.bottom_surfaces = union_(cache.bottom_surfaces, false); // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. if (perimeter_offset > 0.) { // The layer.slices are forced to merge by expanding them first. polygons_append(cache.holes, offset(offset_ex(layer.slices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.slices.expolygons)); svg.draw(layer.slices.expolygons, "blue"); svg.draw(union_ex(cache.holes), "red"); svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05)); svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } cache.holes = union_(cache.holes, false); } }); BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom"; } for (size_t idx_region = 0; idx_region < this->_print->regions.size(); ++ idx_region) { PROFILE_BLOCK(discover_vertical_shells_region); const PrintRegion ®ion = *this->_print->get_region(idx_region); if (! region.config.ensure_vertical_shell_thickness.value) // This region will be handled by discover_horizontal_shells(). continue; int n_extra_top_layers = std::max(0, region.config.top_solid_layers.value - 1); int n_extra_bottom_layers = std::max(0, region.config.bottom_solid_layers.value - 1); if (n_extra_top_layers + n_extra_bottom_layers == 0) // Zero or 1 layer, there is no additional vertical wall thickness enforced. continue; //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(this->layers.size() / 16, size_t(1)); if (! top_bottom_surfaces_all_regions) { // This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness // is calculated over a single material. BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : cache top / bottom"; tbb::parallel_for( tbb::blocked_range(0, this->layers.size(), grain_size), [this, idx_region, &cache_top_botom_regions](const tbb::blocked_range& range) { const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { Layer &layer = *this->layers[idx_layer]; LayerRegion &layerm = *layer.regions[idx_region]; float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f; // Top surfaces. auto &cache = cache_top_botom_regions[idx_layer]; cache.top_surfaces = offset(to_expolygons(layerm.slices.filter_by_type(stTop)), min_perimeter_infill_spacing); append(cache.top_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stTop)), min_perimeter_infill_spacing)); // Bottom surfaces. cache.bottom_surfaces = offset(to_expolygons(layerm.slices.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing); append(cache.bottom_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing)); // Holes over all regions. Only collect them once, they are valid for all idx_region iterations. if (cache.holes.empty()) { for (size_t idx_region = 0; idx_region < layer.regions.size(); ++ idx_region) polygons_append(cache.holes, to_polygons(layer.regions[idx_region]->fill_expolygons)); } } }); BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - end : cache top / bottom"; } BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : ensure vertical wall thickness"; tbb::parallel_for( tbb::blocked_range(0, this->layers.size(), grain_size), [this, idx_region, n_extra_top_layers, n_extra_bottom_layers, &cache_top_botom_regions] (const tbb::blocked_range& range) { // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { PROFILE_BLOCK(discover_vertical_shells_region_layer); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING static size_t debug_idx = 0; ++ debug_idx; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ Layer *layer = this->layers[idx_layer]; LayerRegion *layerm = layer->regions[idx_region]; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-initial"); layerm->export_region_fill_surfaces_to_svg_debug("4_discover_vertical_shells-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ Flow solid_infill_flow = layerm->flow(frSolidInfill); coord_t infill_line_spacing = solid_infill_flow.scaled_spacing(); // Find a union of perimeters below / above this surface to guarantee a minimum shell thickness. Polygons shell; Polygons holes; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING ExPolygons shell_ex; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f; { PROFILE_BLOCK(discover_vertical_shells_region_layer_collect); #if 0 // #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box()); for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) { if (n < 0 || n >= (int)this->layers.size()) continue; ExPolygons &expolys = this->layers[n]->perimeter_expolygons; for (size_t i = 0; i < expolys.size(); ++ i) { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i])); svg.draw(expolys[i]); svg.draw_outline(expolys[i].contour, "black", scale_(0.05)); svg.draw_outline(expolys[i].holes, "blue", scale_(0.05)); svg.Close(); svg_cummulative.draw(expolys[i]); svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05)); svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05)); } } } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Reset the top / bottom inflated regions caches of entries, which are out of the moving window. bool hole_first = true; for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) if (n >= 0 && n < (int)this->layers.size()) { Layer &neighbor_layer = *this->layers[n]; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[n]; if (hole_first) { hole_first = false; polygons_append(holes, cache.holes); } else if (! holes.empty()) { holes = intersection(holes, cache.holes); } size_t n_shell_old = shell.size(); if (n > int(idx_layer)) // Collect top surfaces. polygons_append(shell, cache.top_surfaces); else if (n < int(idx_layer)) // Collect bottom and bottom bridge surfaces. polygons_append(shell, cache.bottom_surfaces); // Running the union_ using the Clipper library piece by piece is cheaper // than running the union_ all at once. if (n_shell_old < shell.size()) shell = union_(shell, false); } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell)); svg.draw(shell); svg.draw_outline(shell, "black", scale_(0.05)); svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #if 0 { PROFILE_BLOCK(discover_vertical_shells_region_layer_shell_); // shell = union_(shell, true); shell = union_(shell, false); } #endif #ifdef SLIC3R_DEBUG_SLICE_PROCESSING shell_ex = union_ex(shell, true); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } //if (shell.empty()) // continue; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-after-union-%d.svg", debug_idx), get_extents(shell)); svg.draw(shell_ex); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internal-wshell-%d.svg", debug_idx), get_extents(shell)); svg.draw(layerm->fill_surfaces.filter_by_type(stInternal), "yellow", 0.5); svg.draw_outline(layerm->fill_surfaces.filter_by_type(stInternal), "black", "blue", scale_(0.05)); svg.draw(shell_ex, "blue", 0.5); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); } { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", debug_idx), get_extents(shell)); svg.draw(layerm->fill_surfaces.filter_by_type(stInternalVoid), "yellow", 0.5); svg.draw_outline(layerm->fill_surfaces.filter_by_type(stInternalVoid), "black", "blue", scale_(0.05)); svg.draw(shell_ex, "blue", 0.5); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); } { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", debug_idx), get_extents(shell)); svg.draw(layerm->fill_surfaces.filter_by_type(stInternalVoid), "yellow", 0.5); svg.draw_outline(layerm->fill_surfaces.filter_by_type(stInternalVoid), "black", "blue", scale_(0.05)); svg.draw(shell_ex, "blue", 0.5); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the shells region by the internal & internal void surfaces. const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid, stInternalSolid }; const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 3)); shell = intersection(shell, polygonsInternal, true); polygons_append(shell, diff(polygonsInternal, holes)); if (shell.empty()) continue; // Append the internal solids, so they will be merged with the new ones. polygons_append(shell, to_polygons(layerm->fill_surfaces.filter_by_type(stInternalSolid))); // These regions will be filled by a rectilinear full infill. Currently this type of infill // only fills regions, which fit at least a single line. To avoid gaps in the sparse infill, // make sure that this region does not contain parts narrower than the infill spacing width. #ifdef SLIC3R_DEBUG_SLICE_PROCESSING Polygons shell_before = shell; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #if 1 // Intentionally inflate a bit more than how much the region has been shrunk, // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). shell = offset2(shell, - 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); if (shell.empty()) continue; #else // Ensure each region is at least 3x infill line width wide, so it could be filled in. // float margin = float(infill_line_spacing) * 3.f; float margin = float(infill_line_spacing) * 1.5f; // 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. Polygons too_narrow = diff(shell, offset2(shell, -margin, margin, ClipperLib::jtMiter, 5.), true); if (! too_narrow.empty()) { // 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 polygons_append(shell, intersection(offset(too_narrow, margin), polygonsInternal)); } #endif ExPolygons new_internal_solid = intersection_ex(polygonsInternal, shell, false); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-regularized-%d.svg", debug_idx), get_extents(shell_before)); // Source shell. svg.draw(union_ex(shell_before, true)); // Shell trimmed to the internal surfaces. svg.draw_outline(union_ex(shell, true), "black", "blue", scale_(0.05)); // Regularized infill region. svg.draw_outline(new_internal_solid, "red", "magenta", scale_(0.05)); svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the internal & internalvoid by the shell. Slic3r::ExPolygons new_internal = diff_ex( to_polygons(layerm->fill_surfaces.filter_by_type(stInternal)), shell, false ); Slic3r::ExPolygons new_internal_void = diff_ex( to_polygons(layerm->fill_surfaces.filter_by_type(stInternalVoid)), shell, false ); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal-%d.svg", debug_idx), get_extents(shell), new_internal, "black", "blue", scale_(0.05)); SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_void-%d.svg", debug_idx), get_extents(shell), new_internal_void, "black", "blue", scale_(0.05)); SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_solid-%d.svg", debug_idx), get_extents(shell), new_internal_solid, "black", "blue", scale_(0.05)); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Assign resulting internal surfaces to layer. const SurfaceType surfaceTypesKeep[] = { stTop, stBottom, stBottomBridge }; layerm->fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType)); layerm->fill_surfaces.append(new_internal, stInternal); layerm->fill_surfaces.append(new_internal_void, stInternalVoid); layerm->fill_surfaces.append(new_internal_solid, stInternalSolid); } // for each layer }); BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - end"; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t idx_layer = 0; idx_layer < this->layers.size(); ++idx_layer) { LayerRegion *layerm = this->layers[idx_layer]->get_region(idx_region); layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-final"); layerm->export_region_fill_surfaces_to_svg_debug("4_discover_vertical_shells-final"); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } // for each region // Write the profiler measurements to file // PROFILE_UPDATE(); // PROFILE_OUTPUT(debug_out_path("discover_vertical_shells-profile.txt").c_str()); } /* This method applies bridge flow to the first internal solid layer above sparse infill */ void PrintObject::bridge_over_infill() { BOOST_LOG_TRIVIAL(info) << "Bridge over infill..."; FOREACH_REGION(this->_print, region) { size_t region_id = region - this->_print->regions.begin(); // skip bridging in case there are no voids if ((*region)->config.fill_density.value == 100) continue; // get bridge flow Flow bridge_flow = (*region)->flow( frSolidInfill, -1, // layer height, not relevant for bridge flow true, // bridge false, // first layer -1, // custom width, not relevant for bridge flow *this ); FOREACH_LAYER(this, layer_it) { // skip first layer if (layer_it == this->layers.begin()) continue; Layer* layer = *layer_it; LayerRegion* layerm = layer->regions[region_id]; // extract the stInternalSolid surfaces that might be transformed into bridges Polygons internal_solid; layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid); // check whether the lower area is deep enough for absorbing the extra flow // (for obvious physical reasons but also for preventing the bridge extrudates // from overflowing in 3D preview) ExPolygons to_bridge; { Polygons to_bridge_pp = internal_solid; // iterate through lower layers spanned by bridge_flow double bottom_z = layer->print_z - bridge_flow.height; for (int i = int(layer_it - this->layers.begin()) - 1; i >= 0; --i) { const Layer* lower_layer = this->layers[i]; // stop iterating if layer is lower than bottom_z if (lower_layer->print_z < bottom_z) break; // iterate through regions and collect internal surfaces Polygons lower_internal; FOREACH_LAYERREGION(lower_layer, lower_layerm_it) (*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal); // intersect such lower internal surfaces with the candidate solid surfaces to_bridge_pp = intersection(to_bridge_pp, lower_internal); } // there's no point in bridging too thin/short regions //FIXME Vojtech: The offset2 function is not a geometric offset, // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour. // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer. { float min_width = float(bridge_flow.scaled_width()) * 3.f; to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width); } if (to_bridge_pp.empty()) continue; // convert into ExPolygons to_bridge = union_ex(to_bridge_pp); } #ifdef SLIC3R_DEBUG printf("Bridging " PRINTF_ZU " internal areas at layer " PRINTF_ZU "\n", to_bridge.size(), layer->id()); #endif // compute the remaning internal solid surfaces as difference ExPolygons not_to_bridge = diff_ex(internal_solid, to_polygons(to_bridge), true); to_bridge = intersection_ex(to_polygons(to_bridge), internal_solid, true); // build the new collection of fill_surfaces layerm->fill_surfaces.remove_type(stInternalSolid); for (ExPolygon &ex : to_bridge) layerm->fill_surfaces.surfaces.push_back(Surface(stInternalBridge, ex)); for (ExPolygon &ex : not_to_bridge) layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, ex)); /* # exclude infill from the layers below if needed # see discussion at https://github.com/alexrj/Slic3r/issues/240 # Update: do not exclude any infill. Sparse infill is able to absorb the excess material. if (0) { my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height; for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) { Slic3r::debugf " skipping infill below those areas at layer %d\n", $i; foreach my $lower_layerm (@{$self->get_layer($i)->regions}) { my @new_surfaces = (); # subtract the area from all types of surfaces foreach my $group (@{$lower_layerm->fill_surfaces->group}) { push @new_surfaces, map $group->[0]->clone(expolygon => $_), @{diff_ex( [ map $_->p, @$group ], [ map @$_, @$to_bridge ], )}; push @new_surfaces, map Slic3r::Surface->new( expolygon => $_, surface_type => S_TYPE_INTERNALVOID, ), @{intersection_ex( [ map $_->p, @$group ], [ map @$_, @$to_bridge ], )}; } $lower_layerm->fill_surfaces->clear; $lower_layerm->fill_surfaces->append($_) for @new_surfaces; } $excess -= $self->get_layer($i)->height; } } */ #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_slices_to_svg_debug("7_bridge_over_infill"); layerm->export_region_fill_surfaces_to_svg_debug("7_bridge_over_infill"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } } } SlicingParameters PrintObject::slicing_parameters() const { return SlicingParameters::create_from_config( this->print()->config, this->config, unscale(this->size.z), this->print()->object_extruders()); } bool PrintObject::update_layer_height_profile(std::vector &layer_height_profile) const { bool updated = false; // If the layer height profile is not set, try to use the one stored at the ModelObject. if (layer_height_profile.empty() && layer_height_profile.data() != this->model_object()->layer_height_profile.data()) { layer_height_profile = this->model_object()->layer_height_profile; updated = true; } // Verify the layer_height_profile. SlicingParameters slicing_params = this->slicing_parameters(); if (! layer_height_profile.empty() && // Must not be of even length. ((layer_height_profile.size() & 1) != 0 || // Last entry must be at the top of the object. std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_height()) > 1e-3)) layer_height_profile.clear(); if (layer_height_profile.empty()) { if (0) // if (this->layer_height_profile.empty()) layer_height_profile = layer_height_profile_adaptive(slicing_params, this->layer_height_ranges, this->model_object()->volumes); else layer_height_profile = layer_height_profile_from_ranges(slicing_params, this->layer_height_ranges); updated = true; } return updated; } // This must be called from the main thread as it modifies the layer_height_profile. bool PrintObject::update_layer_height_profile() { // If the layer height profile has been marked as invalid for some reason (modified at the UI level // or invalidated due to the slicing parameters), clear it now. if (! this->layer_height_profile_valid) { this->layer_height_profile.clear(); this->layer_height_profile_valid = true; } return this->update_layer_height_profile(this->layer_height_profile); } // 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 void PrintObject::_slice() { BOOST_LOG_TRIVIAL(info) << "Slicing objects..."; this->typed_slices = false; #if 0 // Disable parallelization for debugging purposes. static tbb::task_scheduler_init *tbb_init = nullptr; tbb_init = new tbb::task_scheduler_init(1); #endif SlicingParameters slicing_params = this->slicing_parameters(); // 1) Initialize layers and their slice heights. std::vector slice_zs; { this->clear_layers(); // Object layers (pairs of bottom/top Z coordinate), without the raft. std::vector object_layers = generate_object_layers(slicing_params, this->layer_height_profile); // Reserve object layers for the raft. Last layer of the raft is the contact layer. int id = int(slicing_params.raft_layers()); slice_zs.reserve(object_layers.size()); Layer *prev = nullptr; for (size_t i_layer = 0; i_layer < object_layers.size(); i_layer += 2) { coordf_t lo = object_layers[i_layer]; coordf_t hi = object_layers[i_layer + 1]; coordf_t slice_z = 0.5 * (lo + hi); Layer *layer = this->add_layer(id ++, hi - lo, hi + slicing_params.object_print_z_min, slice_z); slice_zs.push_back(float(slice_z)); if (prev != nullptr) { prev->upper_layer = layer; layer->lower_layer = prev; } // Make sure all layers contain layer region objects for all regions. for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) layer->add_region(this->print()->regions[region_id]); prev = layer; } } // Slice all non-modifier volumes. for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing objects - region " << region_id; std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, false); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " start"; for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) this->layers[layer_id]->regions[region_id]->slices.append(std::move(expolygons_by_layer[layer_id]), stInternal); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " end"; } // Slice all modifier volumes. if (this->print()->regions.size() > 1) { for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - region " << region_id; std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, true); // loop through the other regions and 'steal' the slices belonging to this one BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - stealing " << region_id << " start"; for (size_t other_region_id = 0; other_region_id < this->print()->regions.size(); ++ other_region_id) { if (region_id == other_region_id) continue; for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) { Layer *layer = layers[layer_id]; LayerRegion *layerm = layer->regions[region_id]; LayerRegion *other_layerm = layer->regions[other_region_id]; if (layerm == nullptr || other_layerm == nullptr) continue; Polygons other_slices = to_polygons(other_layerm->slices); ExPolygons my_parts = intersection_ex(other_slices, to_polygons(expolygons_by_layer[layer_id])); if (my_parts.empty()) continue; // Remove such parts from original region. other_layerm->slices.set(diff_ex(other_slices, to_polygons(my_parts)), stInternal); // Append new parts to our region. layerm->slices.append(std::move(my_parts), stInternal); } } BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - stealing " << region_id << " end"; } } BOOST_LOG_TRIVIAL(debug) << "Slicing objects - removing top empty layers"; while (! this->layers.empty()) { const Layer *layer = this->layers.back(); for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) if (layer->regions[region_id] != nullptr && ! layer->regions[region_id]->slices.empty()) // Non empty layer. goto end; delete layer; this->layers.pop_back(); if (! this->layers.empty()) this->layers.back()->upper_layer = nullptr; } end: ; BOOST_LOG_TRIVIAL(debug) << "Slicing objects - make_slices in parallel - begin"; tbb::parallel_for( tbb::blocked_range(0, this->layers.size()), [this](const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { Layer *layer = this->layers[layer_id]; // Apply size compensation and perform clipping of multi-part objects. float delta = float(scale_(this->config.xy_size_compensation.value)); if (layer_id == 0) delta -= float(scale_(this->config.elefant_foot_compensation.value)); bool scale = delta != 0.f; bool clip = this->config.clip_multipart_objects.value || delta > 0.f; if (layer->regions.size() == 1) { if (scale) { // Single region, growing or shrinking. LayerRegion *layerm = layer->regions.front(); layerm->slices.set(offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), delta), stInternal); } } else if (scale || clip) { // Multiple regions, growing, shrinking or just clipping one region by the other. // When clipping the regions, priority is given to the first regions. Polygons processed; for (size_t region_id = 0; region_id < layer->regions.size(); ++ region_id) { LayerRegion *layerm = layer->regions[region_id]; ExPolygons slices = to_expolygons(std::move(layerm->slices.surfaces)); if (scale) slices = offset_ex(slices, delta); if (region_id > 0 && clip) // Trim by the slices of already processed regions. slices = diff_ex(to_polygons(std::move(slices)), processed); if (clip && region_id + 1 < layer->regions.size()) // Collect the already processed regions to trim the to be processed regions. polygons_append(processed, slices); layerm->slices.set(std::move(slices), stInternal); } } // Merge all regions' slices to get islands, chain them by a shortest path. layer->make_slices(); } }); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - make_slices in parallel - end"; } std::vector PrintObject::_slice_region(size_t region_id, const std::vector &z, bool modifier) { std::vector layers; if (region_id < this->region_volumes.size()) { std::vector &volumes = this->region_volumes[region_id]; if (! volumes.empty()) { // Compose mesh. //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. TriangleMesh mesh; for (int volume_id : volumes) { ModelVolume *volume = this->model_object()->volumes[volume_id]; if (volume->modifier == modifier) mesh.merge(volume->mesh); } if (mesh.stl.stats.number_of_facets > 0) { // transform mesh // we ignore the per-instance transformations currently and only // consider the first one this->model_object()->instances.front()->transform_mesh(&mesh, true); // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift mesh.translate(- float(unscale(this->_copies_shift.x)), - float(unscale(this->_copies_shift.y)), -float(this->model_object()->bounding_box().min.z)); // perform actual slicing TriangleMeshSlicer mslicer(&mesh); mslicer.slice(z, &layers); } } } return layers; } std::string PrintObject::_fix_slicing_errors() { // Collect layers with slicing errors. // These layers will be fixed in parallel. std::vector buggy_layers; buggy_layers.reserve(this->layers.size()); for (size_t idx_layer = 0; idx_layer < this->layers.size(); ++ idx_layer) if (this->layers[idx_layer]->slicing_errors) buggy_layers.push_back(idx_layer); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - begin"; tbb::parallel_for( tbb::blocked_range(0, buggy_layers.size()), [this, &buggy_layers](const tbb::blocked_range& range) { for (size_t buggy_layer_idx = range.begin(); buggy_layer_idx < range.end(); ++ buggy_layer_idx) { size_t idx_layer = buggy_layers[buggy_layer_idx]; Layer *layer = this->layers[idx_layer]; assert(layer->slicing_errors); // Try to repair the layer surfaces by merging all contours and all holes from neighbor layers. // BOOST_LOG_TRIVIAL(trace) << "Attempting to repair layer" << idx_layer; for (size_t region_id = 0; region_id < layer->regions.size(); ++ region_id) { LayerRegion *layerm = layer->regions[region_id]; // Find the first valid layer below / above the current layer. const Surfaces *upper_surfaces = nullptr; const Surfaces *lower_surfaces = nullptr; for (size_t j = idx_layer + 1; j < this->layers.size(); ++ j) if (! this->layers[j]->slicing_errors) { upper_surfaces = &this->layers[j]->regions[region_id]->slices.surfaces; break; } for (int j = int(idx_layer) - 1; j >= 0; -- j) if (! this->layers[j]->slicing_errors) { lower_surfaces = &this->layers[j]->regions[region_id]->slices.surfaces; break; } // Collect outer contours and holes from the valid layers above & below. Polygons outer; outer.reserve( ((upper_surfaces == nullptr) ? 0 : upper_surfaces->size()) + ((lower_surfaces == nullptr) ? 0 : lower_surfaces->size())); size_t num_holes = 0; if (upper_surfaces) for (const auto &surface : *upper_surfaces) { outer.push_back(surface.expolygon.contour); num_holes += surface.expolygon.holes.size(); } if (lower_surfaces) for (const auto &surface : *lower_surfaces) { outer.push_back(surface.expolygon.contour); num_holes += surface.expolygon.holes.size(); } Polygons holes; holes.reserve(num_holes); if (upper_surfaces) for (const auto &surface : *upper_surfaces) polygons_append(holes, surface.expolygon.holes); if (lower_surfaces) for (const auto &surface : *lower_surfaces) polygons_append(holes, surface.expolygon.holes); layerm->slices.set(diff_ex(union_(outer), holes, false), stInternal); } // Update layer slices after repairing the single regions. layer->make_slices(); } }); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - end"; // remove empty layers from bottom while (! this->layers.empty() && this->layers.front()->slices.expolygons.empty()) { delete this->layers.front(); this->layers.erase(this->layers.begin()); this->layers.front()->lower_layer = nullptr; for (size_t i = 0; i < this->layers.size(); ++ i) this->layers[i]->set_id(this->layers[i]->id() - 1); } return buggy_layers.empty() ? "" : "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"; } // 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. void PrintObject::_simplify_slices(double distance) { BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - begin"; tbb::parallel_for( tbb::blocked_range(0, this->layers.size()), [this, distance](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { Layer *layer = this->layers[layer_idx]; for (size_t region_idx = 0; region_idx < layer->regions.size(); ++ region_idx) layer->regions[region_idx]->slices.simplify(distance); layer->slices.simplify(distance); } }); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - end"; } void PrintObject::_make_perimeters() { if (this->state.is_done(posPerimeters)) return; this->state.set_started(posPerimeters); BOOST_LOG_TRIVIAL(info) << "Generating perimeters..."; // merge slices if they were split into types if (this->typed_slices) { FOREACH_LAYER(this, layer_it) (*layer_it)->merge_slices(); this->typed_slices = false; this->state.invalidate(posPrepareInfill); } // compare each layer to the one below, and mark those slices needing // one additional inner perimeter, like the top of domed objects- // this algorithm makes sure that at least one perimeter is overlapping // but we don't generate any extra perimeter if fill density is zero, as they would be floating // inside the object - infill_only_where_needed should be the method of choice for printing // hollow objects FOREACH_REGION(this->_print, region_it) { size_t region_id = region_it - this->_print->regions.begin(); const PrintRegion ®ion = **region_it; if (!region.config.extra_perimeters || region.config.perimeters == 0 || region.config.fill_density == 0 || this->layer_count() < 2) continue; BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, this->layers.size() - 1), [this, ®ion, region_id](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { LayerRegion &layerm = *this->layers[layer_idx]->regions[region_id]; const LayerRegion &upper_layerm = *this->layers[layer_idx+1]->regions[region_id]; const Polygons upper_layerm_polygons = upper_layerm.slices; // Filter upper layer polygons in intersection_ppl by their bounding boxes? // my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ]; const double total_loop_length = total_length(upper_layerm_polygons); const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing(); const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter); const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width(); const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing(); for (Surface &slice : layerm.slices.surfaces) { for (;;) { // compute the total thickness of perimeters const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2 + (region.config.perimeters-1 + slice.extra_perimeters) * perimeter_spacing; // define a critical area where we don't want the upper slice to fall into // (it should either lay over our perimeters or outside this area) const coord_t critical_area_depth = coord_t(perimeter_spacing * 1.5); const Polygons critical_area = diff( offset(slice.expolygon, float(- perimeters_thickness)), offset(slice.expolygon, float(- perimeters_thickness - critical_area_depth)) ); // check whether a portion of the upper slices falls inside the critical area const Polylines intersection = intersection_pl(to_polylines(upper_layerm_polygons), critical_area); // only add an additional loop if at least 30% of the slice loop would benefit from it if (total_length(intersection) <= total_loop_length*0.3) break; /* if (0) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( "extra.svg", no_arrows => 1, expolygons => union_ex($critical_area), polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ], ); } */ ++ slice.extra_perimeters; } #ifdef DEBUG if (slice.extra_perimeters > 0) printf(" adding %d more perimeter(s) at layer %zu\n", slice.extra_perimeters, layer_idx); #endif } } }); BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - end"; } BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, this->layers.size()), [this](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) this->layers[layer_idx]->make_perimeters(); } ); BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end"; /* simplify slices (both layer and region slices), we only need the max resolution for perimeters ### This makes this method not-idempotent, so we keep it disabled for now. ###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION); */ this->state.set_done(posPerimeters); } void PrintObject::_infill() { if (this->state.is_done(posInfill)) return; this->state.set_started(posInfill); BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, this->layers.size()), [this](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) this->layers[layer_idx]->make_fills(); } ); BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - end"; /* we could free memory now, but this would make this step not idempotent ### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers}; */ this->state.set_done(posInfill); } // Only active if config->infill_only_where_needed. This step trims the sparse infill, // so it acts as an internal support. It maintains all other infill types intact. // Here the internal surfaces and perimeters have to be supported by the sparse infill. //FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support. // Likely the sparse infill will not be anchored correctly, so it will not work as intended. // Also one wishes the perimeters to be supported by a full infill. // 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. void PrintObject::clip_fill_surfaces() { if (! this->config.infill_only_where_needed.value || ! std::any_of(this->print()->regions.begin(), this->print()->regions.end(), [](const PrintRegion *region) { return region->config.fill_density > 0; })) return; // We only want infill under ceilings; this is almost like an // internal support material. // Proceed top-down, skipping the bottom layer. Polygons upper_internal; for (int layer_id = int(this->layers.size()) - 1; layer_id > 0; -- layer_id) { Layer *layer = this->layers[layer_id]; Layer *lower_layer = this->layers[layer_id - 1]; // Detect things that we need to support. // Cummulative slices. Polygons slices; for (const ExPolygon &expoly : layer->slices.expolygons) polygons_append(slices, to_polygons(expoly)); // Cummulative fill surfaces. Polygons fill_surfaces; // Solid surfaces to be supported. Polygons overhangs; for (const LayerRegion *layerm : layer->regions) for (const Surface &surface : layerm->fill_surfaces.surfaces) { Polygons polygons = to_polygons(surface.expolygon); if (surface.is_solid()) polygons_append(overhangs, polygons); polygons_append(fill_surfaces, std::move(polygons)); } Polygons lower_layer_fill_surfaces; Polygons lower_layer_internal_surfaces; for (const LayerRegion *layerm : lower_layer->regions) for (const Surface &surface : layerm->fill_surfaces.surfaces) { Polygons polygons = to_polygons(surface.expolygon); if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) polygons_append(lower_layer_internal_surfaces, polygons); polygons_append(lower_layer_fill_surfaces, std::move(polygons)); } // 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 // Only consider the area that is not supported by lower perimeters Polygons perimeters = intersection(diff(slices, fill_surfaces), lower_layer_fill_surfaces); // Only consider perimeter areas that are at least one extrusion width thick. //FIXME Offset2 eats out from both sides, while the perimeters are create outside in. //Should the pw not be half of the current value? float pw = FLT_MAX; for (const LayerRegion *layerm : layer->regions) pw = std::min(pw, layerm->flow(frPerimeter).scaled_width()); // Append such thick perimeters to the areas that need support polygons_append(overhangs, offset2(perimeters, -pw, +pw)); } // Find new internal infill. polygons_append(overhangs, std::move(upper_internal)); upper_internal = intersection(overhangs, lower_layer_internal_surfaces); // Apply new internal infill to regions. for (LayerRegion *layerm : lower_layer->regions) { if (layerm->region()->config.fill_density.value == 0) continue; SurfaceType internal_surface_types[] = { stInternal, stInternalVoid }; Polygons internal; for (Surface &surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) polygons_append(internal, std::move(surface.expolygon)); layerm->fill_surfaces.remove_types(internal_surface_types, 2); layerm->fill_surfaces.append(intersection_ex(internal, upper_internal, true), stInternal); layerm->fill_surfaces.append(diff_ex (internal, upper_internal, true), stInternalVoid); // 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. #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_fill_surfaces_to_svg_debug("6_clip_fill_surfaces"); #endif } } } void PrintObject::discover_horizontal_shells() { BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()"; for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) { for (int i = 0; i < int(this->layers.size()); ++ i) { LayerRegion *layerm = this->layers[i]->regions[region_id]; PrintRegionConfig ®ion_config = layerm->region()->config; if (region_config.solid_infill_every_layers.value > 0 && region_config.fill_density.value > 0 && (i % region_config.solid_infill_every_layers) == 0) { // Insert a solid internal layer. Mark stInternal surfaces as stInternalSolid or stInternalBridge. SurfaceType type = (region_config.fill_density == 100) ? stInternalSolid : stInternalBridge; for (Surface &surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal) surface.surface_type = type; } // If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells(). if (region_config.ensure_vertical_shell_thickness.value) continue; for (int idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge; // 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. // Surfaces including the area of perimeters. Everything, that is visible from the top / bottom // (not covered by a layer above / below). // This does not contain the areas covered by perimeters! Polygons solid; for (const Surface &surface : layerm->slices.surfaces) if (surface.surface_type == type) polygons_append(solid, to_polygons(surface.expolygon)); // Infill areas (slices without the perimeters). for (const Surface &surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == type) polygons_append(solid, to_polygons(surface.expolygon)); if (solid.empty()) continue; // Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom'; size_t solid_layers = (type == stTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value; for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - i) < solid_layers; (type == stTop) ? -- n : ++ n) { if (n < 0 || n >= int(this->layers.size())) continue; // Slic3r::debugf " looking for neighbors on layer %d...\n", $n; // Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface. LayerRegion *neighbor_layerm = this->layers[n]->regions[region_id]; // find intersection between neighbor and current layer's surfaces // intersections have contours and holes // we update $solid so that we limit the next neighbor layer to the areas that were // found on this one - in other words, solid shells on one layer (for a given external surface) // are always a subset of the shells found on the previous shell layer // this approach allows for DWIM in hollow sloping vases, where we want bottom // shells to be generated in the base but not in the walls (where there are many // narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the // upper perimeter as an obstacle and shell will not be propagated to more upper layers //FIXME How does it work for S_TYPE_INTERNALBRIDGE? This is set for sparse infill. Likely this does not work. Polygons new_internal_solid; { Polygons internal; for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalSolid) polygons_append(internal, to_polygons(surface.expolygon)); new_internal_solid = intersection(solid, internal, true); } if (new_internal_solid.empty()) { // 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 (region_config.fill_density.value == 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. goto EXTERNAL; } else { // If we have internal infill, we can generate internal solid shells freely. continue; } } if (region_config.fill_density.value == 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!) float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width()); Polygons too_narrow = diff( new_internal_solid, offset2(new_internal_solid, -margin, +margin, jtMiter, 5), true); // Trim the regularized region by the original region. if (! too_narrow.empty()) new_internal_solid = solid = diff(new_internal_solid, too_narrow); } // make sure the new internal solid is wide enough, as it might get collapsed // when spacing is added in Fill.pm { //FIXME Vojtech: Disable this and you will be sorry. // https://github.com/prusa3d/Slic3r/issues/26 bottom float margin = 3.f * layerm->flow(frSolidInfill).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. Polygons too_narrow = diff( new_internal_solid, offset2(new_internal_solid, -margin, +margin, ClipperLib::jtMiter, 5), true); if (! too_narrow.empty()) { // 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 Polygons internal; for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces) if (surface.is_internal() && !surface.is_bridge()) polygons_append(internal, to_polygons(surface.expolygon)); polygons_append(new_internal_solid, 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.) internal)); solid = new_internal_solid; } } // internal-solid are the union of the existing internal-solid surfaces // and new ones SurfaceCollection backup = std::move(neighbor_layerm->fill_surfaces); polygons_append(new_internal_solid, to_polygons(backup.filter_by_type(stInternalSolid))); ExPolygons internal_solid = union_ex(new_internal_solid, false); // assign new internal-solid surfaces to layer neighbor_layerm->fill_surfaces.set(internal_solid, stInternalSolid); // subtract intersections from layer surfaces to get resulting internal surfaces Polygons polygons_internal = to_polygons(std::move(internal_solid)); ExPolygons internal = diff_ex( to_polygons(backup.filter_by_type(stInternal)), polygons_internal, true); // assign resulting internal surfaces to layer neighbor_layerm->fill_surfaces.append(internal, stInternal); polygons_append(polygons_internal, to_polygons(std::move(internal))); // assign top and bottom surfaces to layer SurfaceType surface_types_solid[] = { stTop, stBottom, stBottomBridge }; backup.keep_types(surface_types_solid, 3); std::vector top_bottom_groups; backup.group(&top_bottom_groups); for (SurfacesPtr &group : top_bottom_groups) neighbor_layerm->fill_surfaces.append( diff_ex(to_polygons(group), polygons_internal), // Use an existing surface as a template, it carries the bridge angle etc. *group.front()); } EXTERNAL:; } // foreach type (stTop, stBottom, stBottomBridge) } // for each layer } // for each region #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) { for (const Layer *layer : this->layers) { const LayerRegion *layerm = layer->regions[region_id]; layerm->export_region_slices_to_svg_debug("5_discover_horizontal_shells"); layerm->export_region_fill_surfaces_to_svg_debug("5_discover_horizontal_shells"); } // for each layer } // for each region #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } // combine fill surfaces across layers to honor the "infill every N layers" option // 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. void PrintObject::combine_infill() { // Work on each region separately. for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) { const PrintRegion *region = this->print()->regions[region_id]; const int every = region->config.infill_every_layers.value; if (every < 2 || region->config.fill_density == 0.) continue; // Limit the number of combined layers to the maximum height allowed by this regions' nozzle. //FIXME limit the layer height to max_layer_height double nozzle_diameter = std::min( this->print()->config.nozzle_diameter.get_at(region->config.infill_extruder.value - 1), this->print()->config.nozzle_diameter.get_at(region->config.solid_infill_extruder.value - 1)); // define the combinations std::vector combine(this->layers.size(), 0); { double current_height = 0.; size_t num_layers = 0; for (size_t layer_idx = 0; layer_idx < this->layers.size(); ++ layer_idx) { const Layer *layer = this->layers[layer_idx]; if (layer->id() == 0) // Skip first print layer (which may not be first layer in array because of raft). continue; // 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 + layer->height >= nozzle_diameter + EPSILON || num_layers >= every) { // Append combination to lower layer. combine[layer_idx - 1] = num_layers; current_height = 0.; num_layers = 0; } current_height += layer->height; ++ num_layers; } // Append lower layers (if any) to uppermost layer. combine[this->layers.size() - 1] = num_layers; } // loop through layers to which we have assigned layers to combine for (size_t layer_idx = 0; layer_idx < this->layers.size(); ++ layer_idx) { size_t num_layers = combine[layer_idx]; if (num_layers <= 1) continue; // Get all the LayerRegion objects to be combined. std::vector layerms; layerms.reserve(num_layers); for (size_t i = layer_idx + 1 - num_layers; i <= layer_idx; ++ i) layerms.emplace_back(this->layers[i]->regions[region_id]); // 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. ExPolygons intersection = to_expolygons(layerms.front()->fill_surfaces.filter_by_type(stInternal)); // Start looping from the second layer and intersect the current intersection with it. for (size_t i = 1; i < layerms.size(); ++ i) intersection = intersection_ex( to_polygons(intersection), to_polygons(layerms[i]->fill_surfaces.filter_by_type(stInternal)), false); double area_threshold = layerms.front()->infill_area_threshold(); if (! intersection.empty() && area_threshold > 0.) intersection.erase(std::remove_if(intersection.begin(), intersection.end(), [area_threshold](const ExPolygon &expoly) { return expoly.area() <= area_threshold; }), intersection.end()); if (intersection.empty()) continue; // 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. Polygons intersection_with_clearance; intersection_with_clearance.reserve(intersection.size()); float clearance_offset = 0.5f * layerms.back()->flow(frPerimeter).scaled_width() + // Because fill areas for rectilinear and honeycomb are grown // later to overlap perimeters, we need to counteract that too. ((region->config.fill_pattern == ipRectilinear || region->config.fill_pattern == ipGrid || region->config.fill_pattern == ipLine || region->config.fill_pattern == ipHoneycomb) ? 1.5f : 0.5f) * layerms.back()->flow(frSolidInfill).scaled_width(); for (ExPolygon &expoly : intersection) polygons_append(intersection_with_clearance, offset(expoly, clearance_offset)); for (LayerRegion *layerm : layerms) { Polygons internal = to_polygons(layerm->fill_surfaces.filter_by_type(stInternal)); layerm->fill_surfaces.remove_type(stInternal); layerm->fill_surfaces.append(diff_ex(internal, intersection_with_clearance, false), stInternal); if (layerm == layerms.back()) { // Apply surfaces back with adjusted depth to the uppermost layer. Surface templ(stInternal, ExPolygon()); templ.thickness = 0.; for (LayerRegion *layerm2 : layerms) templ.thickness += layerm2->layer()->height; templ.thickness_layers = (unsigned short)layerms.size(); layerm->fill_surfaces.append(intersection, templ); } else { // Save void surfaces. layerm->fill_surfaces.append( intersection_ex(internal, intersection_with_clearance, false), stInternalVoid); } } } } } void PrintObject::_generate_support_material() { PrintObjectSupportMaterial support_material(this, PrintObject::slicing_parameters()); support_material.generate(*this); } void PrintObject::reset_layer_height_profile() { // Reset the layer_heigth_profile. this->layer_height_profile.clear(); this->layer_height_profile_valid = false; // Reset the source layer_height_profile if it exists at the ModelObject. this->model_object()->layer_height_profile.clear(); this->model_object()->layer_height_profile_valid = false; } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/libslic3r/PrintRegion.cpp000066400000000000000000000050061324354444700225770ustar00rootroot00000000000000#include "Print.hpp" namespace Slic3r { Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const { ConfigOptionFloatOrPercent config_width; if (width != -1) { // use the supplied custom width, if any config_width.value = width; config_width.percent = false; } else { // otherwise, get extrusion width from configuration // (might be an absolute value, or a percent value, or zero for auto) if (first_layer && this->_print->config.first_layer_extrusion_width.value > 0) { config_width = this->_print->config.first_layer_extrusion_width; } else if (role == frExternalPerimeter) { config_width = this->config.external_perimeter_extrusion_width; } else if (role == frPerimeter) { config_width = this->config.perimeter_extrusion_width; } else if (role == frInfill) { config_width = this->config.infill_extrusion_width; } else if (role == frSolidInfill) { config_width = this->config.solid_infill_extrusion_width; } else if (role == frTopSolidInfill) { config_width = this->config.top_infill_extrusion_width; } else { CONFESS("Unknown role"); } } if (config_width.value == 0) { config_width = object.config.extrusion_width; } // get the configured nozzle_diameter for the extruder associated // to the flow role requested size_t extruder = 0; // 1-based if (role == frPerimeter || role == frExternalPerimeter) { extruder = this->config.perimeter_extruder; } else if (role == frInfill) { extruder = this->config.infill_extruder; } else if (role == frSolidInfill || role == frTopSolidInfill) { extruder = this->config.solid_infill_extruder; } else { CONFESS("Unknown role $role"); } double nozzle_diameter = this->_print->config.nozzle_diameter.get_at(extruder-1); return Flow::new_from_config_width(role, config_width, nozzle_diameter, layer_height, bridge ? (float)this->config.bridge_flow_ratio : 0.0); } coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const { return (print_config.nozzle_diameter.get_at(this->config.perimeter_extruder.value - 1) + print_config.nozzle_diameter.get_at(this->config.infill_extruder.value - 1) + print_config.nozzle_diameter.get_at(this->config.solid_infill_extruder.value - 1)) / 3.; } } Slic3r-version_1.39.1/xs/src/libslic3r/SVG.cpp000066400000000000000000000320521324354444700207770ustar00rootroot00000000000000#include "SVG.hpp" #include #include #define COORD(x) ((float)unscale((x))*10) namespace Slic3r { bool SVG::open(const char* afilename) { this->filename = afilename; this->f = boost::nowide::fopen(afilename, "w"); if (this->f == NULL) return false; fprintf(this->f, "\n" "\n" "\n" " \n" " \n" " \n" ); return true; } bool SVG::open(const char* afilename, const BoundingBox &bbox, const coord_t bbox_offset, bool aflipY) { this->filename = afilename; this->origin = bbox.min - Point(bbox_offset, bbox_offset); this->flipY = aflipY; this->f = boost::nowide::fopen(afilename, "w"); if (f == NULL) return false; float w = COORD(bbox.max.x - bbox.min.x + 2 * bbox_offset); float h = COORD(bbox.max.y - bbox.min.y + 2 * bbox_offset); fprintf(this->f, "\n" "\n" "\n" " \n" " \n" " \n", h, w); return true; } void SVG::draw(const Line &line, std::string stroke, coordf_t stroke_width) { fprintf(this->f, " arrows) fprintf(this->f, " marker-end=\"url(#endArrow)\""); fprintf(this->f, "/>\n"); } void SVG::draw(const ThickLine &line, const std::string &fill, const std::string &stroke, coordf_t stroke_width) { Pointf dir(line.b.x-line.a.x, line.b.y-line.a.y); Pointf perp(-dir.y, dir.x); coordf_t len = sqrt(perp.x*perp.x + perp.y*perp.y); coordf_t da = coordf_t(0.5)*line.a_width/len; coordf_t db = coordf_t(0.5)*line.b_width/len; fprintf(this->f, " \n", COORD(line.a.x-da*perp.x-origin.x), COORD(line.a.y-da*perp.y-origin.y), COORD(line.b.x-db*perp.x-origin.x), COORD(line.b.y-db*perp.y-origin.y), COORD(line.b.x+db*perp.x-origin.x), COORD(line.b.y+db*perp.y-origin.y), COORD(line.a.x+da*perp.x-origin.x), COORD(line.a.y+da*perp.y-origin.y), fill.c_str(), stroke.c_str(), (stroke_width == 0) ? 1.f : COORD(stroke_width)); } void SVG::draw(const Lines &lines, std::string stroke, coordf_t stroke_width) { for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) this->draw(*it, stroke, stroke_width); } void SVG::draw(const IntersectionLines &lines, std::string stroke) { for (IntersectionLines::const_iterator it = lines.begin(); it != lines.end(); ++it) this->draw((Line)*it, stroke); } void SVG::draw(const ExPolygon &expolygon, std::string fill, const float fill_opacity) { this->fill = fill; std::string d; Polygons pp = expolygon; for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { d += this->get_path_d(*p, true) + " "; } this->path(d, true, 0, fill_opacity); } void SVG::draw_outline(const ExPolygon &expolygon, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width) { draw_outline(expolygon.contour, stroke_outer, stroke_width); for (Polygons::const_iterator it = expolygon.holes.begin(); it != expolygon.holes.end(); ++ it) { draw_outline(*it, stroke_holes, stroke_width); } } void SVG::draw(const ExPolygons &expolygons, std::string fill, const float fill_opacity) { for (ExPolygons::const_iterator it = expolygons.begin(); it != expolygons.end(); ++it) this->draw(*it, fill, fill_opacity); } void SVG::draw_outline(const ExPolygons &expolygons, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width) { for (ExPolygons::const_iterator it = expolygons.begin(); it != expolygons.end(); ++ it) draw_outline(*it, stroke_outer, stroke_holes, stroke_width); } void SVG::draw(const Surface &surface, std::string fill, const float fill_opacity) { draw(surface.expolygon, fill, fill_opacity); } void SVG::draw_outline(const Surface &surface, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width) { draw_outline(surface.expolygon, stroke_outer, stroke_holes, stroke_width); } void SVG::draw(const Surfaces &surfaces, std::string fill, const float fill_opacity) { for (Surfaces::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it) this->draw(*it, fill, fill_opacity); } void SVG::draw_outline(const Surfaces &surfaces, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width) { for (Surfaces::const_iterator it = surfaces.begin(); it != surfaces.end(); ++ it) draw_outline(*it, stroke_outer, stroke_holes, stroke_width); } void SVG::draw(const SurfacesPtr &surfaces, std::string fill, const float fill_opacity) { for (SurfacesPtr::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it) this->draw(*(*it), fill, fill_opacity); } void SVG::draw_outline(const SurfacesPtr &surfaces, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width) { for (SurfacesPtr::const_iterator it = surfaces.begin(); it != surfaces.end(); ++ it) draw_outline(*(*it), stroke_outer, stroke_holes, stroke_width); } void SVG::draw(const Polygon &polygon, std::string fill) { this->fill = fill; this->path(this->get_path_d(polygon, true), !fill.empty(), 0, 1.f); } void SVG::draw(const Polygons &polygons, std::string fill) { for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) this->draw(*it, fill); } void SVG::draw(const Polyline &polyline, std::string stroke, coordf_t stroke_width) { this->stroke = stroke; this->path(this->get_path_d(polyline, false), false, stroke_width, 1.f); } void SVG::draw(const Polylines &polylines, std::string stroke, coordf_t stroke_width) { for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) this->draw(*it, stroke, stroke_width); } void SVG::draw(const ThickLines &thicklines, const std::string &fill, const std::string &stroke, coordf_t stroke_width) { for (ThickLines::const_iterator it = thicklines.begin(); it != thicklines.end(); ++it) this->draw(*it, fill, stroke, stroke_width); } void SVG::draw(const ThickPolylines &polylines, const std::string &stroke, coordf_t stroke_width) { for (ThickPolylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) this->draw((Polyline)*it, stroke, stroke_width); } void SVG::draw(const ThickPolylines &thickpolylines, const std::string &fill, const std::string &stroke, coordf_t stroke_width) { for (ThickPolylines::const_iterator it = thickpolylines.begin(); it != thickpolylines.end(); ++ it) draw(it->thicklines(), fill, stroke, stroke_width); } void SVG::draw(const Point &point, std::string fill, coord_t iradius) { float radius = (iradius == 0) ? 3.f : COORD(iradius); std::ostringstream svg; svg << " "; fprintf(this->f, "%s\n", svg.str().c_str()); } void SVG::draw(const Points &points, std::string fill, coord_t radius) { for (Points::const_iterator it = points.begin(); it != points.end(); ++it) this->draw(*it, fill, radius); } void SVG::draw(const ClipperLib::Path &polygon, double scale, std::string stroke, coordf_t stroke_width) { this->stroke = stroke; this->path(this->get_path_d(polygon, scale, true), false, stroke_width, 1.f); } void SVG::draw(const ClipperLib::Paths &polygons, double scale, std::string stroke, coordf_t stroke_width) { for (ClipperLib::Paths::const_iterator it = polygons.begin(); it != polygons.end(); ++ it) draw(*it, scale, stroke, stroke_width); } void SVG::draw_outline(const Polygon &polygon, std::string stroke, coordf_t stroke_width) { this->stroke = stroke; this->path(this->get_path_d(polygon, true), false, stroke_width, 1.f); } void SVG::draw_outline(const Polygons &polygons, std::string stroke, coordf_t stroke_width) { for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++ it) draw_outline(*it, stroke, stroke_width); } void SVG::path(const std::string &d, bool fill, coordf_t stroke_width, const float fill_opacity) { float lineWidth = 0.f; if (! fill) lineWidth = (stroke_width == 0) ? 2.f : COORD(stroke_width); fprintf( this->f, " \n", d.c_str(), fill ? this->fill.c_str() : "none", this->stroke.c_str(), lineWidth, (this->arrows && !fill) ? " marker-end=\"url(#endArrow)\"" : "", fill_opacity ); } std::string SVG::get_path_d(const MultiPoint &mp, bool closed) const { std::ostringstream d; d << "M "; for (Points::const_iterator p = mp.points.begin(); p != mp.points.end(); ++p) { d << COORD(p->x - origin.x) << " "; d << COORD(p->y - origin.y) << " "; } if (closed) d << "z"; return d.str(); } std::string SVG::get_path_d(const ClipperLib::Path &path, double scale, bool closed) const { std::ostringstream d; d << "M "; for (ClipperLib::Path::const_iterator p = path.begin(); p != path.end(); ++p) { d << COORD(scale * p->X - origin.x) << " "; d << COORD(scale * p->Y - origin.y) << " "; } if (closed) d << "z"; return d.str(); } void SVG::draw_text(const Point &pt, const char *text, const char *color) { fprintf(this->f, "%s", COORD(pt.x-origin.x), COORD(pt.y-origin.y), color, text); } void SVG::draw_legend(const Point &pt, const char *text, const char *color) { fprintf(this->f, "", COORD(pt.x-origin.x), COORD(pt.y-origin.y), color); fprintf(this->f, "%s", COORD(pt.x-origin.x) + 20.f, COORD(pt.y-origin.y), "black", text); } void SVG::Close() { fprintf(this->f, "\n"); fclose(this->f); this->f = NULL; // printf("SVG written to %s\n", this->filename.c_str()); } void SVG::export_expolygons(const char *path, const BoundingBox &bbox, const Slic3r::ExPolygons &expolygons, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width) { SVG svg(path, bbox); svg.draw(expolygons); svg.draw_outline(expolygons, stroke_outer, stroke_holes, stroke_width); svg.Close(); } void SVG::export_expolygons(const char *path, const std::vector> &expolygons_with_attributes) { if (expolygons_with_attributes.empty()) return; BoundingBox bbox = get_extents(expolygons_with_attributes.front().first); for (size_t i = 0; i < expolygons_with_attributes.size(); ++ i) bbox.merge(get_extents(expolygons_with_attributes[i].first)); SVG svg(path, bbox); for (const auto &exp_with_attr : expolygons_with_attributes) svg.draw(exp_with_attr.first, exp_with_attr.second.color_fill, exp_with_attr.second.fill_opacity); for (const auto &exp_with_attr : expolygons_with_attributes) { std::string color_contour = exp_with_attr.second.color_contour; if (color_contour.empty()) color_contour = exp_with_attr.second.color_fill; std::string color_holes = exp_with_attr.second.color_holes; if (color_holes.empty()) color_holes = color_contour; svg.draw_outline(exp_with_attr.first, color_contour, color_holes, exp_with_attr.second.outline_width); } svg.Close(); } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/SVG.hpp������������������������������������������������������0000664�0000000�0000000�00000016534�13243544447�0021013�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef slic3r_SVG_hpp_ #define slic3r_SVG_hpp_ #include "libslic3r.h" #include "clipper.hpp" #include "ExPolygon.hpp" #include "Line.hpp" #include "TriangleMesh.hpp" #include "Surface.hpp" namespace Slic3r { class SVG { public: bool arrows; std::string fill, stroke; Point origin; bool flipY; SVG(const char* afilename) : arrows(false), fill("grey"), stroke("black"), filename(afilename), flipY(false) { open(filename); } SVG(const char* afilename, const BoundingBox &bbox, const coord_t bbox_offset = scale_(1.), bool aflipY = false) : arrows(false), fill("grey"), stroke("black"), filename(afilename), origin(bbox.min - Point(bbox_offset, bbox_offset)), flipY(aflipY) { open(filename, bbox, bbox_offset, aflipY); } SVG(const std::string &filename) : arrows(false), fill("grey"), stroke("black"), filename(filename), flipY(false) { open(filename); } SVG(const std::string &filename, const BoundingBox &bbox, const coord_t bbox_offset = scale_(1.), bool aflipY = false) : arrows(false), fill("grey"), stroke("black"), filename(filename), origin(bbox.min - Point(bbox_offset, bbox_offset)), flipY(aflipY) { open(filename, bbox, bbox_offset, aflipY); } ~SVG() { if (f != NULL) Close(); } bool open(const char* filename); bool open(const char* filename, const BoundingBox &bbox, const coord_t bbox_offset = scale_(1.), bool flipY = false); bool open(const std::string &filename) { return open(filename.c_str()); } bool open(const std::string &filename, const BoundingBox &bbox, const coord_t bbox_offset = scale_(1.), bool flipY = false) { return open(filename.c_str(), bbox, bbox_offset, flipY); } void draw(const Line &line, std::string stroke = "black", coordf_t stroke_width = 0); void draw(const ThickLine &line, const std::string &fill, const std::string &stroke, coordf_t stroke_width = 0); void draw(const Lines &lines, std::string stroke = "black", coordf_t stroke_width = 0); void draw(const IntersectionLines &lines, std::string stroke = "black"); void draw(const ExPolygon &expolygon, std::string fill = "grey", const float fill_opacity=1.f); void draw_outline(const ExPolygon &polygon, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0); void draw(const ExPolygons &expolygons, std::string fill = "grey", const float fill_opacity=1.f); void draw_outline(const ExPolygons &polygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0); void draw(const Surface &surface, std::string fill = "grey", const float fill_opacity=1.f); void draw_outline(const Surface &surface, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0); void draw(const Surfaces &surfaces, std::string fill = "grey", const float fill_opacity=1.f); void draw_outline(const Surfaces &surfaces, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0); void draw(const SurfacesPtr &surfaces, std::string fill = "grey", const float fill_opacity=1.f); void draw_outline(const SurfacesPtr &surfaces, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0); void draw(const Polygon &polygon, std::string fill = "grey"); void draw_outline(const Polygon &polygon, std::string stroke = "black", coordf_t stroke_width = 0); void draw(const Polygons &polygons, std::string fill = "grey"); void draw_outline(const Polygons &polygons, std::string stroke = "black", coordf_t stroke_width = 0); void draw(const Polyline &polyline, std::string stroke = "black", coordf_t stroke_width = 0); void draw(const Polylines &polylines, std::string stroke = "black", coordf_t stroke_width = 0); void draw(const ThickLines &thicklines, const std::string &fill = "lime", const std::string &stroke = "black", coordf_t stroke_width = 0); void draw(const ThickPolylines &polylines, const std::string &stroke = "black", coordf_t stroke_width = 0); void draw(const ThickPolylines &thickpolylines, const std::string &fill, const std::string &stroke, coordf_t stroke_width); void draw(const Point &point, std::string fill = "black", coord_t radius = 0); void draw(const Points &points, std::string fill = "black", coord_t radius = 0); // Support for rendering the ClipperLib paths void draw(const ClipperLib::Path &polygon, double scale, std::string fill = "grey", coordf_t stroke_width = 0); void draw(const ClipperLib::Paths &polygons, double scale, std::string fill = "grey", coordf_t stroke_width = 0); void draw_text(const Point &pt, const char *text, const char *color); void draw_legend(const Point &pt, const char *text, const char *color); void Close(); private: std::string filename; FILE* f; void path(const std::string &d, bool fill, coordf_t stroke_width, const float fill_opacity); std::string get_path_d(const MultiPoint &mp, bool closed = false) const; std::string get_path_d(const ClipperLib::Path &mp, double scale, bool closed = false) const; public: static void export_expolygons(const char *path, const BoundingBox &bbox, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0); static void export_expolygons(const std::string &path, const BoundingBox &bbox, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0) { export_expolygons(path.c_str(), bbox, expolygons, stroke_outer, stroke_holes, stroke_width); } static void export_expolygons(const char *path, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0) { export_expolygons(path, get_extents(expolygons), expolygons, stroke_outer, stroke_holes, stroke_width); } static void export_expolygons(const std::string &path, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0) { export_expolygons(path.c_str(), get_extents(expolygons), expolygons, stroke_outer, stroke_holes, stroke_width); } struct ExPolygonAttributes { ExPolygonAttributes() : ExPolygonAttributes("gray", "black", "blue") {} ExPolygonAttributes(const std::string &color) : ExPolygonAttributes(color, color, color) {} ExPolygonAttributes( const std::string &color_fill, const std::string &color_contour, const std::string &color_holes, const coord_t outline_width = scale_(0.05), const float fill_opacity = 0.5f) : color_fill (color_fill), color_contour (color_contour), color_holes (color_holes), outline_width (outline_width), fill_opacity (fill_opacity) {} std::string color_fill; std::string color_contour; std::string color_holes; coord_t outline_width; float fill_opacity; }; static void export_expolygons(const char *path, const std::vector> &expolygons_with_attributes); }; } #endif ��������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/Slicing.cpp��������������������������������������������������0000664�0000000�0000000�00000101425�13243544447�0021731�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include #include "libslic3r.h" #include "Slicing.hpp" #include "SlicingAdaptive.hpp" #include "PrintConfig.hpp" #include "Model.hpp" // #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG #ifdef SLIC3R_DEBUG #undef NDEBUG #define DEBUG #define _DEBUG #include "SVG.hpp" #undef assert #include #endif namespace Slic3r { static const coordf_t MIN_LAYER_HEIGHT = 0.01; static const coordf_t MIN_LAYER_HEIGHT_DEFAULT = 0.07; // Minimum layer height for the variable layer height algorithm. inline coordf_t min_layer_height_from_nozzle(const PrintConfig &print_config, int idx_nozzle) { coordf_t min_layer_height = print_config.min_layer_height.get_at(idx_nozzle - 1); return (min_layer_height == 0.) ? MIN_LAYER_HEIGHT_DEFAULT : std::max(MIN_LAYER_HEIGHT, min_layer_height); } // Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default, // it should not be smaller than the minimum layer height. inline coordf_t max_layer_height_from_nozzle(const PrintConfig &print_config, int idx_nozzle) { coordf_t min_layer_height = min_layer_height_from_nozzle(print_config, idx_nozzle); coordf_t max_layer_height = print_config.max_layer_height.get_at(idx_nozzle - 1); coordf_t nozzle_dmr = print_config.nozzle_diameter.get_at(idx_nozzle - 1); return std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height); } SlicingParameters SlicingParameters::create_from_config( const PrintConfig &print_config, const PrintObjectConfig &object_config, coordf_t object_height, const std::vector &object_extruders) { coordf_t first_layer_height = (object_config.first_layer_height.value <= 0) ? object_config.layer_height.value : object_config.first_layer_height.get_abs_value(object_config.layer_height.value); // If object_config.support_material_extruder == 0 resp. object_config.support_material_interface_extruder == 0, // print_config.nozzle_diameter.get_at(size_t(-1)) returns the 0th nozzle diameter, // which is consistent with the requirement that if support_material_extruder == 0 resp. support_material_interface_extruder == 0, // support will not trigger tool change, but it will use the current nozzle instead. // In that case all the nozzles have to be of the same diameter. coordf_t support_material_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_material_extruder.value - 1); coordf_t support_material_interface_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_material_interface_extruder.value - 1); bool soluble_interface = object_config.support_material_contact_distance.value == 0.; SlicingParameters params; params.layer_height = object_config.layer_height.value; params.first_print_layer_height = first_layer_height; params.first_object_layer_height = first_layer_height; params.object_print_z_min = 0.; params.object_print_z_max = object_height; params.base_raft_layers = object_config.raft_layers.value; params.soluble_interface = soluble_interface; // Miniumum/maximum of the minimum layer height over all extruders. params.min_layer_height = MIN_LAYER_HEIGHT; params.max_layer_height = std::numeric_limits::max(); if (object_config.support_material.value || params.base_raft_layers > 0) { // Has some form of support. Add the support layers to the minimum / maximum layer height limits. params.min_layer_height = std::max( min_layer_height_from_nozzle(print_config, object_config.support_material_extruder), min_layer_height_from_nozzle(print_config, object_config.support_material_interface_extruder)); params.max_layer_height = std::min( max_layer_height_from_nozzle(print_config, object_config.support_material_extruder), max_layer_height_from_nozzle(print_config, object_config.support_material_interface_extruder)); params.max_suport_layer_height = params.max_layer_height; } if (object_extruders.empty()) { params.min_layer_height = std::max(params.min_layer_height, min_layer_height_from_nozzle(print_config, 0)); params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, 0)); } else { for (unsigned int extruder_id : object_extruders) { params.min_layer_height = std::max(params.min_layer_height, min_layer_height_from_nozzle(print_config, extruder_id)); params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, extruder_id)); } } params.min_layer_height = std::min(params.min_layer_height, params.layer_height); params.max_layer_height = std::max(params.max_layer_height, params.layer_height); if (! soluble_interface) { params.gap_raft_object = object_config.support_material_contact_distance.value; params.gap_object_support = object_config.support_material_contact_distance.value; params.gap_support_object = object_config.support_material_contact_distance.value; } if (params.base_raft_layers > 0) { params.interface_raft_layers = (params.base_raft_layers + 1) / 2; params.base_raft_layers -= params.interface_raft_layers; // Use as large as possible layer height for the intermediate raft layers. params.base_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_extruder_dmr); params.interface_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_interface_extruder_dmr); params.contact_raft_layer_height_bridging = false; params.first_object_layer_bridging = false; #if 1 params.contact_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_interface_extruder_dmr); if (! soluble_interface) { // Compute the average of all nozzles used for printing the object over a raft. //FIXME It is expected, that the 1st layer of the object is printed with a bridging flow over a full raft. Shall it not be vice versa? coordf_t average_object_extruder_dmr = 0.; if (! object_extruders.empty()) { for (unsigned int extruder_id : object_extruders) average_object_extruder_dmr += print_config.nozzle_diameter.get_at(extruder_id); average_object_extruder_dmr /= coordf_t(object_extruders.size()); } params.first_object_layer_height = average_object_extruder_dmr; params.first_object_layer_bridging = true; } #else params.contact_raft_layer_height = soluble_interface ? support_material_interface_extruder_dmr : 0.75 * support_material_interface_extruder_dmr; params.contact_raft_layer_height_bridging = ! soluble_interface; ... #endif } if (params.has_raft()) { // Raise first object layer Z by the thickness of the raft itself plus the extra distance required by the support material logic. //FIXME The last raft layer is the contact layer, which shall be printed with a bridging flow for ease of separation. Currently it is not the case. if (params.raft_layers() == 1) { // There is only the contact layer. params.contact_raft_layer_height = first_layer_height; params.raft_contact_top_z = first_layer_height; } else { assert(params.base_raft_layers > 0); assert(params.interface_raft_layers > 0); // Number of the base raft layers is decreased by the first layer. params.raft_base_top_z = first_layer_height + coordf_t(params.base_raft_layers - 1) * params.base_raft_layer_height; // Number of the interface raft layers is decreased by the contact layer. params.raft_interface_top_z = params.raft_base_top_z + coordf_t(params.interface_raft_layers - 1) * params.interface_raft_layer_height; params.raft_contact_top_z = params.raft_interface_top_z + params.contact_raft_layer_height; } coordf_t print_z = params.raft_contact_top_z + params.gap_raft_object; params.object_print_z_min = print_z; params.object_print_z_max += print_z; } return params; } // Convert layer_height_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for // in the height profile and the printed object may be lifted by the raft thickness at the time of the G-code generation. std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, const t_layer_height_ranges &layer_height_ranges) { // 1) If there are any height ranges, trim one by the other to make them non-overlapping. Insert the 1st layer if fixed. std::vector> ranges_non_overlapping; ranges_non_overlapping.reserve(layer_height_ranges.size() * 4); if (slicing_params.first_object_layer_height_fixed()) ranges_non_overlapping.push_back(std::pair( t_layer_height_range(0., slicing_params.first_object_layer_height), slicing_params.first_object_layer_height)); // The height ranges are sorted lexicographically by low / high layer boundaries. for (t_layer_height_ranges::const_iterator it_range = layer_height_ranges.begin(); it_range != layer_height_ranges.end(); ++ it_range) { coordf_t lo = it_range->first.first; coordf_t hi = std::min(it_range->first.second, slicing_params.object_print_z_height()); coordf_t height = it_range->second; if (! ranges_non_overlapping.empty()) // Trim current low with the last high. lo = std::max(lo, ranges_non_overlapping.back().first.second); if (lo + EPSILON < hi) // Ignore too narrow ranges. ranges_non_overlapping.push_back(std::pair(t_layer_height_range(lo, hi), height)); } // 2) Convert the trimmed ranges to a height profile, fill in the undefined intervals between z=0 and z=slicing_params.object_print_z_max() // with slicing_params.layer_height std::vector layer_height_profile; for (std::vector>::const_iterator it_range = ranges_non_overlapping.begin(); it_range != ranges_non_overlapping.end(); ++ it_range) { coordf_t lo = it_range->first.first; coordf_t hi = it_range->first.second; coordf_t height = it_range->second; coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1]; if (lo > last_z + EPSILON) { // Insert a step of normal layer height. layer_height_profile.push_back(last_z); layer_height_profile.push_back(slicing_params.layer_height); layer_height_profile.push_back(lo); layer_height_profile.push_back(slicing_params.layer_height); } // Insert a step of the overriden layer height. layer_height_profile.push_back(lo); layer_height_profile.push_back(height); layer_height_profile.push_back(hi); layer_height_profile.push_back(height); } coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1]; if (last_z < slicing_params.object_print_z_height()) { // Insert a step of normal layer height up to the object top. layer_height_profile.push_back(last_z); layer_height_profile.push_back(slicing_params.layer_height); layer_height_profile.push_back(slicing_params.object_print_z_height()); layer_height_profile.push_back(slicing_params.layer_height); } return layer_height_profile; } // Based on the work of @platsch // Fill layer_height_profile by heights ensuring a prescribed maximum cusp height. std::vector layer_height_profile_adaptive( const SlicingParameters &slicing_params, const t_layer_height_ranges &layer_height_ranges, const ModelVolumePtrs &volumes) { // 1) Initialize the SlicingAdaptive class with the object meshes. SlicingAdaptive as; as.set_slicing_parameters(slicing_params); for (ModelVolumePtrs::const_iterator it = volumes.begin(); it != volumes.end(); ++ it) if (! (*it)->modifier) as.add_mesh(&(*it)->mesh); as.prepare(); // 2) Generate layers using the algorithm of @platsch // loop until we have at least one layer and the max slice_z reaches the object height //FIXME make it configurable // Cusp value: A maximum allowed distance from a corner of a rectangular extrusion to a chrodal line, in mm. const coordf_t cusp_value = 0.2; // $self->config->get_value('cusp_value'); std::vector layer_height_profile; layer_height_profile.push_back(0.); layer_height_profile.push_back(slicing_params.first_object_layer_height); if (slicing_params.first_object_layer_height_fixed()) { layer_height_profile.push_back(slicing_params.first_object_layer_height); layer_height_profile.push_back(slicing_params.first_object_layer_height); } coordf_t slice_z = slicing_params.first_object_layer_height; coordf_t height = slicing_params.first_object_layer_height; coordf_t cusp_height = 0.; int current_facet = 0; while ((slice_z - height) <= slicing_params.object_print_z_height()) { height = 999; // Slic3r::debugf "\n Slice layer: %d\n", $id; // determine next layer height coordf_t cusp_height = as.cusp_height(slice_z, cusp_value, current_facet); // check for horizontal features and object size /* if($self->config->get_value('match_horizontal_surfaces')) { my $horizontal_dist = $adaptive_slicing[$region_id]->horizontal_facet_distance(scale $slice_z+$cusp_height, $min_height); if(($horizontal_dist < $min_height) && ($horizontal_dist > 0)) { Slic3r::debugf "Horizontal feature ahead, distance: %f\n", $horizontal_dist; # can we shrink the current layer a bit? if($cusp_height-($min_height-$horizontal_dist) > $min_height) { # yes we can $cusp_height = $cusp_height-($min_height-$horizontal_dist); Slic3r::debugf "Shrink layer height to %f\n", $cusp_height; }else{ # no, current layer would become too thin $cusp_height = $cusp_height+$horizontal_dist; Slic3r::debugf "Widen layer height to %f\n", $cusp_height; } } } */ height = std::min(cusp_height, height); // apply z-gradation /* my $gradation = $self->config->get_value('adaptive_slicing_z_gradation'); if($gradation > 0) { $height = $height - unscale((scale($height)) % (scale($gradation))); } */ // look for an applicable custom range /* if (my $range = first { $_->[0] <= $slice_z && $_->[1] > $slice_z } @{$self->layer_height_ranges}) { $height = $range->[2]; # if user set custom height to zero we should just skip the range and resume slicing over it if ($height == 0) { $slice_z += $range->[1] - $range->[0]; next; } } */ layer_height_profile.push_back(slice_z); layer_height_profile.push_back(height); slice_z += height; layer_height_profile.push_back(slice_z); layer_height_profile.push_back(height); } coordf_t last = std::max(slicing_params.first_object_layer_height, layer_height_profile[layer_height_profile.size() - 2]); layer_height_profile.push_back(last); layer_height_profile.push_back(slicing_params.first_object_layer_height); layer_height_profile.push_back(slicing_params.object_print_z_height()); layer_height_profile.push_back(slicing_params.first_object_layer_height); return layer_height_profile; } void adjust_layer_height_profile( const SlicingParameters &slicing_params, std::vector &layer_height_profile, coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, LayerHeightEditActionType action) { // Constrain the profile variability by the 1st layer height. std::pair z_span_variable = std::pair( slicing_params.first_object_layer_height_fixed() ? slicing_params.first_object_layer_height : 0., slicing_params.object_print_z_height()); if (z < z_span_variable.first || z > z_span_variable.second) return; assert(layer_height_profile.size() >= 2); assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_height()) < EPSILON); // 1) Get the current layer thickness at z. coordf_t current_layer_height = slicing_params.layer_height; for (size_t i = 0; i < layer_height_profile.size(); i += 2) { if (i + 2 == layer_height_profile.size()) { current_layer_height = layer_height_profile[i + 1]; break; } else if (layer_height_profile[i + 2] > z) { coordf_t z1 = layer_height_profile[i]; coordf_t h1 = layer_height_profile[i + 1]; coordf_t z2 = layer_height_profile[i + 2]; coordf_t h2 = layer_height_profile[i + 3]; current_layer_height = lerp(h1, h2, (z - z1) / (z2 - z1)); break; } } // 2) Is it possible to apply the delta? switch (action) { case LAYER_HEIGHT_EDIT_ACTION_DECREASE: layer_thickness_delta = - layer_thickness_delta; // fallthrough case LAYER_HEIGHT_EDIT_ACTION_INCREASE: if (layer_thickness_delta > 0) { if (current_layer_height >= slicing_params.max_layer_height - EPSILON) return; layer_thickness_delta = std::min(layer_thickness_delta, slicing_params.max_layer_height - current_layer_height); } else { if (current_layer_height <= slicing_params.min_layer_height + EPSILON) return; layer_thickness_delta = std::max(layer_thickness_delta, slicing_params.min_layer_height - current_layer_height); } break; case LAYER_HEIGHT_EDIT_ACTION_REDUCE: case LAYER_HEIGHT_EDIT_ACTION_SMOOTH: layer_thickness_delta = std::abs(layer_thickness_delta); layer_thickness_delta = std::min(layer_thickness_delta, std::abs(slicing_params.layer_height - current_layer_height)); if (layer_thickness_delta < EPSILON) return; break; default: assert(false); break; } // 3) Densify the profile inside z +- band_width/2, remove duplicate Zs from the height profile inside the band. coordf_t lo = std::max(z_span_variable.first, z - 0.5 * band_width); // Do not limit the upper side of the band, so that the modifications to the top point of the profile will be allowed. coordf_t hi = z + 0.5 * band_width; coordf_t z_step = 0.1; size_t idx = 0; while (idx < layer_height_profile.size() && layer_height_profile[idx] < lo) idx += 2; idx -= 2; std::vector profile_new; profile_new.reserve(layer_height_profile.size()); assert(idx >= 0 && idx + 1 < layer_height_profile.size()); profile_new.insert(profile_new.end(), layer_height_profile.begin(), layer_height_profile.begin() + idx + 2); coordf_t zz = lo; size_t i_resampled_start = profile_new.size(); while (zz < hi) { size_t next = idx + 2; coordf_t z1 = layer_height_profile[idx]; coordf_t h1 = layer_height_profile[idx + 1]; coordf_t height = h1; if (next < layer_height_profile.size()) { coordf_t z2 = layer_height_profile[next]; coordf_t h2 = layer_height_profile[next + 1]; height = lerp(h1, h2, (zz - z1) / (z2 - z1)); } // Adjust height by layer_thickness_delta. coordf_t weight = std::abs(zz - z) < 0.5 * band_width ? (0.5 + 0.5 * cos(2. * M_PI * (zz - z) / band_width)) : 0.; coordf_t height_new = height; switch (action) { case LAYER_HEIGHT_EDIT_ACTION_INCREASE: case LAYER_HEIGHT_EDIT_ACTION_DECREASE: height += weight * layer_thickness_delta; break; case LAYER_HEIGHT_EDIT_ACTION_REDUCE: { coordf_t delta = height - slicing_params.layer_height; coordf_t step = weight * layer_thickness_delta; step = (std::abs(delta) > step) ? (delta > 0) ? -step : step : -delta; height += step; break; } case LAYER_HEIGHT_EDIT_ACTION_SMOOTH: { // Don't modify the profile during resampling process, do it at the next step. break; } default: assert(false); break; } height = clamp(slicing_params.min_layer_height, slicing_params.max_layer_height, height); if (zz == z_span_variable.second) { // This is the last point of the profile. if (profile_new[profile_new.size() - 2] + EPSILON > zz) { profile_new.pop_back(); profile_new.pop_back(); } profile_new.push_back(zz); profile_new.push_back(height); idx = layer_height_profile.size(); break; } // Avoid entering a too short segment. if (profile_new[profile_new.size() - 2] + EPSILON < zz) { profile_new.push_back(zz); profile_new.push_back(height); } // Limit zz to the object height, so the next iteration the last profile point will be set. zz = std::min(zz + z_step, z_span_variable.second); idx = next; while (idx < layer_height_profile.size() && layer_height_profile[idx] < zz) idx += 2; idx -= 2; } idx += 2; assert(idx > 0); size_t i_resampled_end = profile_new.size(); if (idx < layer_height_profile.size()) { assert(zz >= layer_height_profile[idx - 2]); assert(zz <= layer_height_profile[idx]); profile_new.insert(profile_new.end(), layer_height_profile.begin() + idx, layer_height_profile.end()); } else if (profile_new[profile_new.size() - 2] + 0.5 * EPSILON < z_span_variable.second) { profile_new.insert(profile_new.end(), layer_height_profile.end() - 2, layer_height_profile.end()); } layer_height_profile = std::move(profile_new); if (action == LAYER_HEIGHT_EDIT_ACTION_SMOOTH) { if (i_resampled_start == 0) ++ i_resampled_start; if (i_resampled_end == layer_height_profile.size()) i_resampled_end -= 2; size_t n_rounds = 6; for (size_t i_round = 0; i_round < n_rounds; ++ i_round) { profile_new = layer_height_profile; for (size_t i = i_resampled_start; i < i_resampled_end; i += 2) { coordf_t zz = profile_new[i]; coordf_t t = std::abs(zz - z) < 0.5 * band_width ? (0.25 + 0.25 * cos(2. * M_PI * (zz - z) / band_width)) : 0.; assert(t >= 0. && t <= 0.5000001); if (i == 0) layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + t * profile_new[i + 3]; else if (i + 1 == profile_new.size()) layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + t * profile_new[i - 1]; else layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + 0.5 * t * (profile_new[i - 1] + profile_new[i + 3]); } } } assert(layer_height_profile.size() > 2); assert(layer_height_profile.size() % 2 == 0); assert(layer_height_profile[0] == 0.); assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_height()) < EPSILON); #ifdef _DEBUG for (size_t i = 2; i < layer_height_profile.size(); i += 2) assert(layer_height_profile[i - 2] <= layer_height_profile[i]); for (size_t i = 1; i < layer_height_profile.size(); i += 2) { assert(layer_height_profile[i] > slicing_params.min_layer_height - EPSILON); assert(layer_height_profile[i] < slicing_params.max_layer_height + EPSILON); } #endif /* _DEBUG */ } // Produce object layers as pairs of low / high layer boundaries, stored into a linear vector. std::vector generate_object_layers( const SlicingParameters &slicing_params, const std::vector &layer_height_profile) { assert(! layer_height_profile.empty()); coordf_t print_z = 0; coordf_t height = 0; std::vector out; if (slicing_params.first_object_layer_height_fixed()) { out.push_back(0); print_z = slicing_params.first_object_layer_height; out.push_back(print_z); } size_t idx_layer_height_profile = 0; // loop until we have at least one layer and the max slice_z reaches the object height coordf_t slice_z = print_z + 0.5 * slicing_params.min_layer_height; while (slice_z < slicing_params.object_print_z_height()) { height = slicing_params.min_layer_height; if (idx_layer_height_profile < layer_height_profile.size()) { size_t next = idx_layer_height_profile + 2; for (;;) { if (next >= layer_height_profile.size() || slice_z < layer_height_profile[next]) break; idx_layer_height_profile = next; next += 2; } coordf_t z1 = layer_height_profile[idx_layer_height_profile]; coordf_t h1 = layer_height_profile[idx_layer_height_profile + 1]; height = h1; if (next < layer_height_profile.size()) { coordf_t z2 = layer_height_profile[next]; coordf_t h2 = layer_height_profile[next + 1]; height = lerp(h1, h2, (slice_z - z1) / (z2 - z1)); assert(height >= slicing_params.min_layer_height - EPSILON && height <= slicing_params.max_layer_height + EPSILON); } } slice_z = print_z + 0.5 * height; if (slice_z >= slicing_params.object_print_z_height()) break; assert(height > slicing_params.min_layer_height - EPSILON); assert(height < slicing_params.max_layer_height + EPSILON); out.push_back(print_z); print_z += height; slice_z = print_z + 0.5 * slicing_params.min_layer_height; out.push_back(print_z); } //FIXME Adjust the last layer to align with the top object layer exactly? return out; } int generate_layer_height_texture( const SlicingParameters &slicing_params, const std::vector &layers, void *data, int rows, int cols, bool level_of_detail_2nd_level) { // https://github.com/aschn/gnuplot-colorbrewer std::vector palette_raw; palette_raw.push_back(Point3(0x01A, 0x098, 0x050)); palette_raw.push_back(Point3(0x066, 0x0BD, 0x063)); palette_raw.push_back(Point3(0x0A6, 0x0D9, 0x06A)); palette_raw.push_back(Point3(0x0D9, 0x0F1, 0x0EB)); palette_raw.push_back(Point3(0x0FE, 0x0E6, 0x0EB)); palette_raw.push_back(Point3(0x0FD, 0x0AE, 0x061)); palette_raw.push_back(Point3(0x0F4, 0x06D, 0x043)); palette_raw.push_back(Point3(0x0D7, 0x030, 0x027)); // Clear the main texture and the 2nd LOD level. // memset(data, 0, rows * cols * (level_of_detail_2nd_level ? 5 : 4)); // 2nd LOD level data start unsigned char *data1 = reinterpret_cast(data) + rows * cols * 4; int ncells = std::min((cols-1) * rows, int(ceil(16. * (slicing_params.object_print_z_height() / slicing_params.min_layer_height)))); int ncells1 = ncells / 2; int cols1 = cols / 2; coordf_t z_to_cell = coordf_t(ncells-1) / slicing_params.object_print_z_height(); coordf_t cell_to_z = slicing_params.object_print_z_height() / coordf_t(ncells-1); coordf_t z_to_cell1 = coordf_t(ncells1-1) / slicing_params.object_print_z_height(); // for color scaling coordf_t hscale = 2.f * std::max(slicing_params.max_layer_height - slicing_params.layer_height, slicing_params.layer_height - slicing_params.min_layer_height); if (hscale == 0) // All layers have the same height. Provide some height scale to avoid division by zero. hscale = slicing_params.layer_height; for (size_t idx_layer = 0; idx_layer < layers.size(); idx_layer += 2) { coordf_t lo = layers[idx_layer]; coordf_t hi = layers[idx_layer + 1]; coordf_t mid = 0.5f * (lo + hi); assert(mid <= slicing_params.object_print_z_height()); coordf_t h = hi - lo; hi = std::min(hi, slicing_params.object_print_z_height()); int cell_first = clamp(0, ncells-1, int(ceil(lo * z_to_cell))); int cell_last = clamp(0, ncells-1, int(floor(hi * z_to_cell))); for (int cell = cell_first; cell <= cell_last; ++ cell) { coordf_t idxf = (0.5 * hscale + (h - slicing_params.layer_height)) * coordf_t(palette_raw.size()-1) / hscale; int idx1 = clamp(0, int(palette_raw.size() - 1), int(floor(idxf))); int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1); coordf_t t = idxf - coordf_t(idx1); const Point3 &color1 = palette_raw[idx1]; const Point3 &color2 = palette_raw[idx2]; coordf_t z = cell_to_z * coordf_t(cell); assert(z >= lo && z <= hi); // Intensity profile to visualize the layers. coordf_t intensity = cos(M_PI * 0.7 * (mid - z) / h); // Color mapping from layer height to RGB. Pointf3 color( intensity * lerp(coordf_t(color1.x), coordf_t(color2.x), t), intensity * lerp(coordf_t(color1.y), coordf_t(color2.y), t), intensity * lerp(coordf_t(color1.z), coordf_t(color2.z), t)); int row = cell / (cols - 1); int col = cell - row * (cols - 1); assert(row >= 0 && row < rows); assert(col >= 0 && col < cols); unsigned char *ptr = (unsigned char*)data + (row * cols + col) * 4; ptr[0] = (unsigned char)clamp(0, 255, int(floor(color.x + 0.5))); ptr[1] = (unsigned char)clamp(0, 255, int(floor(color.y + 0.5))); ptr[2] = (unsigned char)clamp(0, 255, int(floor(color.z + 0.5))); ptr[3] = 255; if (col == 0 && row > 0) { // Duplicate the first value in a row as a last value of the preceding row. ptr[-4] = ptr[0]; ptr[-3] = ptr[1]; ptr[-2] = ptr[2]; ptr[-1] = ptr[3]; } } if (level_of_detail_2nd_level) { cell_first = clamp(0, ncells1-1, int(ceil(lo * z_to_cell1))); cell_last = clamp(0, ncells1-1, int(floor(hi * z_to_cell1))); for (int cell = cell_first; cell <= cell_last; ++ cell) { coordf_t idxf = (0.5 * hscale + (h - slicing_params.layer_height)) * coordf_t(palette_raw.size()-1) / hscale; int idx1 = clamp(0, int(palette_raw.size() - 1), int(floor(idxf))); int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1); coordf_t t = idxf - coordf_t(idx1); const Point3 &color1 = palette_raw[idx1]; const Point3 &color2 = palette_raw[idx2]; // Color mapping from layer height to RGB. Pointf3 color( lerp(coordf_t(color1.x), coordf_t(color2.x), t), lerp(coordf_t(color1.y), coordf_t(color2.y), t), lerp(coordf_t(color1.z), coordf_t(color2.z), t)); int row = cell / (cols1 - 1); int col = cell - row * (cols1 - 1); assert(row >= 0 && row < rows/2); assert(col >= 0 && col < cols/2); unsigned char *ptr = data1 + (row * cols1 + col) * 4; ptr[0] = (unsigned char)clamp(0, 255, int(floor(color.x + 0.5))); ptr[1] = (unsigned char)clamp(0, 255, int(floor(color.y + 0.5))); ptr[2] = (unsigned char)clamp(0, 255, int(floor(color.z + 0.5))); ptr[3] = 255; if (col == 0 && row > 0) { // Duplicate the first value in a row as a last value of the preceding row. ptr[-4] = ptr[0]; ptr[-3] = ptr[1]; ptr[-2] = ptr[2]; ptr[-1] = ptr[3]; } } } } // Returns number of cells of the 0th LOD level. return ncells; } }; // namespace Slic3r �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/Slicing.hpp��������������������������������������������������0000664�0000000�0000000�00000017410�13243544447�0021736�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Based on implementation by @platsch #ifndef slic3r_Slicing_hpp_ #define slic3r_Slicing_hpp_ #include #include #include "libslic3r.h" namespace Slic3r { class PrintConfig; class PrintObjectConfig; class ModelVolume; typedef std::vector ModelVolumePtrs; // Parameters to guide object slicing and support generation. // The slicing parameters account for a raft and whether the 1st object layer is printed with a normal or a bridging flow // (using a normal flow over a soluble support, using a bridging flow over a non-soluble support). struct SlicingParameters { SlicingParameters() { memset(this, 0, sizeof(SlicingParameters)); } static SlicingParameters create_from_config( const PrintConfig &print_config, const PrintObjectConfig &object_config, coordf_t object_height, const std::vector &object_extruders); // Has any raft layers? bool has_raft() const { return raft_layers() > 0; } size_t raft_layers() const { return base_raft_layers + interface_raft_layers; } // Is the 1st object layer height fixed, or could it be varied? bool first_object_layer_height_fixed() const { return ! has_raft() || first_object_layer_bridging; } // Height of the object to be printed. This value does not contain the raft height. coordf_t object_print_z_height() const { return object_print_z_max - object_print_z_min; } // Number of raft layers. size_t base_raft_layers; // Number of interface layers including the contact layer. size_t interface_raft_layers; // Layer heights of the raft (base, interface and a contact layer). coordf_t base_raft_layer_height; coordf_t interface_raft_layer_height; coordf_t contact_raft_layer_height; bool contact_raft_layer_height_bridging; // The regular layer height, applied for all but the first layer, if not overridden by layer ranges // or by the variable layer thickness table. coordf_t layer_height; // Minimum / maximum layer height, to be used for the automatic adaptive layer height algorithm, // or by an interactive layer height editor. coordf_t min_layer_height; coordf_t max_layer_height; coordf_t max_suport_layer_height; // First layer height of the print, this may be used for the first layer of the raft // or for the first layer of the print. coordf_t first_print_layer_height; // Thickness of the first layer. This is either the first print layer thickness if printed without a raft, // or a bridging flow thickness if printed over a non-soluble raft, // or a normal layer height if printed over a soluble raft. coordf_t first_object_layer_height; // If the object is printed over a non-soluble raft, the first layer may be printed with a briding flow. bool first_object_layer_bridging; // Soluble interface? (PLA soluble in water, HIPS soluble in lemonen) // otherwise the interface must be broken off. bool soluble_interface; // Gap when placing object over raft. coordf_t gap_raft_object; // Gap when placing support over object. coordf_t gap_object_support; // Gap when placing object over support. coordf_t gap_support_object; // Bottom and top of the printed object. // If printed without a raft, object_print_z_min = 0 and object_print_z_max = object height. // Otherwise object_print_z_min is equal to the raft height. coordf_t raft_base_top_z; coordf_t raft_interface_top_z; coordf_t raft_contact_top_z; // In case of a soluble interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer. coordf_t object_print_z_min; coordf_t object_print_z_max; }; // The two slicing parameters lead to the same layering as long as the variable layer thickness is not in action. inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters &sp2) { return sp1.base_raft_layers == sp2.base_raft_layers && sp1.interface_raft_layers == sp2.interface_raft_layers && sp1.base_raft_layer_height == sp2.base_raft_layer_height && sp1.interface_raft_layer_height == sp2.interface_raft_layer_height && sp1.contact_raft_layer_height == sp2.contact_raft_layer_height && sp1.contact_raft_layer_height_bridging == sp2.contact_raft_layer_height_bridging && sp1.layer_height == sp2.layer_height && sp1.min_layer_height == sp2.min_layer_height && sp1.max_layer_height == sp2.max_layer_height && // sp1.max_suport_layer_height == sp2.max_suport_layer_height && sp1.first_print_layer_height == sp2.first_print_layer_height && sp1.first_object_layer_height == sp2.first_object_layer_height && sp1.first_object_layer_bridging == sp2.first_object_layer_bridging && sp1.soluble_interface == sp2.soluble_interface && sp1.gap_raft_object == sp2.gap_raft_object && sp1.gap_object_support == sp2.gap_object_support && sp1.gap_support_object == sp2.gap_support_object && sp1.raft_base_top_z == sp2.raft_base_top_z && sp1.raft_interface_top_z == sp2.raft_interface_top_z && sp1.raft_contact_top_z == sp2.raft_contact_top_z && sp1.object_print_z_min == sp2.object_print_z_min; } typedef std::pair t_layer_height_range; typedef std::map t_layer_height_ranges; extern std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, const t_layer_height_ranges &layer_height_ranges); extern std::vector layer_height_profile_adaptive( const SlicingParameters &slicing_params, const t_layer_height_ranges &layer_height_ranges, const ModelVolumePtrs &volumes); enum LayerHeightEditActionType { LAYER_HEIGHT_EDIT_ACTION_INCREASE = 0, LAYER_HEIGHT_EDIT_ACTION_DECREASE = 1, LAYER_HEIGHT_EDIT_ACTION_REDUCE = 2, LAYER_HEIGHT_EDIT_ACTION_SMOOTH = 3 }; extern void adjust_layer_height_profile( const SlicingParameters &slicing_params, std::vector &layer_height_profile, coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, LayerHeightEditActionType action); // Produce object layers as pairs of low / high layer boundaries, stored into a linear vector. // The object layers are based at z=0, ignoring the raft layers. extern std::vector generate_object_layers( const SlicingParameters &slicing_params, const std::vector &layer_height_profile); // Produce a 1D texture packed into a 2D texture describing in the RGBA format // the planned object layers. // Returns number of cells used by the texture of the 0th LOD level. extern int generate_layer_height_texture( const SlicingParameters &slicing_params, const std::vector &layers, void *data, int rows, int cols, bool level_of_detail_2nd_level); }; // namespace Slic3r #endif /* slic3r_Slicing_hpp_ */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/SlicingAdaptive.cpp������������������������������������������0000664�0000000�0000000�00000012002�13243544447�0023377�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "libslic3r.h" #include "TriangleMesh.hpp" #include "SlicingAdaptive.hpp" namespace Slic3r { void SlicingAdaptive::clear() { m_meshes.clear(); m_faces.clear(); m_face_normal_z.clear(); } std::pair face_z_span(const stl_facet *f) { return std::pair( std::min(std::min(f->vertex[0].z, f->vertex[1].z), f->vertex[2].z), std::max(std::max(f->vertex[0].z, f->vertex[1].z), f->vertex[2].z)); } void SlicingAdaptive::prepare() { // 1) Collect faces of all meshes. int nfaces_total = 0; for (std::vector::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh) nfaces_total += (*it_mesh)->stl.stats.number_of_facets; m_faces.reserve(nfaces_total); for (std::vector::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh) for (int i = 0; i < (*it_mesh)->stl.stats.number_of_facets; ++ i) m_faces.push_back((*it_mesh)->stl.facet_start + i); // 2) Sort faces lexicographically by their Z span. std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) { std::pair span1 = face_z_span(f1); std::pair span2 = face_z_span(f2); return span1 < span2; }); // 3) Generate Z components of the facet normals. m_face_normal_z.assign(m_faces.size(), 0.f); for (size_t iface = 0; iface < m_faces.size(); ++ iface) m_face_normal_z[iface] = m_faces[iface]->normal.z; } float SlicingAdaptive::cusp_height(float z, float cusp_value, int ¤t_facet) { float height = m_slicing_params.max_layer_height; bool first_hit = false; // find all facets intersecting the slice-layer int ordered_id = current_facet; for (; ordered_id < int(m_faces.size()); ++ ordered_id) { std::pair zspan = face_z_span(m_faces[ordered_id]); // facet's minimum is higher than slice_z -> end loop if (zspan.first >= z) break; // facet's maximum is higher than slice_z -> store the first event for next cusp_height call to begin at this point if (zspan.second > z) { // first event? if (! first_hit) { first_hit = true; current_facet = ordered_id; } // skip touching facets which could otherwise cause small cusp values if (zspan.second <= z + EPSILON) continue; // compute cusp-height for this facet and store minimum of all heights float normal_z = m_face_normal_z[ordered_id]; height = std::min(height, (normal_z == 0.f) ? 9999.f : std::abs(cusp_value / normal_z)); } } // lower height limit due to printer capabilities height = std::max(height, float(m_slicing_params.min_layer_height)); // check for sloped facets inside the determined layer and correct height if necessary if (height > m_slicing_params.min_layer_height) { for (; ordered_id < int(m_faces.size()); ++ ordered_id) { std::pair zspan = face_z_span(m_faces[ordered_id]); // facet's minimum is higher than slice_z + height -> end loop if (zspan.first >= z + height) break; // skip touching facets which could otherwise cause small cusp values if (zspan.second <= z + EPSILON) continue; // Compute cusp-height for this facet and check against height. float normal_z = m_face_normal_z[ordered_id]; float cusp = (normal_z == 0) ? 9999 : abs(cusp_value / normal_z); float z_diff = zspan.first - z; // handle horizontal facets if (m_face_normal_z[ordered_id] > 0.999) { // Slic3r::debugf "cusp computation, height is reduced from %f", $height; height = z_diff; // Slic3r::debugf "to %f due to near horizontal facet\n", $height; } else if (cusp > z_diff) { if (cusp < height) { // Slic3r::debugf "cusp computation, height is reduced from %f", $height; height = cusp; // Slic3r::debugf "to %f due to new cusp height\n", $height; } } else { // Slic3r::debugf "cusp computation, height is reduced from %f", $height; height = z_diff; // Slic3r::debugf "to z-diff: %f\n", $height; } } // lower height limit due to printer capabilities again height = std::max(height, float(m_slicing_params.min_layer_height)); } // Slic3r::debugf "cusp computation, layer-bottom at z:%f, cusp_value:%f, resulting layer height:%f\n", unscale $z, $cusp_value, $height; return height; } // Returns the distance to the next horizontal facet in Z-dir // to consider horizontal object features in slice thickness float SlicingAdaptive::horizontal_facet_distance(float z) { for (size_t i = 0; i < m_faces.size(); ++ i) { std::pair zspan = face_z_span(m_faces[i]); // facet's minimum is higher than max forward distance -> end loop if (zspan.first > z + m_slicing_params.max_layer_height) break; // min_z == max_z -> horizontal facet if (zspan.first > z && zspan.first == zspan.second) return zspan.first - z; } // objects maximum? return (z + m_slicing_params.max_layer_height > m_slicing_params.object_print_z_height()) ? std::max(m_slicing_params.object_print_z_height() - z, 0.f) : m_slicing_params.max_layer_height; } }; // namespace Slic3r ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/SlicingAdaptive.hpp������������������������������������������0000664�0000000�0000000�00000001614�13243544447�0023413�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Based on implementation by @platsch #ifndef slic3r_SlicingAdaptive_hpp_ #define slic3r_SlicingAdaptive_hpp_ #include "Slicing.hpp" #include "admesh/stl.h" namespace Slic3r { class TriangleMesh; class SlicingAdaptive { public: void clear(); void set_slicing_parameters(SlicingParameters params) { m_slicing_params = params; } void add_mesh(const TriangleMesh *mesh) { m_meshes.push_back(mesh); } void prepare(); float cusp_height(float z, float cusp_value, int ¤t_facet); float horizontal_facet_distance(float z); protected: SlicingParameters m_slicing_params; std::vector m_meshes; // Collected faces of all meshes, sorted by raising Z of the bottom most face. std::vector m_faces; // Z component of face normals, normalized. std::vector m_face_normal_z; }; }; // namespace Slic3r #endif /* slic3r_SlicingAdaptive_hpp_ */ ��������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/SupportMaterial.cpp������������������������������������������0000664�0000000�0000000�00000522136�13243544447�0023502�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "ClipperUtils.hpp" #include "ExtrusionEntityCollection.hpp" #include "PerimeterGenerator.hpp" #include "Layer.hpp" #include "Print.hpp" #include "SupportMaterial.hpp" #include "Fill/FillBase.hpp" #include "EdgeGrid.hpp" #include "Geometry.hpp" #include #include #include #include #include #include #include // #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG #ifdef SLIC3R_DEBUG #define DEBUG #define _DEBUG #undef NDEBUG #include "SVG.hpp" #endif // #undef NDEBUG #include namespace Slic3r { // Increment used to reach MARGIN in steps to avoid trespassing thin objects #define NUM_MARGIN_STEPS 3 // Dimensions of a tree-like structure to save material #define PILLAR_SIZE (2.5) #define PILLAR_SPACING 10 //#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. //#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 #define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. #ifdef SLIC3R_DEBUG const char* support_surface_type_to_color_name(const PrintObjectSupportMaterial::SupporLayerType surface_type) { switch (surface_type) { case PrintObjectSupportMaterial::sltTopContact: return "rgb(255,0,0)"; // "red"; case PrintObjectSupportMaterial::sltTopInterface: return "rgb(0,255,0)"; // "green"; case PrintObjectSupportMaterial::sltBase: return "rgb(0,0,255)"; // "blue"; case PrintObjectSupportMaterial::sltBottomInterface:return "rgb(255,255,128)"; // yellow case PrintObjectSupportMaterial::sltBottomContact: return "rgb(255,0,255)"; // magenta case PrintObjectSupportMaterial::sltRaftInterface: return "rgb(0,255,255)"; case PrintObjectSupportMaterial::sltRaftBase: return "rgb(128,128,128)"; case PrintObjectSupportMaterial::sltUnknown: return "rgb(128,0,0)"; // maroon default: return "rgb(64,64,64)"; }; } Point export_support_surface_type_legend_to_svg_box_size() { return Point(scale_(1.+10.*8.), scale_(3.)); } void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos) { // 1st row coord_t pos_x0 = pos.x + scale_(1.); coord_t pos_x = pos_x0; coord_t pos_y = pos.y + scale_(1.5); coord_t step_x = scale_(10.); svg.draw_legend(Point(pos_x, pos_y), "top contact" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltTopContact)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "top iface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltTopInterface)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "base" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBase)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "bottom iface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBottomInterface)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "bottom contact" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBottomContact)); // 2nd row pos_x = pos_x0; pos_y = pos.y+scale_(2.8); svg.draw_legend(Point(pos_x, pos_y), "raft interface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltRaftInterface)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "raft base" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltRaftBase)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "unknown" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltUnknown)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "intermediate" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltIntermediate)); } void export_print_z_polygons_to_svg(const char *path, PrintObjectSupportMaterial::MyLayer ** const layers, size_t n_layers) { BoundingBox bbox; for (int i = 0; i < n_layers; ++ i) bbox.merge(get_extents(layers[i]->polygons)); Point legend_size = export_support_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min.x, bbox.max.y); bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); SVG svg(path, bbox); const float transparency = 0.5f; for (int i = 0; i < n_layers; ++ i) svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); for (int i = 0; i < n_layers; ++ i) svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); export_support_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } void export_print_z_polygons_and_extrusions_to_svg( const char *path, PrintObjectSupportMaterial::MyLayer ** const layers, size_t n_layers, SupportLayer &support_layer) { BoundingBox bbox; for (int i = 0; i < n_layers; ++ i) bbox.merge(get_extents(layers[i]->polygons)); Point legend_size = export_support_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min.x, bbox.max.y); bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); SVG svg(path, bbox); const float transparency = 0.5f; for (int i = 0; i < n_layers; ++ i) svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); for (int i = 0; i < n_layers; ++ i) svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); Polygons polygons_support, polygons_interface; support_layer.support_fills.polygons_covered_by_width(polygons_support, SCALED_EPSILON); // support_layer.support_interface_fills.polygons_covered_by_width(polygons_interface, SCALED_EPSILON); svg.draw(union_ex(polygons_support), "brown"); svg.draw(union_ex(polygons_interface), "black"); export_support_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } #endif /* SLIC3R_DEBUG */ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : m_object (object), m_print_config (&object->print()->config), m_object_config (&object->config), m_slicing_params (slicing_params), m_first_layer_flow (support_material_1st_layer_flow(object, float(slicing_params.first_print_layer_height))), m_support_material_flow (support_material_flow(object, float(slicing_params.layer_height))), m_support_material_interface_flow(support_material_interface_flow(object, float(slicing_params.layer_height))), m_support_layer_height_min(0.01) { // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um. m_support_layer_height_min = 1000000.; for (auto lh : m_print_config->min_layer_height.values) m_support_layer_height_min = std::min(m_support_layer_height_min, std::max(0.01, lh)); if (m_object_config->support_material_interface_layers.value == 0) { // No interface layers allowed, print everything with the base support pattern. m_support_material_interface_flow = m_support_material_flow; } // Evaluate the XY gap between the object outer perimeters and the support structures. coordf_t external_perimeter_width = 0.; for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) { if (! object->region_volumes[region_id].empty()) { const PrintRegionConfig &config = object->print()->get_region(region_id)->config; coordf_t width = config.external_perimeter_extrusion_width.get_abs_value(slicing_params.layer_height); if (width <= 0.) width = m_print_config->nozzle_diameter.get_at(config.perimeter_extruder-1); external_perimeter_width = std::max(external_perimeter_width, width); } } m_gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width); m_can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value; if (! m_can_merge_support_regions && (m_object_config->support_material_extruder.value == 0 || m_object_config->support_material_interface_extruder.value == 0)) { // One of the support extruders is of "don't care" type. auto object_extruders = m_object->print()->object_extruders(); if (object_extruders.size() == 1 && *object_extruders.begin() == std::max(m_object_config->support_material_extruder.value, m_object_config->support_material_interface_extruder.value)) // Object is printed with the same extruder as the support. m_can_merge_support_regions = true; } } // Using the std::deque as an allocator. inline PrintObjectSupportMaterial::MyLayer& layer_allocate( std::deque &layer_storage, PrintObjectSupportMaterial::SupporLayerType layer_type) { layer_storage.push_back(PrintObjectSupportMaterial::MyLayer()); layer_storage.back().layer_type = layer_type; return layer_storage.back(); } inline PrintObjectSupportMaterial::MyLayer& layer_allocate( std::deque &layer_storage, tbb::spin_mutex &layer_storage_mutex, PrintObjectSupportMaterial::SupporLayerType layer_type) { layer_storage_mutex.lock(); layer_storage.push_back(PrintObjectSupportMaterial::MyLayer()); PrintObjectSupportMaterial::MyLayer *layer_new = &layer_storage.back(); layer_storage_mutex.unlock(); layer_new->layer_type = layer_type; return *layer_new; } inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const PrintObjectSupportMaterial::MyLayersPtr &src) { dst.insert(dst.end(), src.begin(), src.end()); } // Compare layers lexicographically. struct MyLayersPtrCompare { bool operator()(const PrintObjectSupportMaterial::MyLayer* layer1, const PrintObjectSupportMaterial::MyLayer* layer2) const { return *layer1 < *layer2; } }; void PrintObjectSupportMaterial::generate(PrintObject &object) { BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; coordf_t max_object_layer_height = 0.; for (size_t i = 0; i < object.layer_count(); ++ i) max_object_layer_height = std::max(max_object_layer_height, object.layers[i]->height); // Layer instances will be allocated by std::deque and they will be kept until the end of this function call. // The layers will be referenced by various LayersPtr (of type std::vector) MyLayerStorage layer_storage; BOOST_LOG_TRIVIAL(info) << "Support generator - Creating top contacts"; // Determine the top contact 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. // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette without holes. MyLayersPtr top_contacts = this->top_contact_layers(object, layer_storage); if (top_contacts.empty()) // Nothing is supported, no supports are generated. return; #ifdef SLIC3R_DEBUG static int iRun = 0; iRun ++; for (MyLayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) Slic3r::SVG::export_expolygons( debug_out_path("support-top-contacts-%d-%lf.svg", iRun, (*it)->print_z), union_ex((*it)->polygons, false)); #endif /* SLIC3R_DEBUG */ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts"; // Determine the bottom contact surfaces of the supports over the top surfaces of the object. // Depending on whether the support is soluble or not, the contact layer thickness is decided. // layer_support_areas contains the per object layer support areas. These per object layer support areas // may get merged and trimmed by this->generate_base_layers() if the support layers are not synchronized with object layers. std::vector layer_support_areas; MyLayersPtr bottom_contacts = this->bottom_contact_layers_and_layer_support_areas( object, top_contacts, layer_storage, layer_support_areas); #ifdef SLIC3R_DEBUG for (size_t layer_id = 0; layer_id < object.layers.size(); ++ layer_id) Slic3r::SVG::export_expolygons( debug_out_path("support-areas-%d-%lf.svg", iRun, object.layers[layer_id]->print_z), union_ex(layer_support_areas[layer_id], false)); #endif /* SLIC3R_DEBUG */ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating intermediate layers - indices"; // Allocate empty layers between the top / bottom support contact layers // as placeholders for the base and intermediate support layers. // The layers may or may not be synchronized with the object layers, depending on the configuration. // For example, a single nozzle multi material printing will need to generate a waste tower, which in turn // wastes less material, if there are as little tool changes as possible. MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( object, bottom_contacts, top_contacts, layer_storage); this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers"; // Fill in intermediate layers between the top / bottom support contact layers, trimm them by the object. this->generate_base_layers(object, bottom_contacts, top_contacts, intermediate_layers, layer_support_areas); #ifdef SLIC3R_DEBUG for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++ it) Slic3r::SVG::export_expolygons( debug_out_path("support-base-layers-%d-%lf.svg", iRun, (*it)->print_z), union_ex((*it)->polygons, false)); #endif /* SLIC3R_DEBUG */ BOOST_LOG_TRIVIAL(info) << "Support generator - Trimming top contacts by bottom contacts"; // Because the top and bottom contacts are thick slabs, they may overlap causing over extrusion // and unwanted strong bonds to the object. // Rather trim the top contacts by their overlapping bottom contacts to leave a gap instead of over extruding // top contacts over the bottom contacts. this->trim_top_contacts_by_bottom_contacts(object, bottom_contacts, top_contacts); BOOST_LOG_TRIVIAL(info) << "Support generator - Creating interfaces"; // Propagate top / bottom contact layers to generate interface layers. MyLayersPtr interface_layers = this->generate_interface_layers( bottom_contacts, top_contacts, intermediate_layers, layer_storage); BOOST_LOG_TRIVIAL(info) << "Support generator - Creating raft"; // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette with holes filled. // There is also a 1st intermediate layer containing bases of support columns. // Inflate the bases of the support columns and create the raft base under the object. MyLayersPtr raft_layers = this->generate_raft_base(top_contacts, interface_layers, intermediate_layers, layer_storage); #ifdef SLIC3R_DEBUG for (MyLayersPtr::const_iterator it = interface_layers.begin(); it != interface_layers.end(); ++ it) Slic3r::SVG::export_expolygons( debug_out_path("support-interface-layers-%d-%lf.svg", iRun, (*it)->print_z), union_ex((*it)->polygons, false)); #endif /* SLIC3R_DEBUG */ /* // Clip with the pillars. if (! shape.empty()) { this->clip_with_shape(interface, shape); this->clip_with_shape(base, shape); } */ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating layers"; // For debugging purposes, one may want to show only some of the support extrusions. // raft_layers.clear(); // bottom_contacts.clear(); // top_contacts.clear(); // intermediate_layers.clear(); // interface_layers.clear(); // Install support layers into the object. // A support layer installed on a PrintObject has a unique print_z. MyLayersPtr layers_sorted; layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size()); layers_append(layers_sorted, raft_layers); layers_append(layers_sorted, bottom_contacts); layers_append(layers_sorted, top_contacts); layers_append(layers_sorted, intermediate_layers); layers_append(layers_sorted, interface_layers); // Sort the layers lexicographically by a raising print_z and a decreasing height. std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare()); int layer_id = 0; assert(object.support_layers.empty()); for (int i = 0; i < int(layers_sorted.size());) { // Find the last layer with roughly the same print_z, find the minimum layer height of all. // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. int j = i + 1; coordf_t zmax = layers_sorted[i]->print_z + EPSILON; for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; // Assign an average print_z to the set of layers with nearly equal print_z. coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); coordf_t height_min = layers_sorted[i]->height; bool empty = true; for (int u = i; u < j; ++u) { MyLayer &layer = *layers_sorted[u]; if (! layer.polygons.empty()) empty = false; layer.print_z = zavg; height_min = std::min(height_min, layer.height); } if (! empty) { // Here the upper_layer and lower_layer pointers are left to null at the support layers, // as they are never used. These pointers are candidates for removal. object.add_support_layer(layer_id ++, height_min, zavg); } i = j; } BOOST_LOG_TRIVIAL(info) << "Support generator - Generating tool paths"; // Generate the actual toolpaths and save them into each layer. this->generate_toolpaths(object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers); #ifdef SLIC3R_DEBUG { size_t layer_id = 0; for (int i = 0; i < int(layers_sorted.size());) { // Find the last layer with roughly the same print_z, find the minimum layer height of all. // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. int j = i + 1; coordf_t zmax = layers_sorted[i]->print_z + EPSILON; bool empty = true; for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) if (! layers_sorted[j]->polygons.empty()) empty = false; if (! empty) { export_print_z_polygons_to_svg( debug_out_path("support-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), layers_sorted.data() + i, j - i); export_print_z_polygons_and_extrusions_to_svg( debug_out_path("support-w-fills-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(), layers_sorted.data() + i, j - i, *object.support_layers[layer_id]); ++layer_id; } i = j; } } #endif /* SLIC3R_DEBUG */ BOOST_LOG_TRIVIAL(info) << "Support generator - End"; } // Collect all polygons of all regions in a layer with a given surface type. Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type) { // 1) Count the new polygons first. size_t n_polygons_new = 0; for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) { const LayerRegion ®ion = *(*it_region); const SurfaceCollection &slices = region.slices; for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) { const Surface &surface = *it; if (surface.surface_type == surface_type) n_polygons_new += surface.expolygon.holes.size() + 1; } } // 2) Collect the new polygons. Polygons out; out.reserve(n_polygons_new); for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) { const LayerRegion ®ion = *(*it_region); const SurfaceCollection &slices = region.slices; for (Surfaces::const_iterator it = slices.surfaces.begin(); it != slices.surfaces.end(); ++ it) { const Surface &surface = *it; if (surface.surface_type == surface_type) polygons_append(out, surface.expolygon); } } return out; } // Collect outer contours of all slices of this layer. // This is useful for calculating the support base with holes filled. Polygons collect_slices_outer(const Layer &layer) { Polygons out; out.reserve(out.size() + layer.slices.expolygons.size()); for (ExPolygons::const_iterator it = layer.slices.expolygons.begin(); it != layer.slices.expolygons.end(); ++ it) out.push_back(it->contour); return out; } class SupportGridPattern { public: SupportGridPattern( const Polygons &support_polygons, const Polygons &trimming_polygons, coordf_t support_spacing, coordf_t support_angle) : m_support_polygons(&support_polygons), m_trimming_polygons(&trimming_polygons), m_support_spacing(support_spacing), m_support_angle(support_angle) { if (m_support_angle != 0.) { // Create a copy of the rotated contours. m_support_polygons_rotated = support_polygons; m_trimming_polygons_rotated = trimming_polygons; m_support_polygons = &m_support_polygons_rotated; m_trimming_polygons = &m_trimming_polygons_rotated; polygons_rotate(m_support_polygons_rotated, - support_angle); polygons_rotate(m_trimming_polygons_rotated, - support_angle); } // Create an EdgeGrid, initialize it with projection, initialize signed distance field. coord_t grid_resolution = coord_t(scale_(m_support_spacing)); BoundingBox bbox = get_extents(*m_support_polygons); bbox.offset(20); bbox.align_to_grid(grid_resolution); m_grid.set_bbox(bbox); m_grid.create(*m_support_polygons, grid_resolution); m_grid.calculate_sdf(); // Extract a bounding contour from the grid, trim by the object. m_island_samples = island_samples(*m_support_polygons); } // Extract polygons from the grid, offsetted by offset_in_grid, // and trim the extracted polygons by trimming_polygons. // Trimming by the trimming_polygons may split the extracted polygons into pieces. // Remove all the pieces, which do not contain any of the island_samples. Polygons extract_support(const coord_t offset_in_grid) { // Generate islands, so each island may be tested for overlap with m_island_samples. ExPolygons islands = diff_ex( m_grid.contours_simplified(offset_in_grid), *m_trimming_polygons, false); // Extract polygons, which contain some of the m_island_samples. Polygons out; std::vector> samples_inside; for (ExPolygon &island : islands) { BoundingBox bbox = get_extents(island.contour); auto it_lower = std::lower_bound(m_island_samples.begin(), m_island_samples.end(), bbox.min - Point(1, 1)); auto it_upper = std::upper_bound(m_island_samples.begin(), m_island_samples.end(), bbox.max + Point(1, 1)); samples_inside.clear(); for (auto it = it_lower; it != it_upper; ++ it) if (bbox.contains(*it)) samples_inside.push_back(std::make_pair(*it, false)); if (! samples_inside.empty()) { // For all samples_inside count the boundary crossing. for (size_t i_contour = 0; i_contour <= island.holes.size(); ++ i_contour) { Polygon &contour = (i_contour == 0) ? island.contour : island.holes[i_contour - 1]; Points::const_iterator i = contour.points.begin(); Points::const_iterator j = contour.points.end() - 1; for (; i != contour.points.end(); j = i ++) { //FIXME this test is not numerically robust. Particularly, it does not handle horizontal segments at y == point.y well. // Does the ray with y == point.y intersect this line segment? for (auto &sample_inside : samples_inside) { if ((i->y > sample_inside.first.y) != (j->y > sample_inside.first.y)) { double x1 = (double)sample_inside.first.x; double x2 = (double)i->x + (double)(j->x - i->x) * (double)(sample_inside.first.y - i->y) / (double)(j->y - i->y); if (x1 < x2) sample_inside.second = !sample_inside.second; } } } } // If any of the sample is inside this island, add this island to the output. for (auto &sample_inside : samples_inside) if (sample_inside.second) { polygons_append(out, std::move(island)); island.clear(); break; } } } #ifdef SLIC3R_DEBUG static int iRun = 0; ++iRun; BoundingBox bbox = get_extents(*m_trimming_polygons); if (! islands.empty()) bbox.merge(get_extents(islands)); if (!out.empty()) bbox.merge(get_extents(out)); SVG svg(debug_out_path("extract_support_from_grid_trimmed-%d.svg", iRun).c_str(), bbox); svg.draw(islands, "red", 0.5f); svg.draw(union_ex(out), "green", 0.5f); svg.draw(union_ex(*m_support_polygons), "blue", 0.5f); svg.draw_outline(islands, "red", "red", scale_(0.05)); svg.draw_outline(union_ex(out), "green", "green", scale_(0.05)); svg.draw_outline(union_ex(*m_support_polygons), "blue", "blue", scale_(0.05)); for (const Point &pt : m_island_samples) svg.draw(pt, "black", coord_t(scale_(0.15))); svg.Close(); #endif /* SLIC3R_DEBUG */ if (m_support_angle != 0.) polygons_rotate(out, m_support_angle); return out; } private: SupportGridPattern& operator=(const SupportGridPattern &rhs); // Get some internal point of an expolygon, to be used as a representative // sample to test, whether this island is inside another island. static Point island_sample(const ExPolygon &expoly) { // Find the lowest point lexicographically. const Point *pt_min = &expoly.contour.points.front(); for (size_t i = 1; i < expoly.contour.points.size(); ++ i) if (expoly.contour.points[i] < *pt_min) pt_min = &expoly.contour.points[i]; // Lowest corner will always be convex, in worst case denegenerate with zero angle. const Point &p1 = (pt_min == &expoly.contour.points.front()) ? expoly.contour.points.back() : *(pt_min - 1); const Point &p2 = *pt_min; const Point &p3 = (pt_min == &expoly.contour.points.back()) ? expoly.contour.points.front() : *(pt_min + 1); Vector v = (p3 - p2) + (p1 - p2); double l2 = double(v.x)*double(v.x)+double(v.y)*double(v.y); if (l2 == 0.) return p2; double coef = 20. / sqrt(l2); return Point(p2.x + coef * v.x, p2.y + coef * v.y); } static Points island_samples(const ExPolygons &expolygons) { Points pts; pts.reserve(expolygons.size()); for (const ExPolygon &expoly : expolygons) if (expoly.contour.points.size() > 2) { #if 0 pts.push_back(island_sample(expoly)); #else Polygons polygons = offset(expoly, - 20.f); for (const Polygon &poly : polygons) if (! poly.points.empty()) { pts.push_back(poly.points.front()); break; } #endif } // Sort the points lexicographically, so a binary search could be used to locate points inside a bounding box. std::sort(pts.begin(), pts.end()); return pts; } static Points island_samples(const Polygons &polygons) { return island_samples(union_ex(polygons)); } const Polygons *m_support_polygons; const Polygons *m_trimming_polygons; Polygons m_support_polygons_rotated; Polygons m_trimming_polygons_rotated; // Angle in radians, by which the whole support is rotated. coordf_t m_support_angle; // X spacing of the support lines parallel with the Y axis. coordf_t m_support_spacing; Slic3r::EdgeGrid::Grid m_grid; Points m_island_samples; }; // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers( const PrintObject &object, MyLayerStorage &layer_storage) const { #ifdef SLIC3R_DEBUG static int iRun = 0; ++ iRun; #endif /* SLIC3R_DEBUG */ // Output layers, sorted by top Z. MyLayersPtr contact_out; // If user specified a custom angle threshold, convert it to radians. // Zero means automatic overhang detection. const double threshold_rad = (m_object_config->support_material_threshold.value > 0) ? M_PI * double(m_object_config->support_material_threshold.value + 1) / 180. : // +1 makes the threshold inclusive 0.; // Build support on a build plate only? If so, then collect and union all the surfaces below the current layer. // Unfortunately this is an inherently serial process. const bool buildplate_only = this->build_plate_only(); std::vector buildplate_covered; if (buildplate_only) { BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() - collecting regions covering the print bed."; buildplate_covered.assign(object.layers.size(), Polygons()); for (size_t layer_id = 1; layer_id < object.layers.size(); ++ layer_id) { const Layer &lower_layer = *object.layers[layer_id-1]; // Merge the new slices with the preceding slices. // 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. Polygons &covered = buildplate_covered[layer_id]; covered = buildplate_covered[layer_id - 1]; polygons_append(covered, offset(lower_layer.slices.expolygons, scale_(0.01))); covered = union_(covered, false); // don't apply the safety offset. } } BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - start"; // Determine top contact areas. // If generating raft only (no support), only calculate top contact areas for the 0th layer. // If having a raft, start with 0th layer, otherwise with 1st layer. // Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers. // So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers. size_t num_layers = this->has_support() ? object.layer_count() : 1; contact_out.assign(num_layers, nullptr); tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(this->has_raft() ? 0 : 1, num_layers), [this, &object, &buildplate_covered, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out](const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { const Layer &layer = *object.layers[layer_id]; // Detect overhangs and contact areas needed to support them. // Collect overhangs and contacts of all regions of this layer supported by the layer immediately below. Polygons overhang_polygons; Polygons contact_polygons; Polygons slices_margin_cached; float slices_margin_cached_offset = -1.; if (layer_id == 0) { // This is the first object layer, so the object is being printed on a raft and // 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. overhang_polygons = collect_slices_outer(layer); // Extend by SUPPORT_MATERIAL_MARGIN, which is 1.5mm contact_polygons = offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN)); } else { // Generate overhang / contact_polygons for non-raft layers. const Layer &lower_layer = *object.layers[layer_id-1]; for (LayerRegion *layerm : layer.regions) { // Extrusion width accounts for the roundings of the extrudates. // It is the maximum widh of the extrudate. float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); float lower_layer_offset = (layer_id < this->m_object_config->support_material_enforce_layers.value) ? // Enforce a full possible support, ignore the overhang angle. 0.f : (threshold_rad > 0. ? // Overhang defined by an angle. float(scale_(lower_layer.height / tan(threshold_rad))) : // Overhang defined by half the extrusion width. 0.5f * fw); // Overhang polygons for this layer and region. Polygons diff_polygons; Polygons layerm_polygons = to_polygons(layerm->slices); Polygons lower_layer_polygons = to_polygons(lower_layer.slices.expolygons); if (lower_layer_offset == 0.f) { // Support everything. diff_polygons = diff(layerm_polygons, lower_layer_polygons); if (! buildplate_covered.empty()) { // Don't support overhangs above the top surfaces. // This step is done before the contact surface is calculated by growing the overhang region. diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); } } else { // Get the regions needing a suport, collapse very tiny spots. //FIXME cache the lower layer offset if this layer has multiple regions. diff_polygons = offset2( diff(layerm_polygons, offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)), -0.1f*fw, +0.1f*fw); if (! buildplate_covered.empty()) { // Don't support overhangs above the top surfaces. // This step is done before the contact surface is calculated by growing the overhang region. diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]); } if (diff_polygons.empty()) continue; // Offset the support regions back to a full overhang, restrict them to the full overhang. diff_polygons = diff( intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), lower_layer_polygons); } if (diff_polygons.empty()) continue; #ifdef SLIC3R_DEBUG { ::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg", iRun, layer_id, std::find_if(layer.regions.begin(), layer.regions.end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions.begin()), get_extents(diff_polygons)); Slic3r::ExPolygons expolys = union_ex(diff_polygons, false); svg.draw(expolys); } #endif /* SLIC3R_DEBUG */ if (this->m_object_config->dont_support_bridges) { // compute the area of bridging perimeters // Note: this is duplicate code from GCode.pm, we need to refactor if (true) { Polygons bridged_perimeters; { Flow bridge_flow = layerm->flow(frPerimeter, true); coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at(layerm->region()->config.perimeter_extruder-1); Polygons lower_grown_slices = offset(lower_layer_polygons, 0.5f*float(scale_(nozzle_diameter)), SUPPORT_SURFACES_OFFSET_PARAMETERS); // Collect perimeters of this layer. // TODO: split_at_first_point() could split a bridge mid-way Polylines overhang_perimeters; for (ExtrusionEntity* extrusion_entity : layerm->perimeters.entities) { const ExtrusionEntityCollection *island = dynamic_cast(extrusion_entity); assert(island != NULL); for (size_t i = 0; i < island->entities.size(); ++ i) { ExtrusionEntity *entity = island->entities[i]; ExtrusionLoop *loop = dynamic_cast(entity); overhang_perimeters.push_back(loop ? loop->as_polyline() : dynamic_cast(entity)->polyline); } } // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() for (Polyline &polyline : overhang_perimeters) polyline.points[0].x += 1; // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); // only consider straight overhangs // only consider overhangs having endpoints inside layer's slices // convert bridging polylines into polygons by inflating them with their thickness // since we're dealing with bridges, we can't assume width is larger than spacing, // so we take the largest value and also apply safety offset to be ensure no gaps // are left in between float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); for (Polyline &polyline : overhang_perimeters) if (polyline.is_straight()) { // This is a bridge polyline.extend_start(fw); polyline.extend_end(fw); // Is the straight perimeter segment supported at both sides? if (layer.slices.contains(polyline.first_point()) && layer.slices.contains(polyline.last_point())) // Offset a polyline into a thick line. polygons_append(bridged_perimeters, offset(polyline, 0.5f * w + 10.f)); } bridged_perimeters = union_(bridged_perimeters); } // remove the entire bridges and only support the unsupported edges Polygons bridges; for (const Surface &surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) polygons_append(bridges, surface.expolygon); diff_polygons = diff(diff_polygons, bridges, true); polygons_append(bridges, bridged_perimeters); polygons_append(diff_polygons, intersection( // Offset unsupported edges into polygons. offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), bridges)); } else { // just remove bridged areas diff_polygons = diff(diff_polygons, layerm->bridged, true); } } // if (m_objconfig->dont_support_bridges) if (diff_polygons.empty()) continue; #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-top-contacts-filtered-run%d-layer%d-region%d-z%f.svg", iRun, layer_id, std::find_if(layer.regions.begin(), layer.regions.end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions.begin(), layer.print_z), union_ex(diff_polygons, false)); #endif /* SLIC3R_DEBUG */ if (this->has_contact_loops()) polygons_append(overhang_polygons, diff_polygons); // 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). { //FIMXE 1) Make the offset configurable, 2) Make the Z span configurable. float slices_margin_offset = std::min(lower_layer_offset, float(scale_(m_gap_xy))); if (slices_margin_cached_offset != slices_margin_offset) { slices_margin_cached_offset = slices_margin_offset; slices_margin_cached = (slices_margin_offset == 0.f) ? to_polygons(lower_layer.slices.expolygons) : offset(lower_layer.slices.expolygons, slices_margin_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! buildplate_covered.empty()) { // Trim the inflated contact surfaces by the top surfaces as well. polygons_append(slices_margin_cached, buildplate_covered[layer_id]); slices_margin_cached = union_(slices_margin_cached); } } // Offset the contact polygons outside. for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) { diff_polygons = diff( offset( diff_polygons, SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS, ClipperLib::jtRound, // round mitter limit scale_(0.05)), slices_margin_cached); } } polygons_append(contact_polygons, diff_polygons); } // for each layer.region } // end of Generate overhang/contact_polygons for non-raft layers. // now apply the contact areas to the layer were they need to be made if (! contact_polygons.empty()) { // get the average nozzle diameter used on this layer MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, sltTopContact); new_layer.idx_object_layer_above = layer_id; if (m_slicing_params.soluble_interface) { // Align the contact surface height with a layer immediately below the supported layer. new_layer.print_z = layer.print_z - layer.height; if (layer_id == 0) { // This is a raft contact layer sitting directly on the print bed. new_layer.height = m_slicing_params.contact_raft_layer_height; new_layer.bottom_z = m_slicing_params.raft_interface_top_z; } else { // Interface layer will be synchronized with the object. assert(layer_id > 0); new_layer.height = object.layers[layer_id - 1]->height; new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers[layer_id - 2]->print_z; } } else { // Contact layer will be printed with a normal flow, but // it will support layers printed with a bridging flow. //FIXME Probably printing with the bridge flow? How about the unsupported perimeters? Are they printed with the bridging flow? // In the future we may switch to a normal extrusion flow for the supported bridges. // Get the average nozzle diameter used on this layer. coordf_t nozzle_dmr = 0.; for (const LayerRegion *region : layer.regions) nozzle_dmr += region->region()->nozzle_dmr_avg(*m_print_config); nozzle_dmr /= coordf_t(layer.regions.size()); new_layer.print_z = layer.print_z - nozzle_dmr - m_object_config->support_material_contact_distance; new_layer.bottom_z = new_layer.print_z; new_layer.height = 0.; if (layer_id == 0) { // This is a raft contact layer sitting directly on the print bed. assert(this->has_raft()); new_layer.bottom_z = m_slicing_params.raft_interface_top_z; new_layer.height = m_slicing_params.contact_raft_layer_height; } else { // Ignore this contact area if it's too low. // Don't want to print a layer below the first layer height as it may not stick well. //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact // and it may actually make sense to do it with a thinner layer than the first layer height. if (new_layer.print_z < m_slicing_params.first_print_layer_height - EPSILON) { // This contact layer is below the first layer height, therefore not printable. Don't support this surface. continue; } else if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) { // Align the layer with the 1st layer height. new_layer.print_z = m_slicing_params.first_print_layer_height; new_layer.bottom_z = 0; new_layer.height = m_slicing_params.first_print_layer_height; } else { // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and // its height will be set adaptively later on. } } } SupportGridPattern support_grid_pattern( // Support islands, to be stretched into a grid. contact_polygons, // Trimming polygons, to trim the stretched support islands. slices_margin_cached, // How much to offset the extracted contour outside of the grid. m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); // 1) infill polygons, expand them by half the extrusion width + a tiny bit of extra. new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5); // 2) Contact polygons will be projected down. To keep the interface and base layers to grow, return a contour a tiny bit smaller than the grid cells. new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3)); // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons. // Store the overhang polygons. // The overhang polygons are used in the path generator for planning of the contact loops. // if (this->has_contact_loops()) new_layer.overhang_polygons = new Polygons(std::move(overhang_polygons)); contact_out[layer_id] = &new_layer; } } }); // Compress contact_out, remove the nullptr items. remove_nulls(contact_out); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end"; return contact_out; } // Generate bottom contact layers supporting the top contact layers. // For a soluble interface material synchronize the layer heights with the object, // otherwise set the layer height to a bridging flow of a support interface nozzle. PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_layer_support_areas( const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage, std::vector &layer_support_areas) const { #ifdef SLIC3R_DEBUG static int iRun = 0; ++ iRun; #endif /* SLIC3R_DEBUG */ // Allocate empty surface areas, one per object layer. layer_support_areas.assign(object.total_layer_count(), Polygons()); // find object top surfaces // we'll use them to clip our support and detect where does it stick MyLayersPtr bottom_contacts; if (! top_contacts.empty()) { // There is some support to be built, if there are non-empty top surfaces detected. // Sum of unsupported contact areas above the current layer.print_z. Polygons projection; // Last top contact layer visited when collecting the projection of contact areas. int contact_idx = int(top_contacts.size()) - 1; for (int layer_id = int(object.total_layer_count()) - 2; layer_id >= 0; -- layer_id) { BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id; const Layer &layer = *object.get_layer(layer_id); // Collect projections of all contact areas above or at the same level as this top surface. for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z >= layer.print_z; -- contact_idx) { Polygons polygons_new; // Contact surfaces are expanded away from the object, trimmed by the object. // Use a slight positive offset to overlap the touching regions. #if 0 // Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form. polygons_append(polygons_new, offset(*top_contacts[contact_idx]->contact_polygons, SCALED_EPSILON)); #else // Consume the contact_polygons. The contact polygons are already expanded into a grid form. polygons_append(polygons_new, std::move(*top_contacts[contact_idx]->contact_polygons)); #endif // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. // Use a slight positive offset to overlap the touching regions. polygons_append(polygons_new, offset(*top_contacts[contact_idx]->overhang_polygons, float(SCALED_EPSILON))); polygons_append(projection, union_(polygons_new)); } if (projection.empty()) continue; Polygons projection_raw = union_(projection); // Top surfaces of this layer, to be used to stop the surface volume from growing down. tbb::task_group task_group; if (! m_object_config->support_material_buildplate_only) task_group.run([this, &object, &top_contacts, contact_idx, &layer, layer_id, &layer_storage, &layer_support_areas, &bottom_contacts, &projection_raw] { Polygons top = collect_region_slices_by_type(layer, stTop); #ifdef SLIC3R_DEBUG { BoundingBox bbox = get_extents(projection_raw); bbox.merge(get_extents(top)); ::Slic3r::SVG svg(debug_out_path("support-bottom-layers-raw-%d-%lf.svg", iRun, layer.print_z), bbox); svg.draw(union_ex(top, false), "blue", 0.5f); svg.draw(union_ex(projection_raw, true), "red", 0.5f); svg.draw_outline(union_ex(projection_raw, true), "red", "blue", scale_(0.1f)); svg.draw(layer.slices.expolygons, "green", 0.5f); } #endif /* SLIC3R_DEBUG */ // Now find whether any projection of the contact surfaces above layer.print_z not yet supported by any // top surfaces above layer.print_z falls onto this top surface. // Touching are the contact surfaces supported exclusively by this top surfaces. // Don't use a safety offset as it has been applied during insertion of polygons. if (! top.empty()) { Polygons touching = intersection(top, projection_raw, false); if (! touching.empty()) { // Allocate a new bottom contact layer. MyLayer &layer_new = layer_allocate(layer_storage, sltBottomContact); bottom_contacts.push_back(&layer_new); // 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 layer_new.height = m_slicing_params.soluble_interface ? // Align the interface layer with the object's layer height. object.layers[layer_id + 1]->height : // Place a bridge flow interface layer over the top surface. m_support_material_interface_flow.nozzle_diameter; layer_new.print_z = m_slicing_params.soluble_interface ? object.layers[layer_id + 1]->print_z : layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value; layer_new.bottom_z = layer.print_z; layer_new.idx_object_layer_below = layer_id; layer_new.bridging = ! m_slicing_params.soluble_interface; //FIXME how much to inflate the top surface? layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (! m_slicing_params.soluble_interface) { // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface, // so there will be no support surfaces generated with thickness lower than m_support_layer_height_min. for (size_t top_idx = size_t(std::max(0, contact_idx)); top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + this->m_support_layer_height_min; ++ top_idx) { if (top_contacts[top_idx]->print_z > layer_new.print_z - this->m_support_layer_height_min) { // A top layer has been found, which is close to the new bottom layer. coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z; assert(std::abs(diff) <= this->m_support_layer_height_min); if (diff > 0.) { // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer. assert(diff < layer_new.height + EPSILON); assert(layer_new.height - diff >= this->m_support_layer_height_min - EPSILON); layer_new.print_z = top_contacts[top_idx]->print_z; layer_new.height -= diff; } else { // The top contact layer is above this layer. One may either make this layer thicker or thinner. // By making the layer thicker, one will decrease the number of discrete layers with the price of extruding a bit too thick bridges. // By making the layer thinner, one adds one more discrete layer. layer_new.print_z = top_contacts[top_idx]->print_z; layer_new.height -= diff; } break; } } } #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer_new.print_z), union_ex(layer_new.polygons, false)); #endif /* SLIC3R_DEBUG */ // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. touching = offset(touching, float(SCALED_EPSILON)); for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) { const Layer &layer_above = *object.layers[layer_id_above]; if (layer_above.print_z > layer_new.print_z + EPSILON) break; if (! layer_support_areas[layer_id_above].empty()) { #ifdef SLIC3R_DEBUG { BoundingBox bbox = get_extents(touching); bbox.merge(get_extents(layer_support_areas[layer_id_above])); ::Slic3r::SVG svg(debug_out_path("support-support-areas-raw-before-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), bbox); svg.draw(union_ex(touching, false), "blue", 0.5f); svg.draw(union_ex(layer_support_areas[layer_id_above], true), "red", 0.5f); svg.draw_outline(union_ex(layer_support_areas[layer_id_above], true), "red", "blue", scale_(0.1f)); } #endif /* SLIC3R_DEBUG */ layer_support_areas[layer_id_above] = diff(layer_support_areas[layer_id_above], touching); #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-support-areas-raw-after-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), union_ex(layer_support_areas[layer_id_above], false)); #endif /* SLIC3R_DEBUG */ } } } } // ! top.empty() }); Polygons &layer_support_area = layer_support_areas[layer_id]; task_group.run([this, &projection, &projection_raw, &layer, &layer_support_area, layer_id] { // Remove the areas that touched from the projection that will continue on next, lower, top surfaces. // Polygons trimming = union_(to_polygons(layer.slices.expolygons), touching, true); Polygons trimming = offset(layer.slices.expolygons, float(SCALED_EPSILON)); projection = diff(projection_raw, trimming, false); #ifdef SLIC3R_DEBUG { BoundingBox bbox = get_extents(projection_raw); bbox.merge(get_extents(trimming)); ::Slic3r::SVG svg(debug_out_path("support-support-areas-raw-%d-%lf.svg", iRun, layer.print_z), bbox); svg.draw(union_ex(trimming, false), "blue", 0.5f); svg.draw(union_ex(projection, true), "red", 0.5f); svg.draw_outline(union_ex(projection, true), "red", "blue", scale_(0.1f)); } #endif /* SLIC3R_DEBUG */ remove_sticks(projection); remove_degenerate(projection); #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-support-areas-raw-cleaned-%d-%lf.svg", iRun, layer.print_z), union_ex(projection, false)); #endif /* SLIC3R_DEBUG */ SupportGridPattern support_grid_pattern( // Support islands, to be stretched into a grid. projection, // Trimming polygons, to trim the stretched support islands. trimming, // How much to offset the extracted contour outside of the grid. m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); tbb::task_group task_group_inner; // 1) Cache the slice of a support volume. The support volume is expanded by 1/2 of support material flow spacing // to allow a placement of suppot zig-zag snake along the grid lines. task_group_inner.run([this, &support_grid_pattern, &layer_support_area #ifdef SLIC3R_DEBUG , &layer #endif /* SLIC3R_DEBUG */ ] { layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25); #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-layer_support_area-gridded-%d-%lf.svg", iRun, layer.print_z), union_ex(layer_support_area, false)); #endif /* SLIC3R_DEBUG */ }); // 2) Support polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells. Polygons projection_new; task_group_inner.run([&projection_new, &support_grid_pattern #ifdef SLIC3R_DEBUG , &layer #endif /* SLIC3R_DEBUG */ ] { projection_new = support_grid_pattern.extract_support(-5); #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), union_ex(projection_new, false)); #endif /* SLIC3R_DEBUG */ }); task_group_inner.wait(); projection = std::move(projection_new); }); task_group.wait(); } std::reverse(bottom_contacts.begin(), bottom_contacts.end()); trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy); } // ! top_contacts.empty() return bottom_contacts; } // FN_HIGHER_EQUAL: the provided object pointer has a Z value >= of an internal threshold. // Find the first item with Z value >= of an internal threshold of fn_higher_equal. // If no vec item with Z value >= of an internal threshold of fn_higher_equal is found, return vec.size() // If the initial idx is size_t(-1), then use binary search. // Otherwise search linearly upwards. template size_t idx_higher_or_equal(const std::vector &vec, size_t idx, FN_HIGHER_EQUAL fn_higher_equal) { if (vec.empty()) { idx = 0; } else if (idx == size_t(-1)) { // First of the batch of layers per thread pool invocation. Use binary search. int idx_low = 0; int idx_high = std::max(0, int(vec.size()) - 1); while (idx_low + 1 < idx_high) { int idx_mid = (idx_low + idx_high) / 2; if (fn_higher_equal(vec[idx_mid])) idx_high = idx_mid; else idx_low = idx_mid; } idx = fn_higher_equal(vec[idx_low]) ? idx_low : (fn_higher_equal(vec[idx_high]) ? idx_high : vec.size()); } else { // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search. while (idx < vec.size() && ! fn_higher_equal(vec[idx])) ++ idx; } return idx; } // FN_LOWER_EQUAL: the provided object pointer has a Z value <= of an internal threshold. // Find the first item with Z value <= of an internal threshold of fn_lower_equal. // If no vec item with Z value <= of an internal threshold of fn_lower_equal is found, return -1. // If the initial idx is < -1, then use binary search. // Otherwise search linearly downwards. template int idx_lower_or_equal(const std::vector &vec, int idx, FN_LOWER_EQUAL fn_lower_equal) { if (vec.empty()) { idx = -1; } else if (idx < -1) { // First of the batch of layers per thread pool invocation. Use binary search. int idx_low = 0; int idx_high = std::max(0, int(vec.size()) - 1); while (idx_low + 1 < idx_high) { int idx_mid = (idx_low + idx_high) / 2; if (fn_lower_equal(vec[idx_mid])) idx_low = idx_mid; else idx_high = idx_mid; } idx = fn_lower_equal(vec[idx_high]) ? idx_high : (fn_lower_equal(vec[idx_low ]) ? idx_low : -1); } else { // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search. while (idx >= 0 && ! fn_lower_equal(vec[idx])) -- idx; } return idx; } // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts( const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const { tbb::parallel_for(tbb::blocked_range(0, int(top_contacts.size())), [this, &object, &bottom_contacts, &top_contacts](const tbb::blocked_range& range) { int idx_bottom_overlapping_first = -2; // For all top contact layers, counting downwards due to the way idx_higher_or_equal caches the last index to avoid repeated binary search. for (int idx_top = range.end() - 1; idx_top >= range.begin(); -- idx_top) { MyLayer &layer_top = *top_contacts[idx_top]; // Find the first bottom layer overlapping with layer_top. idx_bottom_overlapping_first = idx_lower_or_equal(bottom_contacts, idx_bottom_overlapping_first, [&layer_top](const MyLayer *layer_bottom){ return layer_bottom->bottom_print_z() - EPSILON <= layer_top.bottom_z; }); // For all top contact layers overlapping with the thick bottom contact layer: for (int idx_bottom_overlapping = idx_bottom_overlapping_first; idx_bottom_overlapping >= 0; -- idx_bottom_overlapping) { const MyLayer &layer_bottom = *bottom_contacts[idx_bottom_overlapping]; assert(layer_bottom.bottom_print_z() - EPSILON <= layer_top.bottom_z); if (layer_top.print_z < layer_bottom.print_z + EPSILON) { // Layers overlap. Trim layer_top with layer_bottom. layer_top.polygons = diff(layer_top.polygons, layer_bottom.polygons); } else break; } } }); } PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_support_layers( const PrintObject &object, const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage) const { MyLayersPtr intermediate_layers; // Collect and sort the extremes (bottoms of the top contacts and tops of the bottom contacts). MyLayersPtr extremes; extremes.reserve(top_contacts.size() + bottom_contacts.size()); for (size_t i = 0; i < top_contacts.size(); ++ i) // Bottoms of the top contact layers. In case of non-soluble supports, // the top contact layer thickness is not known yet. extremes.push_back(top_contacts[i]); for (size_t i = 0; i < bottom_contacts.size(); ++ i) // Tops of the bottom contact layers. extremes.push_back(bottom_contacts[i]); if (extremes.empty()) return intermediate_layers; auto layer_extreme_lower = [](const MyLayer *l1, const MyLayer *l2) { coordf_t z1 = l1->extreme_z(); coordf_t z2 = l2->extreme_z(); // If the layers are aligned, return the top contact surface first. return z1 < z2 || (z1 == z2 && l1->layer_type == PrintObjectSupportMaterial::sltTopContact && l2->layer_type == PrintObjectSupportMaterial::sltBottomContact); }; std::sort(extremes.begin(), extremes.end(), layer_extreme_lower); assert(extremes.empty() || (extremes.front()->extreme_z() > m_slicing_params.raft_interface_top_z - EPSILON && (m_slicing_params.raft_layers() == 1 || // only raft contact layer extremes.front()->layer_type == sltTopContact || // first extreme is a top contact layer extremes.front()->extreme_z() > m_slicing_params.first_print_layer_height - EPSILON))); bool synchronize = this->synchronize_layers(); #ifdef _DEBUG // Verify that the extremes are separated by m_support_layer_height_min. for (size_t i = 1; i < extremes.size(); ++ i) { assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() == 0. || extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > this->m_support_layer_height_min - EPSILON); assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > 0. || extremes[i]->layer_type == extremes[i-1]->layer_type || (extremes[i]->layer_type == sltBottomContact && extremes[i - 1]->layer_type == sltTopContact)); } #endif // Generate intermediate layers. // The first intermediate layer is the same as the 1st layer if there is no raft, // or the bottom of the first intermediate layer is aligned with the bottom of the raft contact layer. // Intermediate layers are always printed with a normal etrusion flow (non-bridging). size_t idx_layer_object = 0; for (size_t idx_extreme = 0; idx_extreme < extremes.size(); ++ idx_extreme) { MyLayer *extr2 = extremes[idx_extreme]; coordf_t extr2z = extr2->extreme_z(); if (std::abs(extr2z - m_slicing_params.raft_interface_top_z) < EPSILON) { // This is a raft contact layer, its height has been decided in this->top_contact_layers(). assert(extr2->layer_type == sltTopContact); continue; } if (std::abs(extr2z - m_slicing_params.first_print_layer_height) < EPSILON) { // This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers(). assert(extr2->layer_type == sltTopContact); assert(extr2->bottom_z == m_slicing_params.first_print_layer_height); assert(extr2->print_z >= m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON); if (intermediate_layers.empty() || intermediate_layers.back()->print_z < m_slicing_params.first_print_layer_height) { MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.bottom_z = 0.; layer_new.print_z = m_slicing_params.first_print_layer_height; layer_new.height = m_slicing_params.first_print_layer_height; intermediate_layers.push_back(&layer_new); } continue; } assert(extr2z >= m_slicing_params.raft_interface_top_z + EPSILON); assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); MyLayer *extr1 = (idx_extreme == 0) ? nullptr : extremes[idx_extreme - 1]; // Fuse a support layer firmly to the raft top interface (not to the raft contacts). coordf_t extr1z = (extr1 == nullptr) ? m_slicing_params.raft_interface_top_z : extr1->extreme_z(); assert(extr2z >= extr1z); assert(extr2z > extr1z || (extr1 != nullptr && extr2->layer_type == sltBottomContact)); if (std::abs(extr1z) < EPSILON) { // This layer interval starts with the 1st layer. Print the 1st layer using the prescribed 1st layer thickness. assert(! m_slicing_params.has_raft()); assert(intermediate_layers.empty() || intermediate_layers.back()->print_z <= m_slicing_params.first_print_layer_height); // At this point only layers above first_print_layer_heigth + EPSILON are expected as the other cases were captured earlier. assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON); // Generate a new intermediate layer. MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.bottom_z = 0.; layer_new.print_z = extr1z = m_slicing_params.first_print_layer_height; layer_new.height = extr1z; intermediate_layers.push_back(&layer_new); // Continue printing the other layers up to extr2z. } coordf_t dist = extr2z - extr1z; assert(dist >= 0.); if (dist == 0.) continue; // The new layers shall be at least m_support_layer_height_min thick. assert(dist >= m_support_layer_height_min - EPSILON); if (synchronize) { // Emit support layers synchronized with the object layers. // Find the first object layer, which has its print_z in this support Z range. while (idx_layer_object < object.layers.size() && object.layers[idx_layer_object]->print_z < extr1z + EPSILON) ++ idx_layer_object; if (idx_layer_object == 0 && extr1z == m_slicing_params.raft_interface_top_z) { // Insert one base support layer below the object. MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.print_z = m_slicing_params.object_print_z_min; layer_new.bottom_z = m_slicing_params.raft_interface_top_z; layer_new.height = layer_new.print_z - layer_new.bottom_z; intermediate_layers.push_back(&layer_new); } // Emit all intermediate support layers synchronized with object layers up to extr2z. for (; idx_layer_object < object.layers.size() && object.layers[idx_layer_object]->print_z < extr2z + EPSILON; ++ idx_layer_object) { MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.print_z = object.layers[idx_layer_object]->print_z; layer_new.height = object.layers[idx_layer_object]->height; layer_new.bottom_z = (idx_layer_object > 0) ? object.layers[idx_layer_object - 1]->print_z : (layer_new.print_z - layer_new.height); assert(intermediate_layers.empty() || intermediate_layers.back()->print_z < layer_new.print_z + EPSILON); intermediate_layers.push_back(&layer_new); } } else { // Insert intermediate layers. size_t n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height)); assert(n_layers_extra > 0); coordf_t step = dist / coordf_t(n_layers_extra); if (extr1 != nullptr && extr1->layer_type == sltTopContact && extr1->print_z + this->m_support_layer_height_min > extr1->bottom_z + step) { // The bottom extreme is a bottom of a top surface. Ensure that the gap // between the 1st intermediate layer print_z and extr1->print_z is not too small. assert(extr1->bottom_z + this->m_support_layer_height_min < extr1->print_z + EPSILON); // Generate the first intermediate layer. MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.bottom_z = extr1->bottom_z; layer_new.print_z = extr1z = extr1->print_z; layer_new.height = extr1->height; intermediate_layers.push_back(&layer_new); dist = extr2z - extr1z; n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height)); if (n_layers_extra == 0) continue; // Continue printing the other layers up to extr2z. step = dist / coordf_t(n_layers_extra); } if (! m_slicing_params.soluble_interface && extr2->layer_type == sltTopContact) { // This is a top interface layer, which does not have a height assigned yet. Do it now. assert(extr2->height == 0.); assert(extr1z > m_slicing_params.first_print_layer_height - EPSILON); extr2->height = step; extr2->bottom_z = extr2z = extr2->print_z - step; if (-- n_layers_extra == 0) continue; } coordf_t extr2z_large_steps = extr2z; // Take the largest allowed step in the Z axis until extr2z_large_steps is reached. for (size_t i = 0; i < n_layers_extra; ++ i) { MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); if (i + 1 == n_layers_extra) { // Last intermediate layer added. Align the last entered layer with extr2z_large_steps exactly. layer_new.bottom_z = (i == 0) ? extr1z : intermediate_layers.back()->print_z; layer_new.print_z = extr2z_large_steps; layer_new.height = layer_new.print_z - layer_new.bottom_z; } else { // Intermediate layer, not the last added. layer_new.height = step; layer_new.bottom_z = extr1z + i * step; layer_new.print_z = layer_new.bottom_z + step; } assert(intermediate_layers.empty() || intermediate_layers.back()->print_z <= layer_new.print_z); intermediate_layers.push_back(&layer_new); } } } #ifdef _DEBUG for (size_t i = 0; i < top_contacts.size(); ++i) assert(top_contacts[i]->height > 0.); #endif /* _DEBUG */ return intermediate_layers; } // At this stage there shall be intermediate_layers allocated between bottom_contacts and top_contacts, but they have no polygons assigned. // Also the bottom/top_contacts shall have a layer thickness assigned already. void PrintObjectSupportMaterial::generate_base_layers( const PrintObject &object, const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, MyLayersPtr &intermediate_layers, const std::vector &layer_support_areas) const { #ifdef SLIC3R_DEBUG static int iRun = 0; #endif /* SLIC3R_DEBUG */ if (top_contacts.empty()) // No top contacts -> no intermediate layers will be produced. return; // coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_base_layers() in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, intermediate_layers.size()), [this, &object, &bottom_contacts, &top_contacts, &intermediate_layers, &layer_support_areas](const tbb::blocked_range& range) { // index -2 means not initialized yet, -1 means intialized and decremented to 0 and then -1. int idx_top_contact_above = -2; int idx_bottom_contact_overlapping = -2; int idx_object_layer_above = -2; // Counting down due to the way idx_lower_or_equal caches indices to avoid repeated binary search over the complete sequence. for (int idx_intermediate = int(range.end()) - 1; idx_intermediate >= int(range.begin()); -- idx_intermediate) { BOOST_LOG_TRIVIAL(trace) << "Support generator - generate_base_layers - creating layer " << idx_intermediate << " of " << intermediate_layers.size(); MyLayer &layer_intermediate = *intermediate_layers[idx_intermediate]; // Layers must be sorted by print_z. assert(idx_intermediate == 0 || layer_intermediate.print_z >= intermediate_layers[idx_intermediate - 1]->print_z); // Find a top_contact layer touching the layer_intermediate from above, if any, and collect its polygons into polygons_new. idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above, [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; }); // New polygons for layer_intermediate. Polygons polygons_new; // Use the precomputed layer_support_areas. idx_object_layer_above = std::max(0, idx_lower_or_equal(object.layers, idx_object_layer_above, [&layer_intermediate](const Layer *layer){ return layer->print_z <= layer_intermediate.print_z + EPSILON; })); polygons_new = layer_support_areas[idx_object_layer_above]; // Polygons to trim polygons_new. Polygons polygons_trimming; // Trimming the base layer with any overlapping top layer. // Following cases are recognized: // 1) top.bottom_z >= base.top_z -> No overlap, no trimming needed. // 2) base.bottom_z >= top.print_z -> No overlap, no trimming needed. // 3) base.print_z > top.print_z && base.bottom_z >= top.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the base layer height where it overlaps the top layer. No trimming needed here. // 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen. // 5) base.print_z <= top.print_z && base.bottom_z >= top.bottom_z -> Base is fully inside top. Trim base by top. int idx_top_contact_overlapping = idx_top_contact_above; while (idx_top_contact_overlapping >= 0 && top_contacts[idx_top_contact_overlapping]->bottom_z > layer_intermediate.print_z - EPSILON) -- idx_top_contact_overlapping; // Collect all the top_contact layer intersecting with this layer. for (; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON) break; // Base must not overlap with top.bottom_z. assert(! (layer_intermediate.print_z > layer_top_overlapping.bottom_z + EPSILON && layer_intermediate.bottom_z < layer_top_overlapping.bottom_z - EPSILON)); if (layer_intermediate.print_z <= layer_top_overlapping.print_z + EPSILON && layer_intermediate.bottom_z >= layer_top_overlapping.bottom_z - EPSILON) // Base is fully inside top. Trim base by top. polygons_append(polygons_trimming, layer_top_overlapping.polygons); } // Trimming the base layer with any overlapping bottom layer. // Following cases are recognized: // 1) bottom.bottom_z >= base.top_z -> No overlap, no trimming needed. // 2) base.bottom_z >= bottom.print_z -> No overlap, no trimming needed. // 3) base.print_z > bottom.bottom_z && base.bottom_z < bottom.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the bottom layer height where it overlaps the base layer. No trimming needed here. // 4) base.print_z > bottom.print_z && base.bottom_z >= bottom.print_z -> Base overlaps with bottom.print_z. This must not happen. // 5) base.print_z <= bottom.print_z && base.bottom_z >= bottom.bottom_z -> Base is fully inside top. Trim base by top. idx_bottom_contact_overlapping = idx_lower_or_equal(bottom_contacts, idx_bottom_contact_overlapping, [&layer_intermediate](const MyLayer *layer){ return layer->bottom_print_z() <= layer_intermediate.print_z - EPSILON; }); // Collect all the bottom_contacts layer intersecting with this layer. for (int i = idx_bottom_contact_overlapping; i >= 0; -- i) { MyLayer &layer_bottom_overlapping = *bottom_contacts[i]; if (layer_bottom_overlapping.print_z < layer_intermediate.bottom_print_z() + EPSILON) break; // Base must not overlap with bottom.top_z. assert(! (layer_intermediate.print_z > layer_bottom_overlapping.print_z + EPSILON && layer_intermediate.bottom_z < layer_bottom_overlapping.print_z - EPSILON)); if (layer_intermediate.print_z <= layer_bottom_overlapping.print_z + EPSILON && layer_intermediate.bottom_z >= layer_bottom_overlapping.bottom_print_z() - EPSILON) // Base is fully inside bottom. Trim base by bottom. polygons_append(polygons_trimming, layer_bottom_overlapping.polygons); } #ifdef SLIC3R_DEBUG { BoundingBox bbox = get_extents(polygons_new); bbox.merge(get_extents(polygons_trimming)); ::Slic3r::SVG svg(debug_out_path("support-intermediate-layers-raw-%d-%lf.svg", iRun, layer_intermediate.print_z), bbox); svg.draw(union_ex(polygons_new, false), "blue", 0.5f); svg.draw(to_polylines(polygons_new), "blue"); svg.draw(union_ex(polygons_trimming, true), "red", 0.5f); svg.draw(to_polylines(polygons_trimming), "red"); } #endif /* SLIC3R_DEBUG */ // Trim the polygons, store them. if (polygons_trimming.empty()) layer_intermediate.polygons = std::move(polygons_new); else layer_intermediate.polygons = diff( polygons_new, polygons_trimming, true); // safety offset to merge the touching source polygons layer_intermediate.layer_type = sltBase; #if 0 // Fillet the base polygons and trim them again with the top, interface and contact layers. $base->{$i} = diff( offset2( $base->{$i}, $fillet_radius_scaled, -$fillet_radius_scaled, # Use a geometric offsetting for filleting. JT_ROUND, 0.2*$fillet_radius_scaled), $trim_polygons, false); // don't apply the safety offset. } #endif } }); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_base_layers() in parallel - end"; #ifdef SLIC3R_DEBUG for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++it) ::Slic3r::SVG::export_expolygons( debug_out_path("support-intermediate-layers-untrimmed-%d-%lf.svg", iRun, (*it)->print_z), union_ex((*it)->polygons, false)); ++ iRun; #endif /* SLIC3R_DEBUG */ trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, m_gap_xy); } void PrintObjectSupportMaterial::trim_support_layers_by_object( const PrintObject &object, MyLayersPtr &support_layers, const coordf_t gap_extra_above, const coordf_t gap_extra_below, const coordf_t gap_xy) const { const float gap_xy_scaled = float(scale_(gap_xy)); // Collect non-empty layers to be processed in parallel. // This is a good idea as pulling a thread from a thread pool for an empty task is expensive. MyLayersPtr nonempty_layers; nonempty_layers.reserve(support_layers.size()); for (size_t idx_layer = 0; idx_layer < support_layers.size(); ++ idx_layer) { MyLayer *support_layer = support_layers[idx_layer]; if (! support_layer->polygons.empty() && support_layer->print_z >= m_slicing_params.raft_contact_top_z + EPSILON) // Non-empty support layer and not a raft layer. nonempty_layers.push_back(support_layer); } // For all intermediate support layers: BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, nonempty_layers.size()), [this, &object, &nonempty_layers, gap_extra_above, gap_extra_below, gap_xy_scaled](const tbb::blocked_range& range) { size_t idx_object_layer_overlapping = size_t(-1); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { MyLayer &support_layer = *nonempty_layers[idx_layer]; // BOOST_LOG_TRIVIAL(trace) << "Support generator - trim_support_layers_by_object - trimmming non-empty layer " << idx_layer << " of " << nonempty_layers.size(); assert(! support_layer.polygons.empty() && support_layer.print_z >= m_slicing_params.raft_contact_top_z + EPSILON); // Find the overlapping object layers including the extra above / below gap. coordf_t z_threshold = support_layer.print_z - support_layer.height - gap_extra_below + EPSILON; idx_object_layer_overlapping = idx_higher_or_equal( object.layers, idx_object_layer_overlapping, [z_threshold](const Layer *layer){ return layer->print_z >= z_threshold; }); // Collect all the object layers intersecting with this layer. Polygons polygons_trimming; size_t i = idx_object_layer_overlapping; for (; i < object.layers.size(); ++ i) { const Layer &object_layer = *object.layers[i]; if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON) break; polygons_append(polygons_trimming, (Polygons)object_layer.slices); } if (! this->m_slicing_params.soluble_interface) { // Collect all bottom surfaces, which will be extruded with a bridging flow. for (; i < object.layers.size(); ++ i) { const Layer &object_layer = *object.layers[i]; bool some_region_overlaps = false; for (LayerRegion* region : object_layer.regions) { coordf_t nozzle_dmr = region->region()->nozzle_dmr_avg(*this->m_print_config); if (object_layer.print_z - nozzle_dmr > support_layer.print_z + gap_extra_above - EPSILON) break; some_region_overlaps = true; polygons_append(polygons_trimming, to_polygons(region->slices.filter_by_type(stBottomBridge))); } if (! some_region_overlaps) break; } } // $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_layer.polygons = diff( support_layer.polygons, offset(polygons_trimming, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); } }); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end"; } PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raft_base( const MyLayersPtr &top_contacts, const MyLayersPtr &interface_layers, const MyLayersPtr &base_layers, MyLayerStorage &layer_storage) const { // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. const float inflate_factor_fine = float(scale_((m_slicing_params.raft_layers() > 1) ? 0.5 : EPSILON)); const float inflate_factor_1st_layer = float(scale_(3.)) - inflate_factor_fine; MyLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front(); MyLayer *interfaces = interface_layers.empty() ? nullptr : interface_layers.front(); MyLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front(); if (contacts != nullptr && contacts->print_z > std::max(m_slicing_params.first_print_layer_height, m_slicing_params.raft_contact_top_z) + EPSILON) // This is not the raft contact layer. contacts = nullptr; if (interfaces != nullptr && interfaces->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) // This is not the raft column base layer. interfaces = nullptr; if (columns_base != nullptr && columns_base->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON) // This is not the raft interface layer. columns_base = nullptr; Polygons interface_polygons; if (contacts != nullptr && ! contacts->polygons.empty()) polygons_append(interface_polygons, offset(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (interfaces != nullptr && ! interfaces->polygons.empty()) polygons_append(interface_polygons, offset(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS)); // Output vector. MyLayersPtr raft_layers; if (m_slicing_params.raft_layers() > 1) { Polygons base; Polygons columns; if (columns_base != nullptr) { base = columns_base->polygons; columns = base; if (! interface_polygons.empty()) // Trim the 1st layer columns with the inflated interface polygons. columns = diff(columns, interface_polygons); } if (! interface_polygons.empty()) { // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. base = union_(base, interface_polygons); } // Do not add the raft contact layer, only add the raft layers below the contact layer. // Insert the 1st layer. { MyLayer &new_layer = layer_allocate(layer_storage, (m_slicing_params.base_raft_layers > 0) ? sltRaftBase : sltRaftInterface); raft_layers.push_back(&new_layer); new_layer.print_z = m_slicing_params.first_print_layer_height; new_layer.height = m_slicing_params.first_print_layer_height; new_layer.bottom_z = 0.; new_layer.polygons = offset(base, inflate_factor_1st_layer); } // Insert the base layers. for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) { coordf_t print_z = raft_layers.back()->print_z; MyLayer &new_layer = layer_allocate(layer_storage, sltRaftBase); raft_layers.push_back(&new_layer); new_layer.print_z = print_z + m_slicing_params.base_raft_layer_height; new_layer.height = m_slicing_params.base_raft_layer_height; new_layer.bottom_z = print_z; new_layer.polygons = base; } // Insert the interface layers. for (size_t i = 1; i < m_slicing_params.interface_raft_layers; ++ i) { coordf_t print_z = raft_layers.back()->print_z; MyLayer &new_layer = layer_allocate(layer_storage, sltRaftInterface); raft_layers.push_back(&new_layer); new_layer.print_z = print_z + m_slicing_params.interface_raft_layer_height; new_layer.height = m_slicing_params.interface_raft_layer_height; new_layer.bottom_z = print_z; new_layer.polygons = interface_polygons; //FIXME misusing contact_polygons for support columns. new_layer.contact_polygons = new Polygons(columns); } } else if (columns_base != nullptr) { // Expand the bases of the support columns in the 1st layer. columns_base->polygons = diff( offset(columns_base->polygons, inflate_factor_1st_layer), offset(m_object->layers.front()->slices.expolygons, scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (contacts != nullptr) columns_base->polygons = diff(columns_base->polygons, interface_polygons); } return raft_layers; } // Convert some of the intermediate layers into top/bottom interface layers. PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_interface_layers( const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, MyLayersPtr &intermediate_layers, MyLayerStorage &layer_storage) const { // my $area_threshold = $self->interface_flow->scaled_spacing ** 2; MyLayersPtr interface_layers; // Contact layer is considered an interface layer, therefore run the following block only if support_material_interface_layers > 1. if (! intermediate_layers.empty() && m_object_config->support_material_interface_layers.value > 1) { // For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers. BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; interface_layers.assign(intermediate_layers.size(), nullptr); tbb::spin_mutex layer_storage_mutex; tbb::parallel_for(tbb::blocked_range(0, intermediate_layers.size()), [this, &bottom_contacts, &top_contacts, &intermediate_layers, &layer_storage, &layer_storage_mutex, &interface_layers](const tbb::blocked_range& range) { // Index of the first top contact layer intersecting the current intermediate layer. size_t idx_top_contact_first = size_t(-1); // Index of the first bottom contact layer intersecting the current intermediate layer. size_t idx_bottom_contact_first = size_t(-1); for (size_t idx_intermediate_layer = range.begin(); idx_intermediate_layer < range.end(); ++ idx_intermediate_layer) { MyLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer]; // Top / bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces. coordf_t top_z = intermediate_layers[std::min(intermediate_layers.size()-1, idx_intermediate_layer + m_object_config->support_material_interface_layers - 1)]->print_z; coordf_t bottom_z = intermediate_layers[std::max(0, int(idx_intermediate_layer) - int(m_object_config->support_material_interface_layers) + 1)]->bottom_z; // Move idx_top_contact_first up until above the current print_z. idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // Collect the top contact areas above this intermediate layer, below top_z. Polygons polygons_top_contact_projected; for (size_t idx_top_contact = idx_top_contact_first; idx_top_contact < top_contacts.size(); ++ idx_top_contact) { const MyLayer &top_contact_layer = *top_contacts[idx_top_contact]; if (top_contact_layer.bottom_z - EPSILON > top_z) break; polygons_append(polygons_top_contact_projected, top_contact_layer.polygons); } // Move idx_bottom_contact_first up until touching bottom_z. idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; }); // Collect the top contact areas above this intermediate layer, below top_z. Polygons polygons_bottom_contact_projected; for (size_t idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < bottom_contacts.size(); ++ idx_bottom_contact) { const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact]; if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z) break; polygons_append(polygons_bottom_contact_projected, bottom_contact_layer.polygons); } if (polygons_top_contact_projected.empty() && polygons_bottom_contact_projected.empty()) continue; // Insert a new layer into top_interface_layers. MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex, polygons_top_contact_projected.empty() ? sltBottomInterface : sltTopInterface); layer_new.print_z = intermediate_layer.print_z; layer_new.bottom_z = intermediate_layer.bottom_z; layer_new.height = intermediate_layer.height; layer_new.bridging = intermediate_layer.bridging; interface_layers[idx_intermediate_layer] = &layer_new; polygons_append(polygons_top_contact_projected, polygons_bottom_contact_projected); polygons_top_contact_projected = union_(polygons_top_contact_projected, true); layer_new.polygons = intersection(intermediate_layer.polygons, polygons_top_contact_projected); //FIXME filter layer_new.polygons islands by a minimum area? // $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ]; intermediate_layer.polygons = diff(intermediate_layer.polygons, polygons_top_contact_projected, false); } }); // Compress contact_out, remove the nullptr items. remove_nulls(interface_layers); BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start"; } return interface_layers; } static inline void fill_expolygons_generate_paths( ExtrusionEntitiesPtr &dst, const ExPolygons &expolygons, Fill *filler, float density, ExtrusionRole role, const Flow &flow) { FillParams fill_params; fill_params.density = density; fill_params.complete = true; fill_params.dont_adjust = true; for (ExPolygons::const_iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) { Surface surface(stInternal, *it_expolygon); extrusion_entities_append_paths( dst, filler->fill_surface(&surface, fill_params), role, flow.mm3_per_mm(), flow.width, flow.height); } } static inline void fill_expolygons_generate_paths( ExtrusionEntitiesPtr &dst, ExPolygons &&expolygons, Fill *filler, float density, ExtrusionRole role, const Flow &flow) { FillParams fill_params; fill_params.density = density; fill_params.complete = true; fill_params.dont_adjust = true; for (ExPolygons::iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) { Surface surface(stInternal, std::move(*it_expolygon)); extrusion_entities_append_paths( dst, filler->fill_surface(&surface, fill_params), role, flow.mm3_per_mm(), flow.width, flow.height); } } // Support layers, partially processed. struct MyLayerExtruded { MyLayerExtruded() : layer(nullptr), m_polygons_to_extrude(nullptr) {} ~MyLayerExtruded() { delete m_polygons_to_extrude; m_polygons_to_extrude = nullptr; } bool empty() const { return layer == nullptr || layer->polygons.empty(); } void set_polygons_to_extrude(Polygons &&polygons) { if (m_polygons_to_extrude == nullptr) m_polygons_to_extrude = new Polygons(std::move(polygons)); else *m_polygons_to_extrude = std::move(polygons); } Polygons& polygons_to_extrude() { return (this->m_polygons_to_extrude == nullptr) ? layer->polygons : *this->m_polygons_to_extrude; } const Polygons& polygons_to_extrude() const { return (this->m_polygons_to_extrude == nullptr) ? layer->polygons : *this->m_polygons_to_extrude; } bool could_merge(const MyLayerExtruded &other) const { return ! this->empty() && ! other.empty() && std::abs(this->layer->height - other.layer->height) < EPSILON && this->layer->bridging == other.layer->bridging; } // Merge regions, perform boolean union over the merged polygons. void merge(MyLayerExtruded &&other) { assert(this->could_merge(other)); // 1) Merge the rest polygons to extrude, if there are any. if (other.m_polygons_to_extrude != nullptr) { if (this->m_polygons_to_extrude == nullptr) { // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). assert(this->extrusions.empty()); this->m_polygons_to_extrude = new Polygons(this->layer->polygons); } Slic3r::polygons_append(*this->m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); *this->m_polygons_to_extrude = union_(*this->m_polygons_to_extrude, true); delete other.m_polygons_to_extrude; other.m_polygons_to_extrude = nullptr; } else if (this->m_polygons_to_extrude != nullptr) { assert(other.m_polygons_to_extrude == nullptr); // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). assert(other.extrusions.empty()); Slic3r::polygons_append(*this->m_polygons_to_extrude, other.layer->polygons); *this->m_polygons_to_extrude = union_(*this->m_polygons_to_extrude, true); } // 2) Merge the extrusions. this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end()); other.extrusions.clear(); // 3) Merge the infill polygons. Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons)); this->layer->polygons = union_(this->layer->polygons, true); other.layer->polygons.clear(); } void polygons_append(Polygons &dst) const { if (layer != NULL && ! layer->polygons.empty()) Slic3r::polygons_append(dst, layer->polygons); } // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). PrintObjectSupportMaterial::MyLayer *layer; // Collect extrusions. They will be exported sorted by the bottom height. ExtrusionEntitiesPtr extrusions; // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. Polygons *m_polygons_to_extrude; }; typedef std::vector MyLayerExtrudedPtrs; struct LoopInterfaceProcessor { LoopInterfaceProcessor(coordf_t circle_r) : n_contact_loops(0), circle_radius(circle_r), circle_distance(circle_r * 3.) { // Shape of the top contact area. circle.points.reserve(6); for (size_t i = 0; i < 6; ++ i) { double angle = double(i) * M_PI / 3.; circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); } } // Generate loop contacts at the top_contact_layer, // trim the top_contact_layer->polygons with the areas covered by the loops. void generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const; int n_contact_loops; coordf_t circle_radius; coordf_t circle_distance; Polygon circle; }; void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const { if (n_contact_loops == 0 || top_contact_layer.empty()) return; Flow flow = interface_flow_src; flow.height = float(top_contact_layer.layer->height); Polygons overhang_polygons; if (top_contact_layer.layer->overhang_polygons != nullptr) overhang_polygons = std::move(*top_contact_layer.layer->overhang_polygons); // Generate the outermost loop. // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) ExPolygons top_contact_expolygons = offset_ex(union_ex(top_contact_layer.layer->polygons), - 0.5f * flow.scaled_width()); // Grid size and bit shifts for quick and exact to/from grid coordinates manipulation. coord_t circle_grid_resolution = 1; coord_t circle_grid_powerof2 = 0; { // epsilon to account for rounding errors coord_t circle_grid_resolution_non_powerof2 = coord_t(2. * circle_distance + 3.); while (circle_grid_resolution < circle_grid_resolution_non_powerof2) { circle_grid_resolution <<= 1; ++ circle_grid_powerof2; } } struct PointAccessor { const Point* operator()(const Point &pt) const { return &pt; } }; typedef ClosestPointInRadiusLookup ClosestPointLookupType; Polygons loops0; { // find centerline of the external loop of the contours // Only consider the loops facing the overhang. Polygons external_loops; // Holes in the external loops. Polygons circles; Polygons overhang_with_margin = offset(union_ex(overhang_polygons), 0.5f * flow.scaled_width()); for (ExPolygons::iterator it_contact_expoly = top_contact_expolygons.begin(); it_contact_expoly != top_contact_expolygons.end(); ++ it_contact_expoly) { // Store the circle centers placed for an expolygon into a regular grid, hashed by the circle centers. ClosestPointLookupType circle_centers_lookup(coord_t(circle_distance - SCALED_EPSILON)); Points circle_centers; Point center_last; // For each contour of the expolygon, start with the outer contour, continue with the holes. for (size_t i_contour = 0; i_contour <= it_contact_expoly->holes.size(); ++ i_contour) { Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; const Point *seg_current_pt = nullptr; coordf_t seg_current_t = 0.; if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { // The contour is below the overhang at least to some extent. //FIXME ideally one would place the circles below the overhang only. // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. if (circle_centers.empty()) { // Place the first circle. seg_current_pt = &contour.points.front(); seg_current_t = 0.; center_last = *seg_current_pt; circle_centers_lookup.insert(center_last); circle_centers.push_back(center_last); } for (Points::const_iterator it = contour.points.begin() + 1; it != contour.points.end(); ++it) { // Is it possible to place a circle on this segment? Is it not too close to any of the circles already placed on this contour? const Point &p1 = *(it-1); const Point &p2 = *it; // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance. const Pointf v_seg(coordf_t(p2.x) - coordf_t(p1.x), coordf_t(p2.y) - coordf_t(p1.y)); const Pointf v_cntr(coordf_t(p1.x - center_last.x), coordf_t(p1.y - center_last.y)); coordf_t a = dot(v_seg); coordf_t b = 2. * dot(v_seg, v_cntr); coordf_t c = dot(v_cntr) - circle_distance * circle_distance; coordf_t disc = b * b - 4. * a * c; if (disc > 0.) { // The circle intersects a ray. Avoid the parts of the segment inside the circle. coordf_t t1 = (-b - sqrt(disc)) / (2. * a); coordf_t t2 = (-b + sqrt(disc)) / (2. * a); coordf_t t0 = (seg_current_pt == &p1) ? seg_current_t : 0.; // Take the lowest t in , excluding . coordf_t t; if (t0 <= t1) t = t0; else if (t2 <= 1.) t = t2; else { // Try the following segment. seg_current_pt = nullptr; continue; } seg_current_pt = &p1; seg_current_t = t; center_last = Point(p1.x + coord_t(v_seg.x * t), p1.y + coord_t(v_seg.y * t)); // It has been verified that the new point is far enough from center_last. // Ensure, that it is far enough from all the centers. std::pair circle_closest = circle_centers_lookup.find(center_last); if (circle_closest.first != nullptr) { -- it; continue; } } else { // All of the segment is outside the circle. Take the first point. seg_current_pt = &p1; seg_current_t = 0.; center_last = p1; } // Place the first circle. circle_centers_lookup.insert(center_last); circle_centers.push_back(center_last); } external_loops.push_back(std::move(contour)); for (Points::const_iterator it_center = circle_centers.begin(); it_center != circle_centers.end(); ++ it_center) { circles.push_back(circle); circles.back().translate(*it_center); } } } } // Apply a pattern to the external loops. loops0 = diff(external_loops, circles); } Polylines loop_lines; { // make more loops Polygons loop_polygons = loops0; for (size_t i = 1; i < n_contact_loops; ++ i) polygons_append(loop_polygons, offset2( loops0, - int(i) * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), 0.5f * flow.scaled_spacing())); // Clip such loops to the side oriented towards the object. // Collect split points, so they will be recognized after the clipping. // At the split points the clipped pieces will be stitched back together. loop_lines.reserve(loop_polygons.size()); std::unordered_map map_split_points; for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) { assert(map_split_points.find(it->first_point()) == map_split_points.end()); map_split_points[it->first_point()] = -1; loop_lines.push_back(it->split_at_first_point()); } loop_lines = intersection_pl(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces. // Try to connect them. for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) { Polyline &polyline = loop_lines[i_line]; auto it = map_split_points.find(polyline.first_point()); if (it != map_split_points.end()) { // This is a stitching point. // If this assert triggers, multiple source polygons likely intersected at this point. assert(it->second != -2); if (it->second < 0) { // First occurence. it->second = i_line; } else { // Second occurence. Join the lines. Polyline &polyline_1st = loop_lines[it->second]; assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); if (polyline_1st.first_point() == it->first) polyline_1st.reverse(); polyline_1st.append(std::move(polyline)); it->second = -2; } continue; } it = map_split_points.find(polyline.last_point()); if (it != map_split_points.end()) { // This is a stitching point. // If this assert triggers, multiple source polygons likely intersected at this point. assert(it->second != -2); if (it->second < 0) { // First occurence. it->second = i_line; } else { // Second occurence. Join the lines. Polyline &polyline_1st = loop_lines[it->second]; assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first); if (polyline_1st.first_point() == it->first) polyline_1st.reverse(); polyline.reverse(); polyline_1st.append(std::move(polyline)); it->second = -2; } } } // Remove empty lines. remove_degenerate(loop_lines); } // 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_polygons, so maybe another // solution should be found to achieve both goals // Store the trimmed polygons into a separate polygon set, so the original infill area remains intact for // "modulate by layer thickness". top_contact_layer.set_polygons_to_extrude(diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1)))); // Transform loops into ExtrusionPath objects. extrusion_entities_append_paths( top_contact_layer.extrusions, STDMOVE(loop_lines), erSupportMaterialInterface, flow.mm3_per_mm(), flow.width, flow.height); } #ifdef SLIC3R_DEBUG static std::string dbg_index_to_color(int idx) { if (idx < 0) return "yellow"; idx = idx % 3; switch (idx) { case 0: return "red"; case 1: return "green"; default: return "blue"; } } #endif /* SLIC3R_DEBUG */ // When extruding a bottom interface layer over an object, the bottom interface layer is extruded in a thin air, therefore // it is being extruded with a bridging flow to not shrink excessively (the die swell effect). // Tiny extrusions are better avoided and it is always better to anchor the thread to an existing support structure if possible. // Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers, // leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers // to stick too firmly to the object. void modulate_extrusion_by_overlapping_layers( // Extrusions generated for this_layer. ExtrusionEntitiesPtr &extrusions_in_out, const PrintObjectSupportMaterial::MyLayer &this_layer, // Multiple layers overlapping with this_layer, sorted bottom up. const PrintObjectSupportMaterial::MyLayersPtr &overlapping_layers) { size_t n_overlapping_layers = overlapping_layers.size(); if (n_overlapping_layers == 0 || extrusions_in_out.empty()) // The extrusions do not overlap with any other extrusion. return; // Get the initial extrusion parameters. ExtrusionPath *extrusion_path_template = dynamic_cast(extrusions_in_out.front()); assert(extrusion_path_template != nullptr); ExtrusionRole extrusion_role = extrusion_path_template->role(); float extrusion_width = extrusion_path_template->width; struct ExtrusionPathFragment { ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {}; ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {}; Polylines polylines; double mm3_per_mm; float width; float height; }; // Split the extrusions by the overlapping layers, reduce their extrusion rate. // The last path_fragment is from this_layer. std::vector path_fragments( n_overlapping_layers + 1, ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height)); // Don't use it, it will be released. extrusion_path_template = nullptr; #ifdef SLIC3R_DEBUG static int iRun = 0; ++ iRun; BoundingBox bbox; for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; bbox.merge(get_extents(overlapping_layer.polygons)); } for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { ExtrusionPath *path = dynamic_cast(*it); assert(path != nullptr); bbox.merge(get_extents(path->polyline)); } SVG svg(debug_out_path("support-fragments-%d-%lf.svg", iRun, this_layer.print_z).c_str(), bbox); const float transparency = 0.5f; // Filled polygons for the overlapping regions. svg.draw(union_ex(this_layer.polygons), dbg_index_to_color(-1), transparency); for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; svg.draw(union_ex(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), transparency); } // Contours of the overlapping regions. svg.draw(to_polylines(this_layer.polygons), dbg_index_to_color(-1), scale_(0.2)); for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) { const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; svg.draw(to_polylines(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), scale_(0.1)); } // Fill extrusion, the source. for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { ExtrusionPath *path = dynamic_cast(*it); std::string color_name; switch ((it - extrusions_in_out.begin()) % 9) { case 0: color_name = "magenta"; break; case 1: color_name = "deepskyblue"; break; case 2: color_name = "coral"; break; case 3: color_name = "goldenrod"; break; case 4: color_name = "orange"; break; case 5: color_name = "olivedrab"; break; case 6: color_name = "blueviolet"; break; case 7: color_name = "brown"; break; default: color_name = "orchid"; break; } svg.draw(path->polyline, color_name, scale_(0.2)); } #endif /* SLIC3R_DEBUG */ // End points of the original paths. std::vector> path_ends; // Collect the paths of this_layer. { Polylines &polylines = path_fragments.back().polylines; for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) { ExtrusionPath *path = dynamic_cast(*it); assert(path != nullptr); polylines.emplace_back(Polyline(std::move(path->polyline))); path_ends.emplace_back(std::pair(polylines.back().points.front(), polylines.back().points.back())); } } // Destroy the original extrusion paths, their polylines were moved to path_fragments already. // This will be the destination for the new paths. extrusions_in_out.clear(); // Fragment the path segments by overlapping layers. The overlapping layers are sorted by an increasing print_z. // Trim by the highest overlapping layer first. for (int i_overlapping_layer = int(n_overlapping_layers) - 1; i_overlapping_layer >= 0; -- i_overlapping_layer) { const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer]; ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer]; Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width))); frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming, false); path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming, false); // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). assert(this_layer.print_z > overlapping_layer.print_z); frag.height = float(this_layer.print_z - overlapping_layer.print_z); frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f, false).mm3_per_mm(); #ifdef SLIC3R_DEBUG svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); #endif /* SLIC3R_DEBUG */ } #ifdef SLIC3R_DEBUG svg.draw(path_fragments.back().polylines, dbg_index_to_color(-1), scale_(0.1)); svg.Close(); #endif /* SLIC3R_DEBUG */ // Now chain the split segments using hashing and a nearly exact match, maintaining the order of segments. // Create a single ExtrusionPath or ExtrusionEntityCollection per source ExtrusionPath. // Map of fragment start/end points to a pair of // Because a non-exact matching is used for the end points, a multi-map is used. // As the clipper library may reverse the order of some clipped paths, store both ends into the map. struct ExtrusionPathFragmentEnd { ExtrusionPathFragmentEnd(size_t alayer_idx, size_t apolyline_idx, bool ais_start) : layer_idx(alayer_idx), polyline_idx(apolyline_idx), is_start(ais_start) {} size_t layer_idx; size_t polyline_idx; bool is_start; }; class ExtrusionPathFragmentEndPointAccessor { public: ExtrusionPathFragmentEndPointAccessor(const std::vector &path_fragments) : m_path_fragments(path_fragments) {} // Return an end point of a fragment, or nullptr if the fragment has been consumed already. const Point* operator()(const ExtrusionPathFragmentEnd &fragment_end) const { const Polyline &polyline = m_path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx]; return polyline.points.empty() ? nullptr : (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); } private: ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&); const std::vector &m_path_fragments; }; const coord_t search_radius = 7; ClosestPointInRadiusLookup map_fragment_starts( search_radius, ExtrusionPathFragmentEndPointAccessor(path_fragments)); for (size_t i_overlapping_layer = 0; i_overlapping_layer <= n_overlapping_layers; ++ i_overlapping_layer) { const Polylines &polylines = path_fragments[i_overlapping_layer].polylines; for (size_t i_polyline = 0; i_polyline < polylines.size(); ++ i_polyline) { // Map a starting point of a polyline to a pair of if (polylines[i_polyline].points.size() >= 2) { map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true)); map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false)); } } } // For each source path: for (size_t i_path = 0; i_path < path_ends.size(); ++ i_path) { const Point &pt_start = path_ends[i_path].first; const Point &pt_end = path_ends[i_path].second; Point pt_current = pt_start; // Find a chain of fragments with the original / reduced print height. ExtrusionMultiPath multipath; for (;;) { // Find a closest end point to pt_current. std::pair end_and_dist2 = map_fragment_starts.find(pt_current); // There may be a bug in Clipper flipping the order of two last points in a fragment? // assert(end_and_dist2.first != nullptr); assert(end_and_dist2.first == nullptr || end_and_dist2.second < search_radius * search_radius); if (end_and_dist2.first == nullptr) { // New fragment connecting to pt_current was not found. // Verify that the last point found is close to the original end point of the unfragmented path. //const double d2 = pt_end.distance_to_sq(pt_current); //assert(d2 < coordf_t(search_radius * search_radius)); // End of the path. break; } const ExtrusionPathFragmentEnd &fragment_end_min = *end_and_dist2.first; // Fragment to consume. ExtrusionPathFragment &frag = path_fragments[fragment_end_min.layer_idx]; Polyline &frag_polyline = frag.polylines[fragment_end_min.polyline_idx]; // Path to append the fragment to. ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); if (path != nullptr) { // Verify whether the path is compatible with the current fragment. assert(this_layer.layer_type == PrintObjectSupportMaterial::sltBottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { path = nullptr; } // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer. } if (path == nullptr) { // Allocate a new path. multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height)); path = &multipath.paths.back(); } // The Clipper library may flip the order of the clipped polylines arbitrarily. // Reverse the source polyline, if connecting to the end. if (! fragment_end_min.is_start) frag_polyline.reverse(); // Enforce exact overlap of the end points of successive fragments. assert(frag_polyline.points.front() == pt_current); frag_polyline.points.front() = pt_current; // Don't repeat the first point. if (! path->polyline.points.empty()) path->polyline.points.pop_back(); // Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time. path->polyline.append(std::move(frag_polyline)); frag_polyline.points.clear(); pt_current = path->polyline.points.back(); if (pt_current == pt_end) { // End of the path. break; } } if (!multipath.paths.empty()) { if (multipath.paths.size() == 1) { // This path was not fragmented. extrusions_in_out.push_back(new ExtrusionPath(std::move(multipath.paths.front()))); } else { // This path was fragmented. Copy the collection as a whole object, so the order inside the collection will not be changed // during the chaining of extrusions_in_out. extrusions_in_out.push_back(new ExtrusionMultiPath(std::move(multipath))); } } } // If there are any non-consumed fragments, add them separately. //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected. for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment) extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); } void PrintObjectSupportMaterial::generate_toolpaths( const PrintObject &object, const MyLayersPtr &raft_layers, const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, const MyLayersPtr &intermediate_layers, const MyLayersPtr &interface_layers) const { // Slic3r::debugf "Generating patterns\n"; // loop_interface_processor with a given circle radius. LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_material_interface_flow.scaled_width()); loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; float base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value)); float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_material_interface_flow.spacing(); coordf_t interface_density = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing); coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_material_flow.spacing(); coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing); if (m_object_config->support_material_interface_layers.value == 0) { // No interface layers allowed, print everything with the base support pattern. interface_spacing = support_spacing; interface_density = support_density; } // Prepare fillers. SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; bool with_sheath = m_object_config->support_material_with_sheath; InfillPattern infill_pattern; std::vector angles; angles.push_back(base_angle); switch (support_pattern) { case smpRectilinearGrid: angles.push_back(interface_angle); // fall through case smpRectilinear: infill_pattern = ipRectilinear; break; case smpHoneycomb: case smpPillars: infill_pattern = ipHoneycomb; break; } BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); // const coordf_t link_max_length_factor = 3.; const coordf_t link_max_length_factor = 0.; float raft_angle_1st_layer = 0.f; float raft_angle_base = 0.f; float raft_angle_interface = 0.f; if (m_slicing_params.base_raft_layers > 1) { // There are all raft layer types (1st layer, base, interface & contact layers) available. raft_angle_1st_layer = interface_angle; raft_angle_base = base_angle; raft_angle_interface = interface_angle; } else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) { // 1st layer, interface & contact layers available. raft_angle_1st_layer = base_angle; if (this->has_support()) // Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer. raft_angle_1st_layer += 0.7854f; raft_angle_interface = interface_angle; } else if (m_slicing_params.interface_raft_layers == 1) { // Only the contact raft layer is non-empty, which will be printed as the 1st layer. assert(m_slicing_params.base_raft_layers == 0); assert(m_slicing_params.interface_raft_layers == 1); assert(m_slicing_params.raft_layers() == 1 && raft_layers.size() == 0); } else { // No raft. assert(m_slicing_params.base_raft_layers == 0); assert(m_slicing_params.interface_raft_layers == 0); assert(m_slicing_params.raft_layers() == 0 && raft_layers.size() == 0); } // Insert the raft base layers. size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1)); tbb::parallel_for(tbb::blocked_range(0, n_raft_layers), [this, &object, &raft_layers, infill_pattern, &bbox_object, support_density, interface_density, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor, with_sheath] (const tbb::blocked_range& range) { for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { assert(support_layer_id < raft_layers.size()); SupportLayer &support_layer = *object.support_layers[support_layer_id]; assert(support_layer.support_fills.entities.empty()); MyLayer &raft_layer = *raft_layers[support_layer_id]; std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(ipRectilinear)); std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(infill_pattern)); filler_interface->set_bounding_box(bbox_object); filler_support->set_bounding_box(bbox_object); // Print the support base below the support columns, or the support base for the support columns plus the contacts. if (support_layer_id > 0) { Polygons to_infill_polygons = (support_layer_id < m_slicing_params.base_raft_layers) ? raft_layer.polygons : //FIXME misusing contact_polygons for support columns. ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons); if (! to_infill_polygons.empty()) { Flow flow(float(m_support_material_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging); // find centerline of the external loop/extrusions ExPolygons to_infill = (support_layer_id == 0 || ! with_sheath) ? // union_ex(base_polygons, true) : offset2_ex(to_infill_polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON)) : offset2_ex(to_infill_polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width())); if (! to_infill.empty() && with_sheath) { // Draw a perimeter all around the support infill. This makes the support stable, but difficult to remove. // TODO: use brim ordering algorithm to_infill_polygons = to_polygons(to_infill); // TODO: use offset2_ex() to_infill = offset_ex(to_infill, float(- 0.4 * flow.scaled_spacing())); extrusion_entities_append_paths( support_layer.support_fills.entities, to_polylines(STDMOVE(to_infill_polygons)), erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height); } if (! to_infill.empty()) { // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. Fill *filler = filler_support.get(); filler->angle = raft_angle_base; filler->spacing = m_support_material_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); fill_expolygons_generate_paths( // Destination support_layer.support_fills.entities, // Regions to fill STDMOVE(to_infill), // Filler and its parameters filler, float(support_density), // Extrusion parameters erSupportMaterial, flow); } } } Fill *filler = filler_interface.get(); Flow flow = m_first_layer_flow; float density = 0.f; if (support_layer_id == 0) { // Base flange. filler->angle = raft_angle_1st_layer; filler->spacing = m_first_layer_flow.spacing(); // 70% of density on the 1st layer. density = 0.7f; } else if (support_layer_id >= m_slicing_params.base_raft_layers) { filler->angle = raft_angle_interface; // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. filler->spacing = m_support_material_flow.spacing(); flow = Flow(float(m_support_material_interface_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging); density = float(interface_density); } else continue; filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); fill_expolygons_generate_paths( // Destination support_layer.support_fills.entities, // Regions to fill offset2_ex(raft_layer.polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON)), // Filler and its parameters filler, density, // Extrusion parameters (support_layer_id < m_slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow); } }); struct LayerCacheItem { LayerCacheItem(MyLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {} MyLayerExtruded *layer_extruded; std::vector overlapping; }; struct LayerCache { MyLayerExtruded bottom_contact_layer; MyLayerExtruded top_contact_layer; MyLayerExtruded base_layer; MyLayerExtruded interface_layer; std::vector overlaps; }; std::vector layer_caches(object.support_layers.size(), LayerCache()); tbb::parallel_for(tbb::blocked_range(n_raft_layers, object.support_layers.size()), [this, &object, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &layer_caches, &loop_interface_processor, infill_pattern, &bbox_object, support_density, interface_density, interface_angle, &angles, link_max_length_factor, with_sheath] (const tbb::blocked_range& range) { // Indices of the 1st layer in their respective container at the support layer height. size_t idx_layer_bottom_contact = size_t(-1); size_t idx_layer_top_contact = size_t(-1); size_t idx_layer_intermediate = size_t(-1); size_t idx_layer_inteface = size_t(-1); std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(m_slicing_params.soluble_interface ? ipConcentric : ipRectilinear)); std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(infill_pattern)); filler_interface->set_bounding_box(bbox_object); filler_support->set_bounding_box(bbox_object); for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { SupportLayer &support_layer = *object.support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; // Find polygons with the same print_z. MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; MyLayerExtruded &top_contact_layer = layer_cache.top_contact_layer; MyLayerExtruded &base_layer = layer_cache.base_layer; MyLayerExtruded &interface_layer = layer_cache.interface_layer; // Increment the layer indices to find a layer at support_layer.print_z. { auto fun = [&support_layer](const MyLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; }; idx_layer_bottom_contact = idx_higher_or_equal(bottom_contacts, idx_layer_bottom_contact, fun); idx_layer_top_contact = idx_higher_or_equal(top_contacts, idx_layer_top_contact, fun); idx_layer_intermediate = idx_higher_or_equal(intermediate_layers, idx_layer_intermediate, fun); idx_layer_inteface = idx_higher_or_equal(interface_layers, idx_layer_inteface, fun); } // Copy polygons from the layers. if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON) bottom_contact_layer.layer = bottom_contacts[idx_layer_bottom_contact]; if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON) top_contact_layer.layer = top_contacts[idx_layer_top_contact]; if (idx_layer_inteface < interface_layers.size() && interface_layers[idx_layer_inteface]->print_z < support_layer.print_z + EPSILON) interface_layer.layer = interface_layers[idx_layer_inteface]; if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) base_layer.layer = intermediate_layers[idx_layer_intermediate]; if (m_object_config->support_material_interface_layers == 0) { // If no interface layers were requested, we treat the contact layer exactly as a generic base layer. if (m_can_merge_support_regions) { if (base_layer.could_merge(top_contact_layer)) base_layer.merge(std::move(top_contact_layer)); else if (base_layer.empty() && !top_contact_layer.empty() && !top_contact_layer.layer->bridging) std::swap(base_layer, top_contact_layer); if (base_layer.could_merge(bottom_contact_layer)) base_layer.merge(std::move(bottom_contact_layer)); else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging) std::swap(base_layer, bottom_contact_layer); } } else { loop_interface_processor.generate(top_contact_layer, m_support_material_interface_flow); // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used // to trim other layers. if (top_contact_layer.could_merge(interface_layer)) top_contact_layer.merge(std::move(interface_layer)); } if (! interface_layer.empty() && ! base_layer.empty()) { // turn base support into interface when it's contained in our holes // (this way we get wider interface anchoring) //FIXME one wants to fill in the inner most holes of the interfaces, not all the holes. Polygons islands = top_level_islands(interface_layer.layer->polygons); polygons_append(interface_layer.layer->polygons, intersection(base_layer.layer->polygons, islands)); base_layer.layer->polygons = diff(base_layer.layer->polygons, islands); } // Top and bottom contacts, interface layers. for (size_t i = 0; i < 3; ++ i) { MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer); if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty()) continue; //FIXME When paralellizing, each thread shall have its own copy of the fillers. bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0; Flow interface_flow( float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)), float(layer_ex.layer->height), m_support_material_interface_flow.nozzle_diameter, layer_ex.layer->bridging); filler_interface->angle = interface_as_base ? // If zero interface layers are configured, use the same angle as for the base layers. angles[support_layer_id % angles.size()] : // Use interface angle for the interface layers. interface_angle; filler_interface->spacing = m_support_material_interface_flow.spacing(); filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / interface_density)); fill_expolygons_generate_paths( // Destination layer_ex.extrusions, // Regions to fill union_ex(layer_ex.polygons_to_extrude(), true), // Filler and its parameters filler_interface.get(), float(interface_density), // Extrusion parameters erSupportMaterialInterface, interface_flow); } // Base support or flange. if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) { //FIXME When paralellizing, each thread shall have its own copy of the fillers. Fill *filler = filler_support.get(); filler->angle = angles[support_layer_id % angles.size()]; // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. Flow flow( float(base_layer.layer->bridging ? base_layer.layer->height : m_support_material_flow.width), float(base_layer.layer->height), m_support_material_flow.nozzle_diameter, base_layer.layer->bridging); filler->spacing = m_support_material_flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density)); float density = float(support_density); // find centerline of the external loop/extrusions ExPolygons to_infill = (support_layer_id == 0 || ! with_sheath) ? // union_ex(base_polygons, true) : offset2_ex(base_layer.polygons_to_extrude(), float(SCALED_EPSILON), float(- SCALED_EPSILON)) : offset2_ex(base_layer.polygons_to_extrude(), float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width())); if (base_layer.layer->bottom_z < EPSILON) { // Base flange (the 1st layer). filler = filler_interface.get(); filler->angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); density = 0.5f; flow = m_first_layer_flow; // use the proper spacing for first layer as we don't need to align // its pattern to the other layers //FIXME When paralellizing, each thread shall have its own copy of the fillers. filler->spacing = flow.spacing(); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); } else if (with_sheath) { // Draw a perimeter all around the support infill. This makes the support stable, but difficult to remove. // TODO: use brim ordering algorithm Polygons to_infill_polygons = to_polygons(to_infill); // TODO: use offset2_ex() to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing())); extrusion_entities_append_paths( base_layer.extrusions, to_polylines(STDMOVE(to_infill_polygons)), erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height); } fill_expolygons_generate_paths( // Destination base_layer.extrusions, // Regions to fill STDMOVE(to_infill), // Filler and its parameters filler, density, // Extrusion parameters erSupportMaterial, flow); } layer_cache.overlaps.reserve(4); if (! bottom_contact_layer.empty()) layer_cache.overlaps.push_back(&bottom_contact_layer); if (! top_contact_layer.empty()) layer_cache.overlaps.push_back(&top_contact_layer); if (! interface_layer.empty()) layer_cache.overlaps.push_back(&interface_layer); if (! base_layer.empty()) layer_cache.overlaps.push_back(&base_layer); // Sort the layers with the same print_z coordinate by their heights, thickest first. std::sort(layer_cache.overlaps.begin(), layer_cache.overlaps.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); // Collect the support areas with this print_z into islands, as there is no need // for retraction over these islands. Polygons polys; // Collect the extrusions, sorted by the bottom extrusion height. for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) { // Collect islands to polys. layer_cache_item.layer_extruded->polygons_append(polys); // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free" // in a sense that they are not synchronized with other support layers. As the top and bottom contact surfaces // are inflated to achieve a better anchoring, it may happen, that these surfaces will at least partially // overlap in Z with another support layers, leading to over-extrusion. // Mitigate the over-extrusion by modulating the extrusion rate over these regions. // The print head will follow the same print_z, but the layer thickness will be reduced // where it overlaps with another support layer. //FIXME When printing a briging path, what is an equivalent height of the squished extrudate of the same width? // Collect overlapping top/bottom surfaces. layer_cache_item.overlapping.reserve(16); coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON; for (int i = int(idx_layer_bottom_contact) - 1; i >= 0 && bottom_contacts[i]->print_z > bottom_z; -- i) layer_cache_item.overlapping.push_back(bottom_contacts[i]); for (int i = int(idx_layer_top_contact) - 1; i >= 0 && top_contacts[i]->print_z > bottom_z; -- i) layer_cache_item.overlapping.push_back(top_contacts[i]); if (layer_cache_item.layer_extruded->layer->layer_type == sltBottomContact) { // Bottom contact layer may overlap with a base layer, which may be changed to interface layer. for (int i = int(idx_layer_intermediate) - 1; i >= 0 && intermediate_layers[i]->print_z > bottom_z; -- i) layer_cache_item.overlapping.push_back(intermediate_layers[i]); for (int i = int(idx_layer_inteface) - 1; i >= 0 && interface_layers[i]->print_z > bottom_z; -- i) layer_cache_item.overlapping.push_back(interface_layers[i]); } std::sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), MyLayersPtrCompare()); } if (! polys.empty()) expolygons_append(support_layer.support_islands.expolygons, union_ex(polys)); /* { 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} ], ); } */ } // for each support_layer_id }); // Now modulate the support layer height in parallel. tbb::parallel_for(tbb::blocked_range(n_raft_layers, object.support_layers.size()), [this, &object, &layer_caches] (const tbb::blocked_range& range) { for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) { SupportLayer &support_layer = *object.support_layers[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id]; for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) { modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping); support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions)); } } }); } /* void PrintObjectSupportMaterial::clip_by_pillars( const PrintObject &object, LayersPtr &bottom_contacts, LayersPtr &top_contacts, LayersPtr &intermediate_contacts); { // this prevents supplying an empty point set to BoundingBox constructor if (top_contacts.empty()) return; coord_t pillar_size = scale_(PILLAR_SIZE); coord_t pillar_spacing = scale_(PILLAR_SPACING); // A regular grid of pillars, filling the 2D bounding box. Polygons grid; { // Rectangle with a side of 2.5x2.5mm. Polygon pillar; pillar.points.push_back(Point(0, 0)); pillar.points.push_back(Point(pillar_size, 0)); pillar.points.push_back(Point(pillar_size, pillar_size)); pillar.points.push_back(Point(0, pillar_size)); // 2D bounding box of the projection of all contact polygons. BoundingBox bbox; for (LayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) bbox.merge(get_extents((*it)->polygons)); grid.reserve(size_t(ceil(bb.size().x / pillar_spacing)) * size_t(ceil(bb.size().y / pillar_spacing))); for (coord_t x = bb.min.x; x <= bb.max.x - pillar_size; x += pillar_spacing) { for (coord_t y = bb.min.y; y <= bb.max.y - pillar_size; y += pillar_spacing) { grid.push_back(pillar); for (size_t i = 0; i < pillar.points.size(); ++ i) grid.back().points[i].translate(Point(x, y)); } } } // add pillars to every layer for my $i (0..n_support_z) { $shape->[$i] = [ @$grid ]; } // build capitals for my $i (0..n_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], ); } } */ } // namespace Slic3r ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/SupportMaterial.hpp������������������������������������������0000664�0000000�0000000�00000023436�13243544447�0023506�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef slic3r_SupportMaterial_hpp_ #define slic3r_SupportMaterial_hpp_ #include "Flow.hpp" #include "PrintConfig.hpp" #include "Slicing.hpp" namespace Slic3r { class PrintObject; class PrintConfig; class PrintObjectConfig; // how much we extend support around the actual contact area #define SUPPORT_MATERIAL_MARGIN 1.5 // This class manages raft and supports for a single PrintObject. // Instantiated by Slic3r::Print::Object->_support_material() // This class is instantiated before the slicing starts as Object.pm will query // the parameters of the raft to determine the 1st layer height and thickness. class PrintObjectSupportMaterial { public: // Support layer type to be used by MyLayer. This type carries a much more detailed information // about the support layer type than the final support layers stored in a PrintObject. enum SupporLayerType { sltUnknown = 0, // Ratft base layer, to be printed with the support material. sltRaftBase, // Raft interface layer, to be printed with the support interface material. sltRaftInterface, // Bottom contact layer placed over a top surface of an object. To be printed with a support interface material. sltBottomContact, // Dense interface layer, to be printed with the support interface material. // This layer is separated from an object by an sltBottomContact layer. sltBottomInterface, // Sparse base support layer, to be printed with a support material. sltBase, // Dense interface layer, to be printed with the support interface material. // This layer is separated from an object with sltTopContact layer. sltTopInterface, // Top contact layer directly supporting an overhang. To be printed with a support interface material. sltTopContact, // Some undecided type yet. It will turn into sltBase first, then it may turn into sltBottomInterface or sltTopInterface. sltIntermediate, }; // A support layer type used internally by the SupportMaterial class. This class carries a much more detailed // information about the support layer than the layers stored in the PrintObject, mainly // the MyLayer is aware of the bridging flow and the interface gaps between the object and the support. class MyLayer { public: MyLayer() : layer_type(sltUnknown), print_z(0.), bottom_z(0.), height(0.), idx_object_layer_above(size_t(-1)), idx_object_layer_below(size_t(-1)), bridging(false), contact_polygons(nullptr), overhang_polygons(nullptr) {} ~MyLayer() { delete contact_polygons; contact_polygons = nullptr; delete overhang_polygons; overhang_polygons = nullptr; } bool operator==(const MyLayer &layer2) const { return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging; } // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. bool operator<(const MyLayer &layer2) const { if (print_z < layer2.print_z) { return true; } else if (print_z == layer2.print_z) { if (height > layer2.height) return true; else if (height == layer2.height) { // Bridging layers first. return bridging && ! layer2.bridging; } else return false; } else return false; } // For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation. // For the non-bridging flow, bottom_print_z will be equal to bottom_z. coordf_t bottom_print_z() const { return print_z - height; } // To sort the extremes of top / bottom interface layers. coordf_t extreme_z() const { return (this->layer_type == sltTopContact) ? this->bottom_z : this->print_z; } SupporLayerType layer_type; // Z used for printing, in unscaled coordinates. coordf_t print_z; // Bottom Z of this layer. For soluble layers, bottom_z + height = print_z, // otherwise bottom_z + gap + height = print_z. coordf_t bottom_z; // Layer height in unscaled coordinates. coordf_t height; // Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers. // If this is not a contact layer, it will be set to size_t(-1). size_t idx_object_layer_above; // Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers. // If this is not a contact layer, it will be set to size_t(-1). size_t idx_object_layer_below; // Use a bridging flow when printing this support layer. bool bridging; // Polygons to be filled by the support pattern. Polygons polygons; // Currently for the contact layers only. // MyLayer owns the contact_polygons and overhang_polygons, they are freed by the destructor. Polygons *contact_polygons; Polygons *overhang_polygons; }; // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained // up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future, // which would allocate layers by multiple chunks. typedef std::deque MyLayerStorage; typedef std::vector MyLayersPtr; public: PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params); // Is raft enabled? bool has_raft() const { return m_slicing_params.has_raft(); } // Has any support? bool has_support() const { return m_object_config->support_material.value; } bool build_plate_only() const { return this->has_support() && m_object_config->support_material_buildplate_only.value; } bool synchronize_layers() const { return m_slicing_params.soluble_interface && m_object_config->support_material_synchronize_layers.value; } bool has_contact_loops() const { return m_object_config->support_material_interface_contact_loops.value; } // Generate support material for the object. // New support layers will be added to the object, // with extrusion paths and islands filled in for each support layer. void generate(PrintObject &object); private: // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. MyLayersPtr top_contact_layers(const PrintObject &object, MyLayerStorage &layer_storage) const; // Generate bottom contact layers supporting the top contact layers. // For a soluble interface material synchronize the layer heights with the object, // otherwise set the layer height to a bridging flow of a support interface nozzle. MyLayersPtr bottom_contact_layers_and_layer_support_areas( const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage, std::vector &layer_support_areas) const; // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const; // Generate raft layers and the intermediate support layers between the bottom contact and top contact surfaces. MyLayersPtr raft_and_intermediate_support_layers( const PrintObject &object, const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage) const; // Fill in the base layers with polygons. void generate_base_layers( const PrintObject &object, const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, MyLayersPtr &intermediate_layers, const std::vector &layer_support_areas) const; // Generate raft layers, also expand the 1st support layer // in case there is no raft layer to improve support adhesion. MyLayersPtr generate_raft_base( const MyLayersPtr &top_contacts, const MyLayersPtr &interface_layers, const MyLayersPtr &base_layers, MyLayerStorage &layer_storage) const; // Turn some of the base layers into interface layers. MyLayersPtr generate_interface_layers( const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, MyLayersPtr &intermediate_layers, MyLayerStorage &layer_storage) const; // Trim support layers by an object to leave a defined gap between // the support volume and the object. void trim_support_layers_by_object( const PrintObject &object, MyLayersPtr &support_layers, const coordf_t gap_extra_above, const coordf_t gap_extra_below, const coordf_t gap_xy) const; /* void generate_pillars_shape(); void clip_with_shape(); */ // Produce the actual G-code. void generate_toolpaths( const PrintObject &object, const MyLayersPtr &raft_layers, const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, const MyLayersPtr &intermediate_layers, const MyLayersPtr &interface_layers) const; // Following objects are not owned by SupportMaterial class. const PrintObject *m_object; const PrintConfig *m_print_config; const PrintObjectConfig *m_object_config; // Pre-calculated parameters shared between the object slicer and the support generator, // carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc. SlicingParameters m_slicing_params; Flow m_first_layer_flow; Flow m_support_material_flow; Flow m_support_material_interface_flow; // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder? bool m_can_merge_support_regions; coordf_t m_support_layer_height_min; coordf_t m_support_layer_height_max; coordf_t m_gap_xy; }; } // namespace Slic3r #endif /* slic3r_SupportMaterial_hpp_ */ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/Surface.cpp��������������������������������������������������0000664�0000000�0000000�00000010650�13243544447�0021730�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "BoundingBox.hpp" #include "Surface.hpp" #include "SVG.hpp" namespace Slic3r { Surface::operator Polygons() const { return this->expolygon; } double Surface::area() const { return this->expolygon.area(); } bool Surface::is_solid() const { return this->surface_type == stTop || this->surface_type == stBottom || this->surface_type == stBottomBridge || this->surface_type == stInternalSolid || this->surface_type == stInternalBridge; } bool Surface::is_external() const { return this->surface_type == stTop || this->surface_type == stBottom || this->surface_type == stBottomBridge; } bool Surface::is_internal() const { return this->surface_type == stInternal || this->surface_type == stInternalBridge || this->surface_type == stInternalSolid || this->surface_type == stInternalVoid; } bool Surface::is_bottom() const { return this->surface_type == stBottom || this->surface_type == stBottomBridge; } bool Surface::is_bridge() const { return this->surface_type == stBottomBridge || this->surface_type == stInternalBridge; } BoundingBox get_extents(const Surface &surface) { return get_extents(surface.expolygon.contour); } BoundingBox get_extents(const Surfaces &surfaces) { BoundingBox bbox; if (! surfaces.empty()) { bbox = get_extents(surfaces.front()); for (size_t i = 1; i < surfaces.size(); ++ i) bbox.merge(get_extents(surfaces[i])); } return bbox; } BoundingBox get_extents(const SurfacesPtr &surfaces) { BoundingBox bbox; if (! surfaces.empty()) { bbox = get_extents(*surfaces.front()); for (size_t i = 1; i < surfaces.size(); ++ i) bbox.merge(get_extents(*surfaces[i])); } return bbox; } const char* surface_type_to_color_name(const SurfaceType surface_type) { switch (surface_type) { case stTop: return "rgb(255,0,0)"; // "red"; case stBottom: return "rgb(0,255,0)"; // "green"; case stBottomBridge: return "rgb(0,0,255)"; // "blue"; case stInternal: return "rgb(255,255,128)"; // yellow case stInternalSolid: return "rgb(255,0,255)"; // magenta case stInternalBridge: return "rgb(0,255,255)"; case stInternalVoid: return "rgb(128,128,128)"; case stPerimeter: return "rgb(128,0,0)"; // maroon default: return "rgb(64,64,64)"; }; } Point export_surface_type_legend_to_svg_box_size() { return Point(scale_(1.+10.*8.), scale_(3.)); } void export_surface_type_legend_to_svg(SVG &svg, const Point &pos) { // 1st row coord_t pos_x0 = pos.x + scale_(1.); coord_t pos_x = pos_x0; coord_t pos_y = pos.y + scale_(1.5); coord_t step_x = scale_(10.); svg.draw_legend(Point(pos_x, pos_y), "perimeter" , surface_type_to_color_name(stPerimeter)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "top" , surface_type_to_color_name(stTop)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "bottom" , surface_type_to_color_name(stBottom)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "bottom bridge" , surface_type_to_color_name(stBottomBridge)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "invalid" , surface_type_to_color_name(SurfaceType(-1))); // 2nd row pos_x = pos_x0; pos_y = pos.y+scale_(2.8); svg.draw_legend(Point(pos_x, pos_y), "internal" , surface_type_to_color_name(stInternal)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "internal solid" , surface_type_to_color_name(stInternalSolid)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "internal bridge", surface_type_to_color_name(stInternalBridge)); pos_x += step_x; svg.draw_legend(Point(pos_x, pos_y), "internal void" , surface_type_to_color_name(stInternalVoid)); } bool export_to_svg(const char *path, const Surfaces &surfaces, const float transparency) { BoundingBox bbox; for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) bbox.merge(get_extents(surface->expolygon)); SVG svg(path, bbox); for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface) svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency); svg.Close(); return true; } } ����������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/Surface.hpp��������������������������������������������������0000664�0000000�0000000�00000025010�13243544447�0021731�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef slic3r_Surface_hpp_ #define slic3r_Surface_hpp_ #include "libslic3r.h" #include "ExPolygon.hpp" namespace Slic3r { enum SurfaceType { // Top horizontal surface, visible from the top. stTop, // Bottom horizontal surface, visible from the bottom, printed with a normal extrusion flow. stBottom, // Bottom horizontal surface, visible from the bottom, unsupported, printed with a bridging extrusion flow. stBottomBridge, // Normal sparse infill. stInternal, // Full infill, supporting the top surfaces and/or defining the verticall wall thickness. stInternalSolid, // 1st layer of dense infill over sparse infill, printed with a bridging extrusion flow. stInternalBridge, // stInternal turns into void surfaces if the sparse infill is used for supports only, // or if sparse infill layers get combined into a single layer. stInternalVoid, // Inner/outer perimeters. stPerimeter, // Last surface type, if the SurfaceType is used as an index into a vector. stLast, stCount = stLast + 1 }; class Surface { public: SurfaceType surface_type; ExPolygon expolygon; double thickness; // in mm unsigned short thickness_layers; // in layers double bridge_angle; // in radians, ccw, 0 = East, only 0+ (negative means undefined) unsigned short extra_perimeters; Surface(const Slic3r::Surface &rhs) : surface_type(rhs.surface_type), expolygon(rhs.expolygon), thickness(rhs.thickness), thickness_layers(rhs.thickness_layers), bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) {}; Surface(SurfaceType _surface_type, const ExPolygon &_expolygon) : surface_type(_surface_type), expolygon(_expolygon), thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0) {}; Surface(const Surface &other, const ExPolygon &_expolygon) : surface_type(other.surface_type), expolygon(_expolygon), thickness(other.thickness), thickness_layers(other.thickness_layers), bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters) {}; Surface(Surface &&rhs) : surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)), thickness(rhs.thickness), thickness_layers(rhs.thickness_layers), bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) {}; Surface(SurfaceType _surface_type, const ExPolygon &&_expolygon) : surface_type(_surface_type), expolygon(std::move(_expolygon)), thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0) {}; Surface(const Surface &other, const ExPolygon &&_expolygon) : surface_type(other.surface_type), expolygon(std::move(_expolygon)), thickness(other.thickness), thickness_layers(other.thickness_layers), bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters) {}; Surface& operator=(const Surface &rhs) { surface_type = rhs.surface_type; expolygon = rhs.expolygon; thickness = rhs.thickness; thickness_layers = rhs.thickness_layers; bridge_angle = rhs.bridge_angle; extra_perimeters = rhs.extra_perimeters; return *this; } Surface& operator=(Surface &&rhs) { surface_type = rhs.surface_type; expolygon = std::move(rhs.expolygon); thickness = rhs.thickness; thickness_layers = rhs.thickness_layers; bridge_angle = rhs.bridge_angle; extra_perimeters = rhs.extra_perimeters; return *this; } operator Polygons() const; double area() const; bool empty() const { return expolygon.empty(); } void clear() { expolygon.clear(); } bool is_solid() const; bool is_external() const; bool is_internal() const; bool is_bottom() const; bool is_bridge() const; }; typedef std::vector Surfaces; typedef std::vector SurfacesPtr; inline Polygons to_polygons(const Surfaces &src) { size_t num = 0; for (Surfaces::const_iterator it = src.begin(); it != src.end(); ++it) num += it->expolygon.holes.size() + 1; Polygons polygons; polygons.reserve(num); for (Surfaces::const_iterator it = src.begin(); it != src.end(); ++it) { polygons.emplace_back(it->expolygon.contour); for (Polygons::const_iterator ith = it->expolygon.holes.begin(); ith != it->expolygon.holes.end(); ++ith) polygons.emplace_back(*ith); } return polygons; } inline Polygons to_polygons(const SurfacesPtr &src) { size_t num = 0; for (SurfacesPtr::const_iterator it = src.begin(); it != src.end(); ++it) num += (*it)->expolygon.holes.size() + 1; Polygons polygons; polygons.reserve(num); for (SurfacesPtr::const_iterator it = src.begin(); it != src.end(); ++it) { polygons.emplace_back((*it)->expolygon.contour); for (Polygons::const_iterator ith = (*it)->expolygon.holes.begin(); ith != (*it)->expolygon.holes.end(); ++ith) polygons.emplace_back(*ith); } return polygons; } inline ExPolygons to_expolygons(const Surfaces &src) { ExPolygons expolygons; expolygons.reserve(src.size()); for (Surfaces::const_iterator it = src.begin(); it != src.end(); ++it) expolygons.emplace_back(it->expolygon); return expolygons; } inline ExPolygons to_expolygons(Surfaces &&src) { ExPolygons expolygons; expolygons.reserve(src.size()); for (Surfaces::const_iterator it = src.begin(); it != src.end(); ++it) expolygons.emplace_back(ExPolygon(std::move(it->expolygon))); src.clear(); return expolygons; } inline ExPolygons to_expolygons(const SurfacesPtr &src) { ExPolygons expolygons; expolygons.reserve(src.size()); for (SurfacesPtr::const_iterator it = src.begin(); it != src.end(); ++it) expolygons.emplace_back((*it)->expolygon); return expolygons; } // Count a nuber of polygons stored inside the vector of expolygons. // Useful for allocating space for polygons when converting expolygons to polygons. inline size_t number_polygons(const Surfaces &surfaces) { size_t n_polygons = 0; for (Surfaces::const_iterator it = surfaces.begin(); it != surfaces.end(); ++ it) n_polygons += it->expolygon.holes.size() + 1; return n_polygons; } inline size_t number_polygons(const SurfacesPtr &surfaces) { size_t n_polygons = 0; for (SurfacesPtr::const_iterator it = surfaces.begin(); it != surfaces.end(); ++ it) n_polygons += (*it)->expolygon.holes.size() + 1; return n_polygons; } // Append a vector of Surfaces at the end of another vector of polygons. inline void polygons_append(Polygons &dst, const Surfaces &src) { dst.reserve(dst.size() + number_polygons(src)); for (Surfaces::const_iterator it = src.begin(); it != src.end(); ++ it) { dst.emplace_back(it->expolygon.contour); dst.insert(dst.end(), it->expolygon.holes.begin(), it->expolygon.holes.end()); } } inline void polygons_append(Polygons &dst, Surfaces &&src) { dst.reserve(dst.size() + number_polygons(src)); for (Surfaces::iterator it = src.begin(); it != src.end(); ++ it) { dst.emplace_back(std::move(it->expolygon.contour)); std::move(std::begin(it->expolygon.holes), std::end(it->expolygon.holes), std::back_inserter(dst)); it->expolygon.holes.clear(); } } // Append a vector of Surfaces at the end of another vector of polygons. inline void polygons_append(Polygons &dst, const SurfacesPtr &src) { dst.reserve(dst.size() + number_polygons(src)); for (SurfacesPtr::const_iterator it = src.begin(); it != src.end(); ++ it) { dst.emplace_back((*it)->expolygon.contour); dst.insert(dst.end(), (*it)->expolygon.holes.begin(), (*it)->expolygon.holes.end()); } } inline void polygons_append(Polygons &dst, SurfacesPtr &&src) { dst.reserve(dst.size() + number_polygons(src)); for (SurfacesPtr::const_iterator it = src.begin(); it != src.end(); ++ it) { dst.emplace_back(std::move((*it)->expolygon.contour)); std::move(std::begin((*it)->expolygon.holes), std::end((*it)->expolygon.holes), std::back_inserter(dst)); (*it)->expolygon.holes.clear(); } } // Append a vector of Surfaces at the end of another vector of polygons. inline void surfaces_append(Surfaces &dst, const ExPolygons &src, SurfaceType surfaceType) { dst.reserve(dst.size() + src.size()); for (const ExPolygon &expoly : src) dst.emplace_back(Surface(surfaceType, expoly)); } inline void surfaces_append(Surfaces &dst, const ExPolygons &src, const Surface &surfaceTempl) { dst.reserve(dst.size() + number_polygons(src)); for (const ExPolygon &expoly : src) dst.emplace_back(Surface(surfaceTempl, expoly)); } inline void surfaces_append(Surfaces &dst, const Surfaces &src) { dst.insert(dst.end(), src.begin(), src.end()); } inline void surfaces_append(Surfaces &dst, ExPolygons &&src, SurfaceType surfaceType) { dst.reserve(dst.size() + src.size()); for (ExPolygon &expoly : src) dst.emplace_back(Surface(surfaceType, std::move(expoly))); src.clear(); } inline void surfaces_append(Surfaces &dst, ExPolygons &&src, const Surface &surfaceTempl) { dst.reserve(dst.size() + number_polygons(src)); for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) dst.emplace_back(Surface(surfaceTempl, std::move(*it))); src.clear(); } inline void surfaces_append(Surfaces &dst, Surfaces &&src) { if (dst.empty()) { dst = std::move(src); } else { std::move(std::begin(src), std::end(src), std::back_inserter(dst)); src.clear(); } } extern BoundingBox get_extents(const Surface &surface); extern BoundingBox get_extents(const Surfaces &surfaces); extern BoundingBox get_extents(const SurfacesPtr &surfaces); inline bool surfaces_could_merge(const Surface &s1, const Surface &s2) { return s1.surface_type == s2.surface_type && s1.thickness == s2.thickness && s1.thickness_layers == s2.thickness_layers && s1.bridge_angle == s2.bridge_angle; } class SVG; extern const char* surface_type_to_color_name(const SurfaceType surface_type); extern void export_surface_type_legend_to_svg(SVG &svg, const Point &pos); extern Point export_surface_type_legend_to_svg_box_size(); extern bool export_to_svg(const char *path, const Surfaces &surfaces, const float transparency = 1.f); } #endif ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/SurfaceCollection.cpp����������������������������������������0000664�0000000�0000000�00000013024�13243544447�0023742�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "SurfaceCollection.hpp" #include "BoundingBox.hpp" #include "SVG.hpp" #include namespace Slic3r { SurfaceCollection::operator Polygons() const { return to_polygons(surfaces); } SurfaceCollection::operator ExPolygons() const { return to_expolygons(surfaces); } void SurfaceCollection::simplify(double tolerance) { Surfaces ss; for (Surfaces::const_iterator it_s = this->surfaces.begin(); it_s != this->surfaces.end(); ++it_s) { ExPolygons expp; it_s->expolygon.simplify(tolerance, &expp); for (ExPolygons::const_iterator it_e = expp.begin(); it_e != expp.end(); ++it_e) { Surface s = *it_s; s.expolygon = *it_e; ss.push_back(s); } } this->surfaces = ss; } /* group surfaces by common properties */ void SurfaceCollection::group(std::vector *retval) { for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) { // find a group with the same properties SurfacesPtr* group = NULL; for (std::vector::iterator git = retval->begin(); git != retval->end(); ++git) if (! git->empty() && surfaces_could_merge(*git->front(), *it)) { group = &*git; break; } // if no group with these properties exists, add one if (group == NULL) { retval->resize(retval->size() + 1); group = &retval->back(); } // append surface to group group->push_back(&*it); } } SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) { SurfacesPtr ss; for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { if (surface->surface_type == type) ss.push_back(&*surface); } return ss; } SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) { SurfacesPtr ss; for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { for (int i = 0; i < ntypes; ++ i) { if (surface->surface_type == types[i]) { ss.push_back(&*surface); break; } } } return ss; } void SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons) { for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { if (surface->surface_type == type) { Polygons pp = surface->expolygon; polygons->insert(polygons->end(), pp.begin(), pp.end()); } } } void SurfaceCollection::keep_type(const SurfaceType type) { size_t j = 0; for (size_t i = 0; i < surfaces.size(); ++ i) { if (surfaces[i].surface_type == type) { if (j < i) std::swap(surfaces[i], surfaces[j]); ++ j; } } if (j < surfaces.size()) surfaces.erase(surfaces.begin() + j, surfaces.end()); } void SurfaceCollection::keep_types(const SurfaceType *types, int ntypes) { size_t j = 0; for (size_t i = 0; i < surfaces.size(); ++ i) { bool keep = false; for (int k = 0; k < ntypes; ++ k) { if (surfaces[i].surface_type == types[k]) { keep = true; break; } } if (keep) { if (j < i) std::swap(surfaces[i], surfaces[j]); ++ j; } } if (j < surfaces.size()) surfaces.erase(surfaces.begin() + j, surfaces.end()); } void SurfaceCollection::remove_type(const SurfaceType type) { size_t j = 0; for (size_t i = 0; i < surfaces.size(); ++ i) { if (surfaces[i].surface_type != type) { if (j < i) std::swap(surfaces[i], surfaces[j]); ++ j; } } if (j < surfaces.size()) surfaces.erase(surfaces.begin() + j, surfaces.end()); } void SurfaceCollection::remove_types(const SurfaceType *types, int ntypes) { size_t j = 0; for (size_t i = 0; i < surfaces.size(); ++ i) { bool remove = false; for (int k = 0; k < ntypes; ++ k) { if (surfaces[i].surface_type == types[k]) { remove = true; break; } } if (! remove) { if (j < i) std::swap(surfaces[i], surfaces[j]); ++ j; } } if (j < surfaces.size()) surfaces.erase(surfaces.begin() + j, surfaces.end()); } void SurfaceCollection::export_to_svg(const char *path, bool show_labels) { BoundingBox bbox; for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) bbox.merge(get_extents(surface->expolygon)); Point legend_size = export_surface_type_legend_to_svg_box_size(); Point legend_pos(bbox.min.x, bbox.max.y); bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); SVG svg(path, bbox); const float transparency = 0.5f; for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency); if (show_labels) { int idx = int(surface - this->surfaces.begin()); char label[64]; sprintf(label, "%d", idx); svg.draw_text(surface->expolygon.contour.points.front(), label, "black"); } } export_surface_type_legend_to_svg(svg, legend_pos); svg.Close(); } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/SurfaceCollection.hpp����������������������������������������0000664�0000000�0000000�00000006153�13243544447�0023754�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef slic3r_SurfaceCollection_hpp_ #define slic3r_SurfaceCollection_hpp_ #include "libslic3r.h" #include "Surface.hpp" #include namespace Slic3r { class SurfaceCollection { public: Surfaces surfaces; SurfaceCollection() {}; SurfaceCollection(const Surfaces &surfaces) : surfaces(surfaces) {}; SurfaceCollection(Surfaces &&surfaces) : surfaces(std::move(surfaces)) {}; operator Polygons() const; operator ExPolygons() const; void simplify(double tolerance); void group(std::vector *retval); template bool any_internal_contains(const T &item) const { for (const Surface &surface : this->surfaces) if (surface.is_internal() && surface.expolygon.contains(item)) return true; return false; } template bool any_bottom_contains(const T &item) const { for (const Surface &surface : this->surfaces) if (surface.is_bottom() && surface.expolygon.contains(item)) return true; return false; } SurfacesPtr filter_by_type(const SurfaceType type); SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes); void keep_type(const SurfaceType type); void keep_types(const SurfaceType *types, int ntypes); void remove_type(const SurfaceType type); void remove_types(const SurfaceType *types, int ntypes); void filter_by_type(SurfaceType type, Polygons* polygons); void clear() { surfaces.clear(); } bool empty() const { return surfaces.empty(); } void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; } void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); } void set(const ExPolygons &src, SurfaceType surfaceType) { clear(); this->append(src, surfaceType); } void set(const ExPolygons &src, const Surface &surfaceTempl) { clear(); this->append(src, surfaceTempl); } void set(const Surfaces &src) { clear(); this->append(src); } void set(ExPolygons &&src, SurfaceType surfaceType) { clear(); this->append(std::move(src), surfaceType); } void set(ExPolygons &&src, const Surface &surfaceTempl) { clear(); this->append(std::move(src), surfaceTempl); } void set(Surfaces &&src) { clear(); this->append(std::move(src)); } void append(const SurfaceCollection &coll) { this->append(coll.surfaces); } void append(SurfaceCollection &&coll) { this->append(std::move(coll.surfaces)); } void append(const ExPolygons &src, SurfaceType surfaceType) { surfaces_append(this->surfaces, src, surfaceType); } void append(const ExPolygons &src, const Surface &surfaceTempl) { surfaces_append(this->surfaces, src, surfaceTempl); } void append(const Surfaces &src) { surfaces_append(this->surfaces, src); } void append(ExPolygons &&src, SurfaceType surfaceType) { surfaces_append(this->surfaces, std::move(src), surfaceType); } void append(ExPolygons &&src, const Surface &surfaceTempl) { surfaces_append(this->surfaces, std::move(src), surfaceTempl); } void append(Surfaces &&src) { surfaces_append(this->surfaces, std::move(src)); } // For debugging purposes: void export_to_svg(const char *path, bool show_labels); }; } #endif ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/TriangleMesh.cpp���������������������������������������������0000664�0000000�0000000�00000164657�13243544447�0022743�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "TriangleMesh.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" #include #include #include #include #include #include #include #include #include #include #include #if 0 #define DEBUG #define _DEBUG #undef NDEBUG #endif #include #ifdef SLIC3R_DEBUG // #define SLIC3R_TRIANGLEMESH_DEBUG #include "SVG.hpp" #endif namespace Slic3r { TriangleMesh::TriangleMesh() : repaired(false) { stl_initialize(&this->stl); } TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets ) : repaired(false) { stl_initialize(&this->stl); stl_file &stl = this->stl; stl.error = 0; stl.stats.type = inmemory; // count facets and allocate memory stl.stats.number_of_facets = facets.size(); stl.stats.original_num_facets = stl.stats.number_of_facets; stl_allocate(&stl); for (int i = 0; i < stl.stats.number_of_facets; i++) { stl_facet facet; facet.normal.x = 0; facet.normal.y = 0; facet.normal.z = 0; const Pointf3& ref_f1 = points[facets[i].x]; facet.vertex[0].x = ref_f1.x; facet.vertex[0].y = ref_f1.y; facet.vertex[0].z = ref_f1.z; const Pointf3& ref_f2 = points[facets[i].y]; facet.vertex[1].x = ref_f2.x; facet.vertex[1].y = ref_f2.y; facet.vertex[1].z = ref_f2.z; const Pointf3& ref_f3 = points[facets[i].z]; facet.vertex[2].x = ref_f3.x; facet.vertex[2].y = ref_f3.y; facet.vertex[2].z = ref_f3.z; facet.extra[0] = 0; facet.extra[1] = 0; stl.facet_start[i] = facet; } stl_get_size(&stl); } TriangleMesh::TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; } TriangleMesh::TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); } TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) { stl_close(&this->stl); this->stl = other.stl; this->repaired = other.repaired; this->stl.heads = nullptr; this->stl.tail = nullptr; this->stl.error = other.stl.error; if (other.stl.facet_start != nullptr) { this->stl.facet_start = (stl_facet*)calloc(other.stl.stats.number_of_facets, sizeof(stl_facet)); std::copy(other.stl.facet_start, other.stl.facet_start + other.stl.stats.number_of_facets, this->stl.facet_start); } if (other.stl.neighbors_start != nullptr) { this->stl.neighbors_start = (stl_neighbors*)calloc(other.stl.stats.number_of_facets, sizeof(stl_neighbors)); std::copy(other.stl.neighbors_start, other.stl.neighbors_start + other.stl.stats.number_of_facets, this->stl.neighbors_start); } if (other.stl.v_indices != nullptr) { this->stl.v_indices = (v_indices_struct*)calloc(other.stl.stats.number_of_facets, sizeof(v_indices_struct)); std::copy(other.stl.v_indices, other.stl.v_indices + other.stl.stats.number_of_facets, this->stl.v_indices); } if (other.stl.v_shared != nullptr) { this->stl.v_shared = (stl_vertex*)calloc(other.stl.stats.shared_vertices, sizeof(stl_vertex)); std::copy(other.stl.v_shared, other.stl.v_shared + other.stl.stats.shared_vertices, this->stl.v_shared); } return *this; } TriangleMesh& TriangleMesh::operator=(TriangleMesh &&other) { this->swap(other); return *this; } void TriangleMesh::swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); } TriangleMesh::~TriangleMesh() { stl_close(&this->stl); } void TriangleMesh::ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); } void TriangleMesh::write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); } void TriangleMesh::write_binary(const char* output_file) { stl_write_binary(&this->stl, output_file, ""); } void TriangleMesh::repair() { if (this->repaired) return; // admesh fails when repairing empty meshes if (this->stl.stats.number_of_facets == 0) return; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started"; // checking exact stl_check_facets_exact(&stl); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge); // checking nearby //int last_edges_fixed = 0; float tolerance = stl.stats.shortest_edge; float increment = stl.stats.bounding_diameter / 10000.0; int iterations = 2; if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { for (int i = 0; i < iterations; i++) { if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); stl_check_facets_nearby(&stl, tolerance); //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed); //last_edges_fixed = stl.stats.edges_fixed; tolerance += increment; } else { break; } } } // remove_unconnected if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { stl_remove_unconnected_facets(&stl); } // fill_holes if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { stl_fill_holes(&stl); stl_clear_error(&stl); } // commenting out the following call fixes: #574, #413, #269, #262, #259, #230, #228, #206 // // normal_directions // stl_fix_normal_directions(&stl); // normal_values stl_fix_normal_values(&stl); // always calculate the volume and reverse all normals if volume is negative stl_calculate_volume(&stl); // neighbors stl_verify_neighbors(&stl); this->repaired = true; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; } float TriangleMesh::volume() { if (this->stl.stats.volume == -1) stl_calculate_volume(&this->stl); return this->stl.stats.volume; } void TriangleMesh::check_topology() { // checking exact stl_check_facets_exact(&stl); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge); // checking nearby //int last_edges_fixed = 0; float tolerance = stl.stats.shortest_edge; float increment = stl.stats.bounding_diameter / 10000.0; int iterations = 2; if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { for (int i = 0; i < iterations; i++) { if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); stl_check_facets_nearby(&stl, tolerance); //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed); //last_edges_fixed = stl.stats.edges_fixed; tolerance += increment; } else { break; } } } } bool TriangleMesh::is_manifold() const { return this->stl.stats.connected_facets_3_edge == this->stl.stats.number_of_facets; } void TriangleMesh::reset_repair_stats() { this->stl.stats.degenerate_facets = 0; this->stl.stats.edges_fixed = 0; this->stl.stats.facets_removed = 0; this->stl.stats.facets_added = 0; this->stl.stats.facets_reversed = 0; this->stl.stats.backwards_edges = 0; this->stl.stats.normals_fixed = 0; } bool TriangleMesh::needed_repair() const { return this->stl.stats.degenerate_facets > 0 || this->stl.stats.edges_fixed > 0 || this->stl.stats.facets_removed > 0 || this->stl.stats.facets_added > 0 || this->stl.stats.facets_reversed > 0 || this->stl.stats.backwards_edges > 0; } size_t TriangleMesh::facets_count() const { return this->stl.stats.number_of_facets; } void TriangleMesh::WriteOBJFile(char* output_file) { stl_generate_shared_vertices(&stl); stl_write_obj(&stl, output_file); } void TriangleMesh::scale(float factor) { stl_scale(&(this->stl), factor); stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::scale(const Pointf3 &versor) { float fversor[3]; fversor[0] = versor.x; fversor[1] = versor.y; fversor[2] = versor.z; stl_scale_versor(&this->stl, fversor); stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::translate(float x, float y, float z) { if (x == 0.f && y == 0.f && z == 0.f) return; stl_translate_relative(&(this->stl), x, y, z); stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::rotate(float angle, const Axis &axis) { if (angle == 0.f) return; // admesh uses degrees angle = Slic3r::Geometry::rad2deg(angle); if (axis == X) { stl_rotate_x(&(this->stl), angle); } else if (axis == Y) { stl_rotate_y(&(this->stl), angle); } else if (axis == Z) { stl_rotate_z(&(this->stl), angle); } stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::rotate_x(float angle) { this->rotate(angle, X); } void TriangleMesh::rotate_y(float angle) { this->rotate(angle, Y); } void TriangleMesh::rotate_z(float angle) { this->rotate(angle, Z); } void TriangleMesh::mirror(const Axis &axis) { if (axis == X) { stl_mirror_yz(&this->stl); } else if (axis == Y) { stl_mirror_xz(&this->stl); } else if (axis == Z) { stl_mirror_xy(&this->stl); } stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::mirror_x() { this->mirror(X); } void TriangleMesh::mirror_y() { this->mirror(Y); } void TriangleMesh::mirror_z() { this->mirror(Z); } void TriangleMesh::align_to_origin() { this->translate( -(this->stl.stats.min.x), -(this->stl.stats.min.y), -(this->stl.stats.min.z) ); } void TriangleMesh::rotate(double angle, Point* center) { if (angle == 0.) return; this->translate(float(-center->x), float(-center->y), 0); stl_rotate_z(&(this->stl), (float)angle); this->translate(float(+center->x), float(+center->y), 0); } bool TriangleMesh::has_multiple_patches() const { // we need neighbors if (!this->repaired) CONFESS("split() requires repair()"); if (this->stl.stats.number_of_facets == 0) return false; std::vector facet_queue(this->stl.stats.number_of_facets, 0); std::vector facet_visited(this->stl.stats.number_of_facets, false); int facet_queue_cnt = 1; facet_queue[0] = 0; facet_visited[0] = true; while (facet_queue_cnt > 0) { int facet_idx = facet_queue[-- facet_queue_cnt]; facet_visited[facet_idx] = true; for (int j = 0; j < 3; ++ j) { int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; if (! facet_visited[neighbor_idx]) facet_queue[facet_queue_cnt ++] = neighbor_idx; } } // If any of the face was not visited at the first time, return "multiple bodies". for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; ++ facet_idx) if (! facet_visited[facet_idx]) return true; return false; } size_t TriangleMesh::number_of_patches() const { // we need neighbors if (!this->repaired) CONFESS("split() requires repair()"); if (this->stl.stats.number_of_facets == 0) return false; std::vector facet_queue(this->stl.stats.number_of_facets, 0); std::vector facet_visited(this->stl.stats.number_of_facets, false); int facet_queue_cnt = 0; size_t num_bodies = 0; for (;;) { // Find a seeding triangle for a new body. int facet_idx = 0; for (; facet_idx < this->stl.stats.number_of_facets; ++ facet_idx) if (! facet_visited[facet_idx]) { // A seed triangle was found. facet_queue[facet_queue_cnt ++] = facet_idx; facet_visited[facet_idx] = true; break; } if (facet_idx == this->stl.stats.number_of_facets) // No seed found. break; ++ num_bodies; while (facet_queue_cnt > 0) { int facet_idx = facet_queue[-- facet_queue_cnt]; facet_visited[facet_idx] = true; for (int j = 0; j < 3; ++ j) { int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; if (! facet_visited[neighbor_idx]) facet_queue[facet_queue_cnt ++] = neighbor_idx; } } } return num_bodies; } TriangleMeshPtrs TriangleMesh::split() const { TriangleMeshPtrs meshes; std::set seen_facets; // we need neighbors if (!this->repaired) CONFESS("split() requires repair()"); // loop while we have remaining facets for (;;) { // get the first facet std::queue facet_queue; std::deque facets; for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; facet_idx++) { if (seen_facets.find(facet_idx) == seen_facets.end()) { // if facet was not seen put it into queue and start searching facet_queue.push(facet_idx); break; } } if (facet_queue.empty()) break; while (!facet_queue.empty()) { int facet_idx = facet_queue.front(); facet_queue.pop(); if (seen_facets.find(facet_idx) != seen_facets.end()) continue; facets.push_back(facet_idx); for (int j = 0; j <= 2; j++) { facet_queue.push(this->stl.neighbors_start[facet_idx].neighbor[j]); } seen_facets.insert(facet_idx); } TriangleMesh* mesh = new TriangleMesh; meshes.push_back(mesh); mesh->stl.stats.type = inmemory; mesh->stl.stats.number_of_facets = facets.size(); mesh->stl.stats.original_num_facets = mesh->stl.stats.number_of_facets; stl_clear_error(&mesh->stl); stl_allocate(&mesh->stl); int first = 1; for (std::deque::const_iterator facet = facets.begin(); facet != facets.end(); ++facet) { mesh->stl.facet_start[facet - facets.begin()] = this->stl.facet_start[*facet]; stl_facet_stats(&mesh->stl, this->stl.facet_start[*facet], first); first = 0; } } return meshes; } void TriangleMesh::merge(const TriangleMesh &mesh) { // reset stats and metadata int number_of_facets = this->stl.stats.number_of_facets; stl_invalidate_shared_vertices(&this->stl); this->repaired = false; // update facet count and allocate more memory this->stl.stats.number_of_facets = number_of_facets + mesh.stl.stats.number_of_facets; this->stl.stats.original_num_facets = this->stl.stats.number_of_facets; stl_reallocate(&this->stl); // copy facets for (int i = 0; i < mesh.stl.stats.number_of_facets; i++) { this->stl.facet_start[number_of_facets + i] = mesh.stl.facet_start[i]; } // update size stl_get_size(&this->stl); } // Calculate projection of the mesh into the XY plane, in scaled coordinates. //FIXME This could be extremely slow! Use it for tiny meshes only! ExPolygons TriangleMesh::horizontal_projection() const { Polygons pp; pp.reserve(this->stl.stats.number_of_facets); for (int i = 0; i < this->stl.stats.number_of_facets; i++) { stl_facet* facet = &this->stl.facet_start[i]; Polygon p; p.points.resize(3); p.points[0] = Point::new_scale(facet->vertex[0].x, facet->vertex[0].y); p.points[1] = Point::new_scale(facet->vertex[1].x, facet->vertex[1].y); p.points[2] = Point::new_scale(facet->vertex[2].x, facet->vertex[2].y); p.make_counter_clockwise(); // do this after scaling, as winding order might change while doing that pp.push_back(p); } // the offset factor was tuned using groovemount.stl return union_ex(offset(pp, scale_(0.01)), true); } Polygon TriangleMesh::convex_hull() { this->require_shared_vertices(); Points pp; pp.reserve(this->stl.stats.shared_vertices); for (int i = 0; i < this->stl.stats.shared_vertices; ++ i) { stl_vertex* v = &this->stl.v_shared[i]; pp.emplace_back(Point::new_scale(v->x, v->y)); } return Slic3r::Geometry::convex_hull(pp); } BoundingBoxf3 TriangleMesh::bounding_box() const { BoundingBoxf3 bb; bb.defined = true; bb.min.x = this->stl.stats.min.x; bb.min.y = this->stl.stats.min.y; bb.min.z = this->stl.stats.min.z; bb.max.x = this->stl.stats.max.x; bb.max.y = this->stl.stats.max.y; bb.max.z = this->stl.stats.max.z; return bb; } void TriangleMesh::require_shared_vertices() { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start"; if (!this->repaired) this->repair(); if (this->stl.v_shared == NULL) { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices"; stl_generate_shared_vertices(&(this->stl)); } BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; } TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : mesh(_mesh) { _mesh->require_shared_vertices(); facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1); v_scaled_shared.assign(_mesh->stl.v_shared, _mesh->stl.v_shared + _mesh->stl.stats.shared_vertices); // Scale the copied vertices. for (int i = 0; i < this->mesh->stl.stats.shared_vertices; ++ i) { this->v_scaled_shared[i].x /= float(SCALING_FACTOR); this->v_scaled_shared[i].y /= float(SCALING_FACTOR); this->v_scaled_shared[i].z /= float(SCALING_FACTOR); } // Create a mapping from triangle edge into face. struct EdgeToFace { // Index of the 1st vertex of the triangle edge. vertex_low <= vertex_high. int vertex_low; // Index of the 2nd vertex of the triangle edge. int vertex_high; // Index of a triangular face. int face; // Index of edge in the face, starting with 1. Negative indices if the edge was stored reverse in (vertex_low, vertex_high). int face_edge; bool operator==(const EdgeToFace &other) const { return vertex_low == other.vertex_low && vertex_high == other.vertex_high; } bool operator<(const EdgeToFace &other) const { return vertex_low < other.vertex_low || (vertex_low == other.vertex_low && vertex_high < other.vertex_high); } }; std::vector edges_map; edges_map.assign(this->mesh->stl.stats.number_of_facets * 3, EdgeToFace()); for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) for (int i = 0; i < 3; ++ i) { EdgeToFace &e2f = edges_map[facet_idx*3+i]; e2f.vertex_low = this->mesh->stl.v_indices[facet_idx].vertex[i]; e2f.vertex_high = this->mesh->stl.v_indices[facet_idx].vertex[(i + 1) % 3]; e2f.face = facet_idx; // 1 based indexing, to be always strictly positive. e2f.face_edge = i + 1; if (e2f.vertex_low > e2f.vertex_high) { // Sort the vertices std::swap(e2f.vertex_low, e2f.vertex_high); // and make the face_edge negative to indicate a flipped edge. e2f.face_edge = - e2f.face_edge; } } std::sort(edges_map.begin(), edges_map.end()); // Assign a unique common edge id to touching triangle edges. int num_edges = 0; for (size_t i = 0; i < edges_map.size(); ++ i) { EdgeToFace &edge_i = edges_map[i]; if (edge_i.face == -1) // This edge has been connected to some neighbor already. continue; // Unconnected edge. Find its neighbor with the correct orientation. size_t j; bool found = false; for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j]; ++ j) if (edge_i.face_edge * edges_map[j].face_edge < 0 && edges_map[j].face != -1) { // Faces touching with opposite oriented edges and none of the edges is connected yet. found = true; break; } if (! found) { //FIXME Vojtech: Trying to find an edge with equal orientation. This smells. // admesh can assign the same edge ID to more than two facets (which is // still topologically correct), so we have to search for a duplicate of // this edge too in case it was already seen in this orientation for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j]; ++ j) if (edges_map[j].face != -1) { // Faces touching with equally oriented edges and none of the edges is connected yet. found = true; break; } } // Assign an edge index to the 1st face. this->facets_edges[edge_i.face * 3 + std::abs(edge_i.face_edge) - 1] = num_edges; if (found) { EdgeToFace &edge_j = edges_map[j]; this->facets_edges[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges; // Mark the edge as connected. edge_j.face = -1; } ++ num_edges; } } void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const { BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice"; /* This method gets called with a list of unscaled Z coordinates and outputs a vector pointer having the same number of items as the original list. Each item is a vector of polygons created by slicing our mesh at the given heights. This method should basically combine the behavior of the existing Perl methods defined in lib/Slic3r/TriangleMesh.pm: - analyze(): this creates the 'facets_edges' and the 'edges_facets' tables (we don't need the 'edges' table) - slice_facet(): this has to be done for each facet. It generates intersection lines with each plane identified by the Z list. The get_layer_range() binary search used to identify the Z range of the facet is already ported to C++ (see Object.xsp) - make_loops(): this has to be done for each layer. It creates polygons from the lines generated by the previous step. At the end, we free the tables generated by analyze() as we don't need them anymore. NOTE: this method accepts a vector of floats because the mesh coordinate type is float. */ BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::_slice_do"; std::vector lines(z.size()); { boost::mutex lines_mutex; tbb::parallel_for( tbb::blocked_range(0,this->mesh->stl.stats.number_of_facets), [&lines, &lines_mutex, &z, this](const tbb::blocked_range& range) { for (int facet_idx = range.begin(); facet_idx < range.end(); ++ facet_idx) this->_slice_do(facet_idx, &lines, &lines_mutex, z); } ); } // v_scaled_shared could be freed here // build loops BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::_make_loops_do"; layers->resize(z.size()); tbb::parallel_for( tbb::blocked_range(0, z.size()), [&lines, &layers, this](const tbb::blocked_range& range) { for (size_t line_idx = range.begin(); line_idx < range.end(); ++ line_idx) this->make_loops(lines[line_idx], &(*layers)[line_idx]); } ); BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice finished"; #ifdef SLIC3R_DEBUG { static int iRun = 0; for (size_t i = 0; i < z.size(); ++ i) { Polygons &polygons = (*layers)[i]; SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), union_ex(polygons, true)); for (Polygon &poly : polygons) { for (size_t i = 1; i < poly.points.size(); ++ i) assert(poly.points[i-1] != poly.points[i]); assert(poly.points.front() != poly.points.back()); } } ++ iRun; } #endif } void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector* lines, boost::mutex* lines_mutex, const std::vector &z) const { const stl_facet &facet = this->mesh->stl.facet_start[facet_idx]; // find facet extents const float min_z = fminf(facet.vertex[0].z, fminf(facet.vertex[1].z, facet.vertex[2].z)); const float max_z = fmaxf(facet.vertex[0].z, fmaxf(facet.vertex[1].z, facet.vertex[2].z)); #ifdef SLIC3R_DEBUG printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0].z, facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1].z, facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2].z); printf("z: min = %.2f, max = %.2f\n", min_z, max_z); #endif // find layer extents std::vector::const_iterator min_layer, max_layer; min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z #ifdef SLIC3R_DEBUG printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); #endif for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { std::vector::size_type layer_idx = it - z.begin(); IntersectionLine il; if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il)) { boost::lock_guard l(*lines_mutex); if (il.edge_type == feHorizontal) { // Insert all three edges of the face. const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; const bool reverse = this->mesh->stl.facet_start[facet_idx].normal.z < 0; for (int j = 0; j < 3; ++ j) { int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; if (reverse) std::swap(a_id, b_id); const stl_vertex *a = &this->v_scaled_shared[a_id]; const stl_vertex *b = &this->v_scaled_shared[b_id]; il.a.x = a->x; il.a.y = a->y; il.b.x = b->x; il.b.y = b->y; il.a_id = a_id; il.b_id = b_id; (*lines)[layer_idx].push_back(il); } } else (*lines)[layer_idx].push_back(il); } } } void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const { std::vector layers_p; this->slice(z, &layers_p); BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start"; layers->resize(z.size()); tbb::parallel_for( tbb::blocked_range(0, z.size()), [&layers_p, layers, this](const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { #ifdef SLIC3R_TRIANGLEMESH_DEBUG printf("Layer " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]); #endif this->make_expolygons(layers_p[layer_id], &(*layers)[layer_id]); } }); BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end"; } // Return true, if the facet has been sliced and line_out has been filled. bool TriangleMeshSlicer::slice_facet( float slice_z, const stl_facet &facet, const int facet_idx, const float min_z, const float max_z, IntersectionLine *line_out) const { IntersectionPoint points[3]; size_t num_points = 0; size_t points_on_layer[3]; size_t num_points_on_layer = 0; // Reorder vertices so that the first one is the one with lowest Z. // This is needed to get all intersection lines in a consistent order // (external on the right of the line) int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0); for (int j = i; j - i < 3; ++ j) { // loop through facet edges int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; const stl_vertex *a = &this->v_scaled_shared[a_id]; const stl_vertex *b = &this->v_scaled_shared[b_id]; // Is edge or face aligned with the cutting plane? if (a->z == slice_z && b->z == slice_z) { // Edge is horizontal and belongs to the current layer. const stl_vertex &v0 = this->v_scaled_shared[vertices[0]]; const stl_vertex &v1 = this->v_scaled_shared[vertices[1]]; const stl_vertex &v2 = this->v_scaled_shared[vertices[2]]; if (min_z == max_z) { // All three vertices are aligned with slice_z. line_out->edge_type = feHorizontal; if (this->mesh->stl.facet_start[facet_idx].normal.z < 0) { // If normal points downwards this is a bottom horizontal facet so we reverse its point order. std::swap(a, b); std::swap(a_id, b_id); } } else if (v0.z < slice_z || v1.z < slice_z || v2.z < slice_z) { // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. line_out->edge_type = feTop; std::swap(a, b); std::swap(a_id, b_id); } else { // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. line_out->edge_type = feBottom; } line_out->a.x = a->x; line_out->a.y = a->y; line_out->b.x = b->x; line_out->b.y = b->y; line_out->a_id = a_id; line_out->b_id = b_id; return true; } if (a->z == slice_z) { // Only point a alings with the cutting plane. points_on_layer[num_points_on_layer ++] = num_points; IntersectionPoint &point = points[num_points ++]; point.x = a->x; point.y = a->y; point.point_id = a_id; } else if (b->z == slice_z) { // Only point b alings with the cutting plane. points_on_layer[num_points_on_layer ++] = num_points; IntersectionPoint &point = points[num_points ++]; point.x = b->x; point.y = b->y; point.point_id = b_id; } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) { // A general case. The face edge intersects the cutting plane. Calculate the intersection point. IntersectionPoint &point = points[num_points ++]; point.x = b->x + (a->x - b->x) * (slice_z - b->z) / (a->z - b->z); point.y = b->y + (a->y - b->y) * (slice_z - b->z) / (a->z - b->z); point.edge_id = edge_id; } } // We can't have only one point on layer because each vertex gets detected // twice (once for each edge), and we can't have three points on layer, // because we assume this code is not getting called for horizontal facets. assert(num_points_on_layer == 0 || num_points_on_layer == 2); if (num_points_on_layer > 0) { assert(points[points_on_layer[0]].point_id == points[points_on_layer[1]].point_id); assert(num_points == 2 || num_points == 3); if (num_points < 3) // This triangle touches the cutting plane with a single vertex. Ignore it. return false; // Erase one of the duplicate points. -- num_points; for (int i = points_on_layer[1]; i < num_points; ++ i) points[i] = points[i + 1]; } // Facets must intersect each plane 0 or 2 times. assert(num_points == 0 || num_points == 2); if (num_points == 2) { line_out->edge_type = feNone; line_out->a = (Point)points[1]; line_out->b = (Point)points[0]; line_out->a_id = points[1].point_id; line_out->b_id = points[0].point_id; line_out->edge_a_id = points[1].edge_id; line_out->edge_b_id = points[0].edge_id; return true; } return false; } void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { // Remove tangent edges. //FIXME This is O(n^2) in rare cases when many faces intersect the cutting plane. for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++ line) if (! line->skip && line->edge_type != feNone) { // This line is af facet edge. There may be a duplicate line with the same end vertices. // If the line is is an edge connecting two facets, find another facet edge // having the same endpoints but in reverse order. for (IntersectionLines::iterator line2 = line + 1; line2 != lines.end(); ++ line2) if (! line2->skip && line2->edge_type != feNone) { // Are these facets adjacent? (sharing a common edge on this layer) if (line->a_id == line2->a_id && line->b_id == line2->b_id) { line2->skip = true; /* if they are both oriented upwards or downwards (like a 'V') then we can remove both edges from this layer since it won't affect the sliced shape */ /* if one of them is oriented upwards and the other is oriented downwards, let's only keep one of them (it doesn't matter which one since all 'top' lines were reversed at slicing) */ if (line->edge_type == line2->edge_type) { line->skip = true; break; } } else if (line->a_id == line2->b_id && line->b_id == line2->a_id) { /* if this edge joins two horizontal facets, remove both of them */ if (line->edge_type == feHorizontal && line2->edge_type == feHorizontal) { line->skip = true; line2->skip = true; break; } } } } // Build a map of lines by edge_a_id and a_id. std::vector by_edge_a_id; std::vector by_a_id; by_edge_a_id.reserve(lines.size()); by_a_id.reserve(lines.size()); for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++ line) { if (! line->skip) { if (line->edge_a_id != -1) by_edge_a_id.push_back(&(*line)); // [line->edge_a_id].push_back(); if (line->a_id != -1) by_a_id.push_back(&(*line)); // [line->a_id].push_back(&(*line)); } } auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); IntersectionLines::iterator it_line_seed = lines.begin(); CYCLE: while (1) { // take first spare line and start a new loop IntersectionLine *first_line = nullptr; for (; it_line_seed != lines.end(); ++ it_line_seed) if (! it_line_seed->skip) { first_line = &(*it_line_seed ++); break; } if (first_line == nullptr) break; first_line->skip = true; Points loop_pts; loop_pts.push_back(first_line->a); IntersectionLine *last_line = first_line; /* printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); */ IntersectionLine key; for (;;) { // find a line starting where last one finishes IntersectionLine* next_line = nullptr; if (last_line->edge_b_id != -1) { key.edge_a_id = last_line->edge_b_id; auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); if (it_begin != by_edge_a_id.end()) { auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); for (auto it_line = it_begin; it_line != it_end; ++ it_line) if (! (*it_line)->skip) { next_line = *it_line; break; } } } if (next_line == nullptr && last_line->b_id != -1) { key.a_id = last_line->b_id; auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); if (it_begin != by_a_id.end()) { auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); for (auto it_line = it_begin; it_line != it_end; ++ it_line) if (! (*it_line)->skip) { next_line = *it_line; break; } } } if (next_line == nullptr) { // check whether we closed this loop if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { // loop is complete loops->emplace_back(std::move(loop_pts)); #ifdef SLIC3R_TRIANGLEMESH_DEBUG printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); #endif goto CYCLE; } // we can't close this loop! //// push @failed_loops, [@loop]; //#ifdef SLIC3R_TRIANGLEMESH_DEBUG printf(" Unable to close this loop having %d points\n", (int)loop_pts.size()); //#endif goto CYCLE; } /* printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); */ loop_pts.push_back(next_line->a); last_line = next_line; next_line->skip = true; } } } // Only used to cut the mesh into two halves. void TriangleMeshSlicer::make_expolygons_simple(std::vector &lines, ExPolygons* slices) const { assert(slices->empty()); Polygons loops; this->make_loops(lines, &loops); Polygons holes; for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) { if (loop->area() >= 0.) { ExPolygon ex; ex.contour = *loop; slices->push_back(ex); } else { holes.push_back(*loop); } } // If there are holes, then there should also be outer contours. assert(holes.empty() || ! slices->empty()); if (slices->empty()) return; // Assign holes to outer contours. for (Polygons::const_iterator hole = holes.begin(); hole != holes.end(); ++ hole) { // Find an outer contour to a hole. int slice_idx = -1; double current_contour_area = std::numeric_limits::max(); for (ExPolygons::iterator slice = slices->begin(); slice != slices->end(); ++ slice) { if (slice->contour.contains(hole->points.front())) { double area = slice->contour.area(); if (area < current_contour_area) { slice_idx = slice - slices->begin(); current_contour_area = area; } } } // assert(slice_idx != -1); if (slice_idx == -1) // Ignore this hole. continue; assert(current_contour_area < std::numeric_limits::max() && current_contour_area >= -hole->area()); (*slices)[slice_idx].holes.emplace_back(std::move(*hole)); } #if 0 // If the input mesh is not valid, the holes may intersect with the external contour. // Rather subtract them from the outer contour. Polygons poly; for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { if (it_slice->holes.empty()) { poly.emplace_back(std::move(it_slice->contour)); } else { Polygons contours; contours.emplace_back(std::move(it_slice->contour)); for (auto it = it_slice->holes.begin(); it != it_slice->holes.end(); ++ it) it->reverse(); polygons_append(poly, diff(contours, it_slice->holes)); } } // If the input mesh is not valid, the input contours may intersect. *slices = union_ex(poly); #endif #if 0 // If the input mesh is not valid, the holes may intersect with the external contour. // Rather subtract them from the outer contour. ExPolygons poly; for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { Polygons contours; contours.emplace_back(std::move(it_slice->contour)); for (auto it = it_slice->holes.begin(); it != it_slice->holes.end(); ++ it) it->reverse(); expolygons_append(poly, diff_ex(contours, it_slice->holes)); } // If the input mesh is not valid, the input contours may intersect. *slices = std::move(poly); #endif } void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slices) const { /* Input loops are not suitable for evenodd nor nonzero fill types, as we might get two consecutive concentric loops having the same winding order - and we have to respect such order. In that case, evenodd would create wrong inversions, and nonzero would ignore holes inside two concentric contours. So we're ordering loops and collapse consecutive concentric loops having the same winding order. TODO: find a faster algorithm for this, maybe with some sort of binary search. If we computed a "nesting tree" we could also just remove the consecutive loops having the same winding order, and remove the extra one(s) so that we could just supply everything to offset() instead of performing several union/diff calls. we sort by area assuming that the outermost loops have larger area; the previous sorting method, based on $b->contains($a->[0]), failed to nest loops correctly in some edge cases when original model had overlapping facets */ std::vector area; std::vector sorted_area; // vector of indices for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) { area.push_back(loop->area()); sorted_area.push_back(loop - loops.begin()); } // outer first std::sort(sorted_area.begin(), sorted_area.end(), [&area](size_t a, size_t b) { return std::abs(area[a]) > std::abs(area[b]); }); // we don't perform a safety offset now because it might reverse cw loops Polygons p_slices; for (std::vector::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++ loop_idx) { /* we rely on the already computed area to determine the winding order of the loops, since the Orientation() function provided by Clipper would do the same, thus repeating the calculation */ Polygons::const_iterator loop = loops.begin() + *loop_idx; if (area[*loop_idx] > +EPSILON) p_slices.push_back(*loop); else if (area[*loop_idx] < -EPSILON) //FIXME This is arbitrary and possibly very slow. // If the hole is inside a polygon, then there is no need to diff. // If the hole intersects a polygon boundary, then diff it, but then // there is no guarantee of an ordering of the loops. // Maybe we can test for the intersection before running the expensive diff algorithm? p_slices = diff(p_slices, *loop); } // perform a safety offset to merge very close facets (TODO: find test case for this) double safety_offset = scale_(0.0499); //FIXME see https://github.com/prusa3d/Slic3r/issues/520 // double safety_offset = scale_(0.0001); ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); #ifdef SLIC3R_TRIANGLEMESH_DEBUG size_t holes_count = 0; for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++ e) holes_count += e->holes.size(); printf(PRINTF_ZU " surface(s) having " PRINTF_ZU " holes detected from " PRINTF_ZU " polylines\n", ex_slices.size(), holes_count, loops.size()); #endif // append to the supplied collection expolygons_append(*slices, ex_slices); } void TriangleMeshSlicer::make_expolygons(std::vector &lines, ExPolygons* slices) const { Polygons pp; this->make_loops(lines, &pp); this->make_expolygons(pp, slices); } void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) const { IntersectionLines upper_lines, lower_lines; float scaled_z = scale_(z); for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) { stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; // find facet extents float min_z = std::min(facet->vertex[0].z, std::min(facet->vertex[1].z, facet->vertex[2].z)); float max_z = std::max(facet->vertex[0].z, std::max(facet->vertex[1].z, facet->vertex[2].z)); // intersect facet with cutting plane IntersectionLine line; if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line)) { // Save intersection lines for generating correct triangulations. if (line.edge_type == feTop) { lower_lines.push_back(line); } else if (line.edge_type == feBottom) { upper_lines.push_back(line); } else if (line.edge_type != feHorizontal) { lower_lines.push_back(line); upper_lines.push_back(line); } } if (min_z > z || (min_z == z && max_z > z)) { // facet is above the cut plane and does not belong to it if (upper != NULL) stl_add_facet(&upper->stl, facet); } else if (max_z < z || (max_z == z && min_z < z)) { // facet is below the cut plane and does not belong to it if (lower != NULL) stl_add_facet(&lower->stl, facet); } else if (min_z < z && max_z > z) { // Facet is cut by the slicing plane. // look for the vertex on whose side of the slicing plane there are no other vertices int isolated_vertex; if ( (facet->vertex[0].z > z) == (facet->vertex[1].z > z) ) { isolated_vertex = 2; } else if ( (facet->vertex[1].z > z) == (facet->vertex[2].z > z) ) { isolated_vertex = 0; } else { isolated_vertex = 1; } // get vertices starting from the isolated one stl_vertex* v0 = &facet->vertex[isolated_vertex]; stl_vertex* v1 = &facet->vertex[(isolated_vertex+1) % 3]; stl_vertex* v2 = &facet->vertex[(isolated_vertex+2) % 3]; // intersect v0-v1 and v2-v0 with cutting plane and make new vertices stl_vertex v0v1, v2v0; v0v1.x = v1->x + (v0->x - v1->x) * (z - v1->z) / (v0->z - v1->z); v0v1.y = v1->y + (v0->y - v1->y) * (z - v1->z) / (v0->z - v1->z); v0v1.z = z; v2v0.x = v2->x + (v0->x - v2->x) * (z - v2->z) / (v0->z - v2->z); v2v0.y = v2->y + (v0->y - v2->y) * (z - v2->z) / (v0->z - v2->z); v2v0.z = z; // build the triangular facet stl_facet triangle; triangle.normal = facet->normal; triangle.vertex[0] = *v0; triangle.vertex[1] = v0v1; triangle.vertex[2] = v2v0; // build the facets forming a quadrilateral on the other side stl_facet quadrilateral[2]; quadrilateral[0].normal = facet->normal; quadrilateral[0].vertex[0] = *v1; quadrilateral[0].vertex[1] = *v2; quadrilateral[0].vertex[2] = v0v1; quadrilateral[1].normal = facet->normal; quadrilateral[1].vertex[0] = *v2; quadrilateral[1].vertex[1] = v2v0; quadrilateral[1].vertex[2] = v0v1; if (v0->z > z) { if (upper != NULL) stl_add_facet(&upper->stl, &triangle); if (lower != NULL) { stl_add_facet(&lower->stl, &quadrilateral[0]); stl_add_facet(&lower->stl, &quadrilateral[1]); } } else { if (upper != NULL) { stl_add_facet(&upper->stl, &quadrilateral[0]); stl_add_facet(&upper->stl, &quadrilateral[1]); } if (lower != NULL) stl_add_facet(&lower->stl, &triangle); } } } // triangulate holes of upper mesh if (upper != NULL) { // compute shape of section ExPolygons section; this->make_expolygons_simple(upper_lines, §ion); // triangulate section Polygons triangles; for (ExPolygons::const_iterator expolygon = section.begin(); expolygon != section.end(); ++expolygon) expolygon->triangulate_p2t(&triangles); // convert triangles to facets and append them to mesh for (Polygons::const_iterator polygon = triangles.begin(); polygon != triangles.end(); ++polygon) { Polygon p = *polygon; p.reverse(); stl_facet facet; facet.normal.x = 0; facet.normal.y = 0; facet.normal.z = -1; for (size_t i = 0; i <= 2; ++i) { facet.vertex[i].x = unscale(p.points[i].x); facet.vertex[i].y = unscale(p.points[i].y); facet.vertex[i].z = z; } stl_add_facet(&upper->stl, &facet); } } // triangulate holes of lower mesh if (lower != NULL) { // compute shape of section ExPolygons section; this->make_expolygons_simple(lower_lines, §ion); // triangulate section Polygons triangles; for (ExPolygons::const_iterator expolygon = section.begin(); expolygon != section.end(); ++expolygon) expolygon->triangulate_p2t(&triangles); // convert triangles to facets and append them to mesh for (Polygons::const_iterator polygon = triangles.begin(); polygon != triangles.end(); ++polygon) { stl_facet facet; facet.normal.x = 0; facet.normal.y = 0; facet.normal.z = 1; for (size_t i = 0; i <= 2; ++i) { facet.vertex[i].x = unscale(polygon->points[i].x); facet.vertex[i].y = unscale(polygon->points[i].y); facet.vertex[i].z = z; } stl_add_facet(&lower->stl, &facet); } } // Update the bounding box / sphere of the new meshes. stl_get_size(&upper->stl); stl_get_size(&lower->stl); } // Generate the vertex list for a cube solid of arbitrary size in X/Y/Z. TriangleMesh make_cube(double x, double y, double z) { Pointf3 pv[8] = { Pointf3(x, y, 0), Pointf3(x, 0, 0), Pointf3(0, 0, 0), Pointf3(0, y, 0), Pointf3(x, y, z), Pointf3(0, y, z), Pointf3(0, 0, z), Pointf3(x, 0, z) }; Point3 fv[12] = { Point3(0, 1, 2), Point3(0, 2, 3), Point3(4, 5, 6), Point3(4, 6, 7), Point3(0, 4, 7), Point3(0, 7, 1), Point3(1, 7, 6), Point3(1, 6, 2), Point3(2, 6, 5), Point3(2, 5, 3), Point3(4, 0, 3), Point3(4, 3, 5) }; std::vector facets(&fv[0], &fv[0]+12); Pointf3s vertices(&pv[0], &pv[0]+8); TriangleMesh mesh(vertices ,facets); return mesh; } // Generate the mesh for a cylinder and return it, using // the generated angle to calculate the top mesh triangles. // Default is 360 sides, angle fa is in radians. TriangleMesh make_cylinder(double r, double h, double fa) { Pointf3s vertices; std::vector facets; // 2 special vertices, top and bottom center, rest are relative to this vertices.push_back(Pointf3(0.0, 0.0, 0.0)); vertices.push_back(Pointf3(0.0, 0.0, h)); // adjust via rounding to get an even multiple for any provided angle. double angle = (2*PI / floor(2*PI / fa)); // for each line along the polygon approximating the top/bottom of the // circle, generate four points and four facets (2 for the wall, 2 for the // top and bottom. // Special case: Last line shares 2 vertices with the first line. unsigned id = vertices.size() - 1; vertices.push_back(Pointf3(sin(0) * r , cos(0) * r, 0)); vertices.push_back(Pointf3(sin(0) * r , cos(0) * r, h)); for (double i = 0; i < 2*PI; i+=angle) { Pointf3 b(0, r, 0); Pointf3 t(0, r, h); b.rotate(i, Pointf3(0,0,0)); t.rotate(i, Pointf3(0,0,h)); vertices.push_back(b); vertices.push_back(t); id = vertices.size() - 1; facets.push_back(Point3( 0, id - 1, id - 3)); // top facets.push_back(Point3(id, 1, id - 2)); // bottom facets.push_back(Point3(id, id - 2, id - 3)); // upper-right of side facets.push_back(Point3(id, id - 3, id - 1)); // bottom-left of side } // Connect the last set of vertices with the first. facets.push_back(Point3( 2, 0, id - 1)); facets.push_back(Point3( 1, 3, id)); facets.push_back(Point3(id, 3, 2)); facets.push_back(Point3(id, 2, id - 1)); TriangleMesh mesh(vertices, facets); return mesh; } // Generates mesh for a sphere centered about the origin, using the generated angle // to determine the granularity. // Default angle is 1 degree. TriangleMesh make_sphere(double rho, double fa) { Pointf3s vertices; std::vector facets; // Algorithm: // Add points one-by-one to the sphere grid and form facets using relative coordinates. // Sphere is composed effectively of a mesh of stacked circles. // adjust via rounding to get an even multiple for any provided angle. double angle = (2*PI / floor(2*PI / fa)); // Ring to be scaled to generate the steps of the sphere std::vector ring; for (double i = 0; i < 2*PI; i+=angle) { ring.push_back(i); } const size_t steps = ring.size(); const double increment = (double)(1.0 / (double)steps); // special case: first ring connects to 0,0,0 // insert and form facets. vertices.push_back(Pointf3(0.0, 0.0, -rho)); size_t id = vertices.size(); for (size_t i = 0; i < ring.size(); i++) { // Fixed scaling const double z = -rho + increment*rho*2.0; // radius of the circle for this step. const double r = sqrt(abs(rho*rho - z*z)); Pointf3 b(0, r, z); b.rotate(ring[i], Pointf3(0,0,z)); vertices.push_back(b); if (i == 0) { facets.push_back(Point3(1, 0, ring.size())); } else { facets.push_back(Point3(id, 0, id - 1)); } id++; } // General case: insert and form facets for each step, joining it to the ring below it. for (size_t s = 2; s < steps - 1; s++) { const double z = -rho + increment*(double)s*2.0*rho; const double r = sqrt(abs(rho*rho - z*z)); for (size_t i = 0; i < ring.size(); i++) { Pointf3 b(0, r, z); b.rotate(ring[i], Pointf3(0,0,z)); vertices.push_back(b); if (i == 0) { // wrap around facets.push_back(Point3(id + ring.size() - 1 , id, id - 1)); facets.push_back(Point3(id, id - ring.size(), id - 1)); } else { facets.push_back(Point3(id , id - ring.size(), (id - 1) - ring.size())); facets.push_back(Point3(id, id - 1 - ring.size() , id - 1)); } id++; } } // special case: last ring connects to 0,0,rho*2.0 // only form facets. vertices.push_back(Pointf3(0.0, 0.0, rho)); for (size_t i = 0; i < ring.size(); i++) { if (i == 0) { // third vertex is on the other side of the ring. facets.push_back(Point3(id, id - ring.size(), id - 1)); } else { facets.push_back(Point3(id, id - ring.size() + i, id - ring.size() + (i - 1))); } } id++; TriangleMesh mesh(vertices, facets); return mesh; } } ���������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/TriangleMesh.hpp���������������������������������������������0000664�0000000�0000000�00000012144�13243544447�0022727�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef slic3r_TriangleMesh_hpp_ #define slic3r_TriangleMesh_hpp_ #include "libslic3r.h" #include #include #include #include "BoundingBox.hpp" #include "Line.hpp" #include "Point.hpp" #include "Polygon.hpp" #include "ExPolygon.hpp" namespace Slic3r { class TriangleMesh; class TriangleMeshSlicer; typedef std::vector TriangleMeshPtrs; class TriangleMesh { public: TriangleMesh(); TriangleMesh(const Pointf3s &points, const std::vector &facets); TriangleMesh(const TriangleMesh &other); TriangleMesh(TriangleMesh &&other); TriangleMesh& operator=(const TriangleMesh &other); TriangleMesh& operator=(TriangleMesh &&other); void swap(TriangleMesh &other); ~TriangleMesh(); void ReadSTLFile(const char* input_file); void write_ascii(const char* output_file); void write_binary(const char* output_file); void repair(); float volume(); void check_topology(); bool is_manifold() const; void WriteOBJFile(char* output_file); void scale(float factor); void scale(const Pointf3 &versor); void translate(float x, float y, float z); void rotate(float angle, const Axis &axis); void rotate_x(float angle); void rotate_y(float angle); void rotate_z(float angle); void mirror(const Axis &axis); void mirror_x(); void mirror_y(); void mirror_z(); void align_to_origin(); void rotate(double angle, Point* center); TriangleMeshPtrs split() const; void merge(const TriangleMesh &mesh); ExPolygons horizontal_projection() const; Polygon convex_hull(); BoundingBoxf3 bounding_box() const; void reset_repair_stats(); bool needed_repair() const; size_t facets_count() const; // Returns true, if there are two and more connected patches in the mesh. // Returns false, if one or zero connected patch is in the mesh. bool has_multiple_patches() const; // Count disconnected triangle patches. size_t number_of_patches() const; stl_file stl; bool repaired; private: void require_shared_vertices(); friend class TriangleMeshSlicer; }; enum FacetEdgeType { // A general case, the cutting plane intersect a face at two different edges. feNone, // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. feTop, // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. feBottom, // All three vertices of a face are aligned with the cutting plane. feHorizontal }; class IntersectionPoint : public Point { public: IntersectionPoint() : point_id(-1), edge_id(-1) {}; // Inherits coord_t x, y // Where is this intersection point located? On mesh vertex or mesh edge? // Only one of the following will be set, the other will remain set to -1. // Index of the mesh vertex. int point_id; // Index of the mesh edge. int edge_id; }; class IntersectionLine : public Line { public: // Inherits Point a, b // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. // Vertex indices of the line end points. int a_id; int b_id; // Source mesh edges of the line end points. int edge_a_id; int edge_b_id; // feNone, feTop, feBottom, feHorizontal FacetEdgeType edge_type; // Used by TriangleMeshSlicer::make_loops() to skip duplicate edges. bool skip; IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feNone), skip(false) {}; }; typedef std::vector IntersectionLines; typedef std::vector IntersectionLinePtrs; class TriangleMeshSlicer { public: TriangleMeshSlicer(TriangleMesh* _mesh); void slice(const std::vector &z, std::vector* layers) const; void slice(const std::vector &z, std::vector* layers) const; bool slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, const float min_z, const float max_z, IntersectionLine *line_out) const; void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; private: const TriangleMesh *mesh; // Map from a facet to an edge index. std::vector facets_edges; // Scaled copy of this->mesh->stl.v_shared std::vector v_scaled_shared; void _slice_do(size_t facet_idx, std::vector* lines, boost::mutex* lines_mutex, const std::vector &z) const; void make_loops(std::vector &lines, Polygons* loops) const; void make_expolygons(const Polygons &loops, ExPolygons* slices) const; void make_expolygons_simple(std::vector &lines, ExPolygons* slices) const; void make_expolygons(std::vector &lines, ExPolygons* slices) const; }; TriangleMesh make_cube(double x, double y, double z); // Generate a TriangleMesh of a cylinder TriangleMesh make_cylinder(double r, double h, double fa=(2*PI/360)); TriangleMesh make_sphere(double rho, double fa=(2*PI/360)); } #endif ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/Utils.hpp����������������������������������������������������0000664�0000000�0000000�00000005737�13243544447�0021457�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef slic3r_Utils_hpp_ #define slic3r_Utils_hpp_ #include namespace Slic3r { extern void set_logging_level(unsigned int level); extern void trace(unsigned int level, const char *message); // Set a path with GUI resource files. void set_var_dir(const std::string &path); // Return a full path to the GUI resource files. const std::string& var_dir(); // Return a full resource path for a file_name. std::string var(const std::string &file_name); // Set a path with various static definition data (for example the initial config bundles). void set_resources_dir(const std::string &path); // Return a full path to the resources directory. const std::string& resources_dir(); // Set a path with preset files. void set_data_dir(const std::string &path); // Return a full path to the GUI resource files. const std::string& data_dir(); // A special type for strings encoded in the local Windows 8-bit code page. // This type is only needed for Perl bindings to relay to Perl that the string is raw, not UTF-8 encoded. typedef std::string local_encoded_string; // Convert an UTF-8 encoded string into local coding. // On Windows, the UTF-8 string is converted to a local 8-bit code page. // On OSX and Linux, this function does no conversion and returns a copy of the source string. extern local_encoded_string encode_path(const char *src); extern std::string decode_path(const char *src); extern std::string normalize_utf8_nfc(const char *src); // File path / name / extension splitting utilities, working with UTF-8, // to be published to Perl. namespace PerlUtils { // Get a file name including the extension. extern std::string path_to_filename(const char *src); // Get a file name without the extension. extern std::string path_to_stem(const char *src); // Get just the extension. extern std::string path_to_extension(const char *src); // Get a directory without the trailing slash. extern std::string path_to_parent_path(const char *src); }; // Timestamp formatted for header_slic3r_generated(). extern std::string timestamp_str(); // Standard "generated by Slic3r version xxx timestamp xxx" header string, // to be placed at the top of Slic3r generated files. inline std::string header_slic3r_generated() { return std::string("generated by " SLIC3R_FORK_NAME " " SLIC3R_VERSION " " ) + timestamp_str(); } // Encode a file into a multi-part HTTP response with a given boundary. std::string octoprint_encode_file_send_request_content(const char *path, bool select, bool print, const char *boundary); // Compute the next highest power of 2 of 32-bit v // http://graphics.stanford.edu/~seander/bithacks.html template inline T next_highest_power_of_2(T v) { if (v != 0) -- v; v |= v >> 1; v |= v >> 2; v |= v >> 4; if (sizeof(T) >= sizeof(uint16_t)) v |= v >> 8; if (sizeof(T) >= sizeof(uint32_t)) v |= v >> 16; if (sizeof(T) >= sizeof(uint64_t)) v |= v >> 32; return ++ v; } } // namespace Slic3r #endif // slic3r_Utils_hpp_ ���������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/libslic3r.h��������������������������������������������������0000664�0000000�0000000�00000012166�13243544447�0021677�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef _libslic3r_h_ #define _libslic3r_h_ // this needs to be included early for MSVC (listing it in Build.PL is not enough) #include #include #include #include #include #include #include #include #include #include #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" #define SLIC3R_VERSION "1.39.0" #define SLIC3R_BUILD "UNKNOWN" typedef int32_t coord_t; typedef double coordf_t; //FIXME This epsilon value is used for many non-related purposes: // For a threshold of a squared Euclidean distance, // for a trheshold in a difference of radians, // for a threshold of a cross product of two non-normalized vectors etc. #define EPSILON 1e-4 // Scaling factor for a conversion from coord_t to coordf_t: 10e-6 // This scaling generates a following fixed point representation with for a 32bit integer: // 0..4294mm with 1nm resolution // int32_t fits an interval of (-2147.48mm, +2147.48mm) #define SCALING_FACTOR 0.000001 // RESOLUTION, SCALED_RESOLUTION: Used as an error threshold for a Douglas-Peucker polyline simplification algorithm. #define RESOLUTION 0.0125 #define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR) #define PI 3.141592653589793238 // When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam. #define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15 // Maximum perimeter length for the loop to apply the small perimeter speed. #define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI #define INSET_OVERLAP_TOLERANCE 0.4 // 3mm ring around the top / bottom / bridging areas. //FIXME This is quite a lot. #define EXTERNAL_INFILL_MARGIN 3. //FIXME Better to use an inline function with an explicit return type. //inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); } #define scale_(val) ((val) / SCALING_FACTOR) #define unscale(val) ((val) * SCALING_FACTOR) #define SCALED_EPSILON scale_(EPSILON) /* Implementation of CONFESS("foo"): */ #ifdef _MSC_VER #define CONFESS(...) confess_at(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) #else #define CONFESS(...) confess_at(__FILE__, __LINE__, __func__, __VA_ARGS__) #endif void confess_at(const char *file, int line, const char *func, const char *pat, ...); /* End implementation of CONFESS("foo"): */ // Which C++ version is supported? // For example, could optimized functions with move semantics be used? #if __cplusplus==201402L #define SLIC3R_CPPVER 14 #define STDMOVE(WHAT) std::move(WHAT) #elif __cplusplus==201103L #define SLIC3R_CPPVER 11 #define STDMOVE(WHAT) std::move(WHAT) #else #define SLIC3R_CPPVER 0 #define STDMOVE(WHAT) (WHAT) #endif #define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/" inline std::string debug_out_path(const char *name, ...) { char buffer[2048]; va_list args; va_start(args, name); std::vsprintf(buffer, name, args); va_end(args); return std::string(SLIC3R_DEBUG_OUT_PATH_PREFIX) + std::string(buffer); } #ifdef _MSC_VER // Visual Studio older than 2015 does not support the prinf type specifier %zu. Use %Iu instead. #define PRINTF_ZU "%Iu" #else #define PRINTF_ZU "%zu" #endif #ifndef UNUSED #define UNUSED(x) (void)(x) #endif /* UNUSED */ // Detect whether the compiler supports C++11 noexcept exception specifications. #if defined(_MSC_VER) && _MSC_VER < 1900 #define noexcept throw() #endif // Write slices as SVG images into out directory during the 2D processing of the slices. // #define SLIC3R_DEBUG_SLICE_PROCESSING namespace Slic3r { enum Axis { X=0, Y, Z, E, F, NUM_AXES }; template inline void append_to(std::vector &dst, const std::vector &src) { dst.insert(dst.end(), src.begin(), src.end()); } template inline void append(std::vector& dest, const std::vector& src) { if (dest.empty()) dest = src; else dest.insert(dest.end(), src.begin(), src.end()); } template inline void append(std::vector& dest, std::vector&& src) { if (dest.empty()) dest = std::move(src); else std::move(std::begin(src), std::end(src), std::back_inserter(dest)); src.clear(); src.shrink_to_fit(); } template inline void remove_nulls(std::vector &vec) { vec.erase( std::remove_if(vec.begin(), vec.end(), [](const T *ptr) { return ptr == nullptr; }), vec.end()); } template inline void sort_remove_duplicates(std::vector &vec) { std::sort(vec.begin(), vec.end()); vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); } // Older compilers do not provide a std::make_unique template. Provide a simple one. template inline std::unique_ptr make_unique(Args&&... args) { return std::unique_ptr(new T(std::forward(args)...)); } template static inline T sqr(T x) { return x * x; } template static inline T clamp(const T low, const T high, const T value) { return std::max(low, std::min(high, value)); } template static inline T lerp(const T a, const T b, const T t) { assert(t >= T(-EPSILON) && t <= T(1.+EPSILON)); return (1. - t) * a + t * b; } } // namespace Slic3r #endif ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/libslic3r/utils.cpp����������������������������������������������������0000664�0000000�0000000�00000021071�13243544447�0021477�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include #include #include #include #include #include #include #include #include #include #include #include #include namespace Slic3r { static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error; void set_logging_level(unsigned int level) { switch (level) { // Report fatal errors only. case 0: logSeverity = boost::log::trivial::fatal; break; // Report fatal errors and errors. case 1: logSeverity = boost::log::trivial::error; break; // Report fatal errors, errors and warnings. case 2: logSeverity = boost::log::trivial::warning; break; // Report all errors, warnings and infos. case 3: logSeverity = boost::log::trivial::info; break; // Report all errors, warnings, infos and debugging. case 4: logSeverity = boost::log::trivial::debug; break; // Report everyting including fine level tracing information. default: logSeverity = boost::log::trivial::trace; break; } boost::log::core::get()->set_filter ( boost::log::trivial::severity >= logSeverity ); } // Force set_logging_level(<=error) after loading of the DLL. // Switch boost::filesystem to utf8. static struct RunOnInit { RunOnInit() { boost::nowide::nowide_filesystem(); set_logging_level(1); } } g_RunOnInit; void trace(unsigned int level, const char *message) { boost::log::trivial::severity_level severity = boost::log::trivial::trace; switch (level) { // Report fatal errors only. case 0: severity = boost::log::trivial::fatal; break; // Report fatal errors and errors. case 1: severity = boost::log::trivial::error; break; // Report fatal errors, errors and warnings. case 2: severity = boost::log::trivial::warning; break; // Report all errors, warnings and infos. case 3: severity = boost::log::trivial::info; break; // Report all errors, warnings, infos and debugging. case 4: severity = boost::log::trivial::debug; break; // Report everyting including fine level tracing information. default: severity = boost::log::trivial::trace; break; } BOOST_LOG_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(),\ (::boost::log::keywords::severity = severity)) << message; } static std::string g_var_dir; void set_var_dir(const std::string &dir) { g_var_dir = dir; } const std::string& var_dir() { return g_var_dir; } std::string var(const std::string &file_name) { auto file = boost::filesystem::canonical(boost::filesystem::path(g_var_dir) / file_name).make_preferred(); return file.string(); } static std::string g_resources_dir; void set_resources_dir(const std::string &dir) { g_resources_dir = dir; } const std::string& resources_dir() { return g_resources_dir; } static std::string g_data_dir; void set_data_dir(const std::string &dir) { g_data_dir = dir; } const std::string& data_dir() { return g_data_dir; } } // namespace Slic3r #ifdef SLIC3R_HAS_BROKEN_CROAK // Some Strawberry Perl builds (mainly the latest 64bit builds) have a broken mechanism // for emiting Perl exception after handling a C++ exception. Perl interpreter // simply hangs. Better to show a message box in that case and stop the application. #include #include #include #include #ifdef WIN32 #include #endif void confess_at(const char *file, int line, const char *func, const char *format, ...) { char dest[1024*8]; va_list argptr; va_start(argptr, format); vsprintf(dest, format, argptr); va_end(argptr); char filelinefunc[1024*8]; sprintf(filelinefunc, "\r\nin function: %s\r\nfile: %s\r\nline: %d\r\n", func, file, line); strcat(dest, filelinefunc); strcat(dest, "\r\n Closing the application.\r\n"); #ifdef WIN32 ::MessageBoxA(NULL, dest, "Slic3r Prusa Edition", MB_OK | MB_ICONERROR); #endif // Give up. printf(dest); exit(-1); } #else #include void confess_at(const char *file, int line, const char *func, const char *pat, ...) { #ifdef SLIC3RXS va_list args; SV *error_sv = newSVpvf("Error in function %s at %s:%d: ", func, file, line); va_start(args, pat); sv_vcatpvf(error_sv, pat, &args); va_end(args); sv_catpvn(error_sv, "\n\t", 2); dSP; ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs( sv_2mortal(error_sv) ); PUTBACK; call_pv("Carp::confess", G_DISCARD); FREETMPS; LEAVE; #endif } #endif #ifdef WIN32 #ifndef NOMINMAX # define NOMINMAX #endif #include #endif /* WIN32 */ namespace Slic3r { // Encode an UTF-8 string to the local code page. std::string encode_path(const char *src) { #ifdef WIN32 // Convert the source utf8 encoded string to a wide string. std::wstring wstr_src = boost::nowide::widen(src); if (wstr_src.length() == 0) return std::string(); // Convert a wide string to a local code page. int size_needed = ::WideCharToMultiByte(0, 0, wstr_src.data(), (int)wstr_src.size(), nullptr, 0, nullptr, nullptr); std::string str_dst(size_needed, 0); ::WideCharToMultiByte(0, 0, wstr_src.data(), (int)wstr_src.size(), const_cast(str_dst.data()), size_needed, nullptr, nullptr); return str_dst; #else /* WIN32 */ return src; #endif /* WIN32 */ } // Encode an 8-bit string from a local code page to UTF-8. std::string decode_path(const char *src) { #ifdef WIN32 int len = int(strlen(src)); if (len == 0) return std::string(); // Convert the string encoded using the local code page to a wide string. int size_needed = ::MultiByteToWideChar(0, 0, src, len, nullptr, 0); std::wstring wstr_dst(size_needed, 0); ::MultiByteToWideChar(0, 0, src, len, const_cast(wstr_dst.data()), size_needed); // Convert a wide string to utf8. return boost::nowide::narrow(wstr_dst.c_str()); #else /* WIN32 */ return src; #endif /* WIN32 */ } std::string normalize_utf8_nfc(const char *src) { static std::locale locale_utf8(boost::locale::generator().generate("")); return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8); } namespace PerlUtils { // Get a file name including the extension. std::string path_to_filename(const char *src) { return boost::filesystem::path(src).filename().string(); } // Get a file name without the extension. std::string path_to_stem(const char *src) { return boost::filesystem::path(src).stem().string(); } // Get just the extension. std::string path_to_extension(const char *src) { return boost::filesystem::path(src).extension().string(); } // Get a directory without the trailing slash. std::string path_to_parent_path(const char *src) { return boost::filesystem::path(src).parent_path().string(); } }; std::string timestamp_str() { const auto now = boost::posix_time::second_clock::local_time(); const auto date = now.date(); char buf[2048]; sprintf(buf, "on %04d-%02d-%02d at %02d:%02d:%02d", // Local date in an ANSII format. int(now.date().year()), int(now.date().month()), int(now.date().day()), int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds())); return buf; } std::string octoprint_encode_file_send_request_content(const char *cpath, bool select, bool print, const char *boundary) { // Read the complete G-code string into a string buffer. // It will throw if the file cannot be open or read. std::stringstream str_stream; { boost::nowide::ifstream ifs(cpath); str_stream << ifs.rdbuf(); } boost::filesystem::path path(cpath); std::string request = boundary + '\n'; request += "Content-Disposition: form-data; name=\""; request += path.stem().string() + "\"; filename=\"" + path.filename().string() + "\"\n"; request += "Content-Type: application/octet-stream\n\n"; request += str_stream.str(); request += boundary + '\n'; request += "Content-Disposition: form-data; name=\"select\"\n\n"; request += select ? "true\n" : "false\n"; request += boundary + '\n'; request += "Content-Disposition: form-data; name=\"print\"\n\n"; request += print ? "true\n" : "false\n"; request += boundary + '\n'; return request; } }; // namespace Slic3r �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/perlglue.cpp�����������������������������������������������������������0000664�0000000�0000000�00000044775�13243544447�0020310�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifdef SLIC3RXS #include namespace Slic3r { REGISTER_CLASS(ExPolygon, "ExPolygon"); REGISTER_CLASS(ExPolygonCollection, "ExPolygon::Collection"); REGISTER_CLASS(ExtrusionMultiPath, "ExtrusionMultiPath"); REGISTER_CLASS(ExtrusionPath, "ExtrusionPath"); REGISTER_CLASS(ExtrusionLoop, "ExtrusionLoop"); // there is no ExtrusionLoop::Collection or ExtrusionEntity::Collection REGISTER_CLASS(ExtrusionEntityCollection, "ExtrusionPath::Collection"); REGISTER_CLASS(ExtrusionSimulator, "ExtrusionSimulator"); REGISTER_CLASS(Filler, "Filler"); REGISTER_CLASS(Flow, "Flow"); REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(GCode, "GCode"); REGISTER_CLASS(GCodeSender, "GCode::Sender"); REGISTER_CLASS(Layer, "Layer"); REGISTER_CLASS(SupportLayer, "Layer::Support"); REGISTER_CLASS(LayerRegion, "Layer::Region"); REGISTER_CLASS(Line, "Line"); REGISTER_CLASS(Linef3, "Linef3"); REGISTER_CLASS(PerimeterGenerator, "Layer::PerimeterGenerator"); REGISTER_CLASS(PlaceholderParser, "GCode::PlaceholderParser"); REGISTER_CLASS(Polygon, "Polygon"); REGISTER_CLASS(Polyline, "Polyline"); REGISTER_CLASS(PolylineCollection, "Polyline::Collection"); REGISTER_CLASS(Print, "Print"); REGISTER_CLASS(PrintObject, "Print::Object"); REGISTER_CLASS(PrintRegion, "Print::Region"); REGISTER_CLASS(Model, "Model"); REGISTER_CLASS(ModelMaterial, "Model::Material"); REGISTER_CLASS(ModelObject, "Model::Object"); REGISTER_CLASS(ModelVolume, "Model::Volume"); REGISTER_CLASS(ModelInstance, "Model::Instance"); REGISTER_CLASS(MotionPlanner, "MotionPlanner"); REGISTER_CLASS(BoundingBox, "Geometry::BoundingBox"); REGISTER_CLASS(BoundingBoxf, "Geometry::BoundingBoxf"); REGISTER_CLASS(BoundingBoxf3, "Geometry::BoundingBoxf3"); REGISTER_CLASS(BridgeDetector, "BridgeDetector"); REGISTER_CLASS(Point, "Point"); REGISTER_CLASS(Point3, "Point3"); REGISTER_CLASS(Pointf, "Pointf"); REGISTER_CLASS(Pointf3, "Pointf3"); REGISTER_CLASS(DynamicPrintConfig, "Config"); REGISTER_CLASS(StaticPrintConfig, "Config::Static"); REGISTER_CLASS(PrintObjectConfig, "Config::PrintObject"); REGISTER_CLASS(PrintRegionConfig, "Config::PrintRegion"); REGISTER_CLASS(GCodeConfig, "Config::GCode"); REGISTER_CLASS(PrintConfig, "Config::Print"); REGISTER_CLASS(FullPrintConfig, "Config::Full"); REGISTER_CLASS(Surface, "Surface"); REGISTER_CLASS(SurfaceCollection, "Surface::Collection"); REGISTER_CLASS(PrintObjectSupportMaterial, "Print::SupportMaterial2"); REGISTER_CLASS(TriangleMesh, "TriangleMesh"); REGISTER_CLASS(AppConfig, "GUI::AppConfig"); REGISTER_CLASS(GLShader, "GUI::_3DScene::GLShader"); REGISTER_CLASS(GLVolume, "GUI::_3DScene::GLVolume"); REGISTER_CLASS(GLVolumeCollection, "GUI::_3DScene::GLVolume::Collection"); REGISTER_CLASS(Preset, "GUI::Preset"); REGISTER_CLASS(PresetCollection, "GUI::PresetCollection"); REGISTER_CLASS(PresetBundle, "GUI::PresetBundle"); REGISTER_CLASS(PresetHints, "GUI::PresetHints"); SV* ConfigBase__as_hash(ConfigBase* THIS) { HV* hv = newHV(); for (auto &key : THIS->keys()) (void)hv_store(hv, key.c_str(), key.length(), ConfigBase__get(THIS, key), 0); return newRV_noinc((SV*)hv); } SV* ConfigBase__get(ConfigBase* THIS, const t_config_option_key &opt_key) { ConfigOption *opt = THIS->option(opt_key, false); return (opt == nullptr) ? &PL_sv_undef : ConfigOption_to_SV(*opt, *THIS->def()->get(opt_key)); } SV* ConfigOption_to_SV(const ConfigOption &opt, const ConfigOptionDef &def) { switch (def.type) { case coFloat: case coPercent: return newSVnv(static_cast(&opt)->value); case coFloats: case coPercents: { auto optv = static_cast(&opt); AV* av = newAV(); av_fill(av, optv->values.size()-1); for (const double &v : optv->values) av_store(av, &v - optv->values.data(), newSVnv(v)); return newRV_noinc((SV*)av); } case coInt: return newSViv(static_cast(&opt)->value); case coInts: { auto optv = static_cast(&opt); AV* av = newAV(); av_fill(av, optv->values.size()-1); for (const int &v : optv->values) av_store(av, &v - optv->values.data(), newSViv(v)); return newRV_noinc((SV*)av); } case coString: { auto optv = static_cast(&opt); // we don't serialize() because that would escape newlines return newSVpvn_utf8(optv->value.c_str(), optv->value.length(), true); } case coStrings: { auto optv = static_cast(&opt); AV* av = newAV(); av_fill(av, optv->values.size()-1); for (const std::string &v : optv->values) av_store(av, &v - optv->values.data(), newSVpvn_utf8(v.c_str(), v.length(), true)); return newRV_noinc((SV*)av); } case coPoint: return perl_to_SV_clone_ref(static_cast(&opt)->value); case coPoints: { auto optv = static_cast(&opt); AV* av = newAV(); av_fill(av, optv->values.size()-1); for (const Pointf &v : optv->values) av_store(av, &v - optv->values.data(), perl_to_SV_clone_ref(v)); return newRV_noinc((SV*)av); } case coBool: return newSViv(static_cast(&opt)->value ? 1 : 0); case coBools: { auto optv = static_cast(&opt); AV* av = newAV(); av_fill(av, optv->values.size()-1); for (size_t i = 0; i < optv->values.size(); ++ i) av_store(av, i, newSViv(optv->values[i] ? 1 : 0)); return newRV_noinc((SV*)av); } default: std::string serialized = opt.serialize(); return newSVpvn_utf8(serialized.c_str(), serialized.length(), true); } } SV* ConfigBase__get_at(ConfigBase* THIS, const t_config_option_key &opt_key, size_t i) { ConfigOption* opt = THIS->option(opt_key, false); if (opt == nullptr) return &PL_sv_undef; const ConfigOptionDef* def = THIS->def()->get(opt_key); switch (def->type) { case coFloats: case coPercents: return newSVnv(static_cast(opt)->get_at(i)); case coInts: return newSViv(static_cast(opt)->get_at(i)); case coStrings: { // we don't serialize() because that would escape newlines const std::string &val = static_cast(opt)->get_at(i); return newSVpvn_utf8(val.c_str(), val.length(), true); } case coPoints: return perl_to_SV_clone_ref(static_cast(opt)->get_at(i)); case coBools: return newSViv(static_cast(opt)->get_at(i) ? 1 : 0); default: return &PL_sv_undef; } } bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value) { ConfigOption* opt = THIS->option(opt_key, true); if (opt == nullptr) CONFESS("Trying to set non-existing option"); const ConfigOptionDef* def = THIS->def()->get(opt_key); if (opt->type() != def->type) CONFESS("Option type is different from the definition"); switch (def->type) { case coFloat: if (!looks_like_number(value)) return false; static_cast(opt)->value = SvNV(value); break; case coFloats: { std::vector &values = static_cast(opt)->values; AV* av = (AV*)SvRV(value); const size_t len = av_len(av)+1; values.clear(); values.reserve(len); for (size_t i = 0; i < len; ++ i) { SV** elem = av_fetch(av, i, 0); if (elem == NULL || !looks_like_number(*elem)) return false; values.emplace_back(SvNV(*elem)); } break; } case coPercents: { std::vector &values = static_cast(opt)->values; AV* av = (AV*)SvRV(value); const size_t len = av_len(av)+1; values.clear(); values.reserve(len); for (size_t i = 0; i < len; i++) { SV** elem = av_fetch(av, i, 0); if (elem == NULL || !looks_like_number(*elem)) return false; values.emplace_back(SvNV(*elem)); } break; } case coInt: if (!looks_like_number(value)) return false; static_cast(opt)->value = SvIV(value); break; case coInts: { std::vector &values = static_cast(opt)->values; AV* av = (AV*)SvRV(value); const size_t len = av_len(av)+1; values.clear(); values.reserve(len); for (size_t i = 0; i < len; i++) { SV** elem = av_fetch(av, i, 0); if (elem == NULL || !looks_like_number(*elem)) return false; values.emplace_back(SvIV(*elem)); } break; } case coString: static_cast(opt)->value = std::string(SvPV_nolen(value), SvCUR(value)); break; case coStrings: { std::vector &values = static_cast(opt)->values; AV* av = (AV*)SvRV(value); const size_t len = av_len(av)+1; values.clear(); values.reserve(len); for (size_t i = 0; i < len; i++) { SV** elem = av_fetch(av, i, 0); if (elem == NULL) return false; values.emplace_back(std::string(SvPV_nolen(*elem), SvCUR(*elem))); } break; } case coPoint: return from_SV_check(value, &static_cast(opt)->value); case coPoints: { std::vector &values = static_cast(opt)->values; AV* av = (AV*)SvRV(value); const size_t len = av_len(av)+1; values.clear(); values.reserve(len); for (size_t i = 0; i < len; i++) { SV** elem = av_fetch(av, i, 0); Pointf point; if (elem == NULL || !from_SV_check(*elem, &point)) return false; values.emplace_back(point); } break; } case coBool: static_cast(opt)->value = SvTRUE(value); break; case coBools: { std::vector &values = static_cast(opt)->values; AV* av = (AV*)SvRV(value); const size_t len = av_len(av)+1; values.clear(); values.reserve(len); for (size_t i = 0; i < len; i++) { SV** elem = av_fetch(av, i, 0); if (elem == NULL) return false; values.emplace_back(SvTRUE(*elem)); } break; } default: if (! opt->deserialize(std::string(SvPV_nolen(value)))) return false; } return true; } /* This method is implemented as a workaround for this typemap bug: https://rt.cpan.org/Public/Bug/Display.html?id=94110 */ bool ConfigBase__set_deserialize(ConfigBase* THIS, const t_config_option_key &opt_key, SV* str) { size_t len; const char * c = SvPV(str, len); std::string value(c, len); return THIS->set_deserialize(opt_key, value); } void ConfigBase__set_ifndef(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value, bool deserialize) { if (THIS->has(opt_key)) return; if (deserialize) ConfigBase__set_deserialize(THIS, opt_key, value); else ConfigBase__set(THIS, opt_key, value); } bool StaticConfig__set(StaticConfig* THIS, const t_config_option_key &opt_key, SV* value) { const ConfigOptionDef* optdef = THIS->def()->get(opt_key); if (optdef->shortcut.empty()) return ConfigBase__set(THIS, opt_key, value); for (const t_config_option_key &key : optdef->shortcut) if (! StaticConfig__set(THIS, key, value)) return false; return true; } SV* to_AV(ExPolygon* expolygon) { const unsigned int num_holes = expolygon->holes.size(); AV* av = newAV(); av_extend(av, num_holes); // -1 +1 av_store(av, 0, perl_to_SV_ref(expolygon->contour)); for (unsigned int i = 0; i < num_holes; i++) { av_store(av, i+1, perl_to_SV_ref(expolygon->holes[i])); } return newRV_noinc((SV*)av); } SV* to_SV_pureperl(const ExPolygon* expolygon) { const unsigned int num_holes = expolygon->holes.size(); AV* av = newAV(); av_extend(av, num_holes); // -1 +1 av_store(av, 0, to_SV_pureperl(&expolygon->contour)); for (unsigned int i = 0; i < num_holes; i++) { av_store(av, i+1, to_SV_pureperl(&expolygon->holes[i])); } return newRV_noinc((SV*)av); } void from_SV(SV* expoly_sv, ExPolygon* expolygon) { AV* expoly_av = (AV*)SvRV(expoly_sv); const unsigned int num_polygons = av_len(expoly_av)+1; expolygon->holes.resize(num_polygons-1); SV** polygon_sv = av_fetch(expoly_av, 0, 0); from_SV(*polygon_sv, &expolygon->contour); for (unsigned int i = 0; i < num_polygons-1; i++) { polygon_sv = av_fetch(expoly_av, i+1, 0); from_SV(*polygon_sv, &expolygon->holes[i]); } } void from_SV_check(SV* expoly_sv, ExPolygon* expolygon) { if (sv_isobject(expoly_sv) && (SvTYPE(SvRV(expoly_sv)) == SVt_PVMG)) { if (!sv_isa(expoly_sv, perl_class_name(expolygon)) && !sv_isa(expoly_sv, perl_class_name_ref(expolygon))) CONFESS("Not a valid %s object", perl_class_name(expolygon)); // a XS ExPolygon was supplied *expolygon = *(ExPolygon *)SvIV((SV*)SvRV( expoly_sv )); } else { // a Perl arrayref was supplied from_SV(expoly_sv, expolygon); } } void from_SV(SV* line_sv, Line* THIS) { AV* line_av = (AV*)SvRV(line_sv); from_SV_check(*av_fetch(line_av, 0, 0), &THIS->a); from_SV_check(*av_fetch(line_av, 1, 0), &THIS->b); } void from_SV_check(SV* line_sv, Line* THIS) { if (sv_isobject(line_sv) && (SvTYPE(SvRV(line_sv)) == SVt_PVMG)) { if (!sv_isa(line_sv, perl_class_name(THIS)) && !sv_isa(line_sv, perl_class_name_ref(THIS))) CONFESS("Not a valid %s object", perl_class_name(THIS)); *THIS = *(Line*)SvIV((SV*)SvRV( line_sv )); } else { from_SV(line_sv, THIS); } } SV* to_AV(Line* THIS) { AV* av = newAV(); av_extend(av, 1); av_store(av, 0, perl_to_SV_ref(THIS->a)); av_store(av, 1, perl_to_SV_ref(THIS->b)); return newRV_noinc((SV*)av); } SV* to_SV_pureperl(const Line* THIS) { AV* av = newAV(); av_extend(av, 1); av_store(av, 0, to_SV_pureperl(&THIS->a)); av_store(av, 1, to_SV_pureperl(&THIS->b)); return newRV_noinc((SV*)av); } void from_SV(SV* poly_sv, MultiPoint* THIS) { AV* poly_av = (AV*)SvRV(poly_sv); const unsigned int num_points = av_len(poly_av)+1; THIS->points.resize(num_points); for (unsigned int i = 0; i < num_points; i++) { SV** point_sv = av_fetch(poly_av, i, 0); from_SV_check(*point_sv, &THIS->points[i]); } } void from_SV_check(SV* poly_sv, MultiPoint* THIS) { if (sv_isobject(poly_sv) && (SvTYPE(SvRV(poly_sv)) == SVt_PVMG)) { *THIS = *(MultiPoint*)SvIV((SV*)SvRV( poly_sv )); } else { from_SV(poly_sv, THIS); } } SV* to_AV(MultiPoint* THIS) { const unsigned int num_points = THIS->points.size(); AV* av = newAV(); if (num_points > 0) av_extend(av, num_points-1); for (unsigned int i = 0; i < num_points; i++) { av_store(av, i, perl_to_SV_ref(THIS->points[i])); } return newRV_noinc((SV*)av); } SV* to_SV_pureperl(const MultiPoint* THIS) { const unsigned int num_points = THIS->points.size(); AV* av = newAV(); if (num_points > 0) av_extend(av, num_points-1); for (unsigned int i = 0; i < num_points; i++) { av_store(av, i, to_SV_pureperl(&THIS->points[i])); } return newRV_noinc((SV*)av); } void from_SV_check(SV* poly_sv, Polygon* THIS) { if (sv_isobject(poly_sv) && !sv_isa(poly_sv, perl_class_name(THIS)) && !sv_isa(poly_sv, perl_class_name_ref(THIS))) CONFESS("Not a valid %s object", perl_class_name(THIS)); from_SV_check(poly_sv, (MultiPoint*)THIS); } void from_SV_check(SV* poly_sv, Polyline* THIS) { if (!sv_isa(poly_sv, perl_class_name(THIS)) && !sv_isa(poly_sv, perl_class_name_ref(THIS))) CONFESS("Not a valid %s object", perl_class_name(THIS)); from_SV_check(poly_sv, (MultiPoint*)THIS); } SV* to_SV_pureperl(const Point* THIS) { AV* av = newAV(); av_fill(av, 1); av_store(av, 0, newSViv(THIS->x)); av_store(av, 1, newSViv(THIS->y)); return newRV_noinc((SV*)av); } void from_SV(SV* point_sv, Point* point) { AV* point_av = (AV*)SvRV(point_sv); // get a double from Perl and round it, otherwise // it would get truncated point->x = lrint(SvNV(*av_fetch(point_av, 0, 0))); point->y = lrint(SvNV(*av_fetch(point_av, 1, 0))); } void from_SV_check(SV* point_sv, Point* point) { if (sv_isobject(point_sv) && (SvTYPE(SvRV(point_sv)) == SVt_PVMG)) { if (!sv_isa(point_sv, perl_class_name(point)) && !sv_isa(point_sv, perl_class_name_ref(point))) CONFESS("Not a valid %s object (got %s)", perl_class_name(point), HvNAME(SvSTASH(SvRV(point_sv)))); *point = *(Point*)SvIV((SV*)SvRV( point_sv )); } else { from_SV(point_sv, point); } } SV* to_SV_pureperl(const Pointf* point) { AV* av = newAV(); av_fill(av, 1); av_store(av, 0, newSVnv(point->x)); av_store(av, 1, newSVnv(point->y)); return newRV_noinc((SV*)av); } bool from_SV(SV* point_sv, Pointf* point) { AV* point_av = (AV*)SvRV(point_sv); SV* sv_x = *av_fetch(point_av, 0, 0); SV* sv_y = *av_fetch(point_av, 1, 0); if (!looks_like_number(sv_x) || !looks_like_number(sv_y)) return false; point->x = SvNV(sv_x); point->y = SvNV(sv_y); return true; } bool from_SV_check(SV* point_sv, Pointf* point) { if (sv_isobject(point_sv) && (SvTYPE(SvRV(point_sv)) == SVt_PVMG)) { if (!sv_isa(point_sv, perl_class_name(point)) && !sv_isa(point_sv, perl_class_name_ref(point))) CONFESS("Not a valid %s object (got %s)", perl_class_name(point), HvNAME(SvSTASH(SvRV(point_sv)))); *point = *(Pointf*)SvIV((SV*)SvRV( point_sv )); return true; } else { return from_SV(point_sv, point); } } void from_SV_check(SV* surface_sv, Surface* THIS) { if (!sv_isa(surface_sv, perl_class_name(THIS)) && !sv_isa(surface_sv, perl_class_name_ref(THIS))) CONFESS("Not a valid %s object", perl_class_name(THIS)); // a XS Surface was supplied *THIS = *(Surface *)SvIV((SV*)SvRV( surface_sv )); } SV* to_SV(TriangleMesh* THIS) { SV* sv = newSV(0); sv_setref_pv( sv, perl_class_name(THIS), (void*)THIS ); return sv; } } #endif ���Slic3r-version_1.39.1/xs/src/poly2tri/��������������������������������������������������������������0000775�0000000�0000000�00000000000�13243544447�0017530�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/poly2tri/common/�������������������������������������������������������0000775�0000000�0000000�00000000000�13243544447�0021020�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Slic3r-version_1.39.1/xs/src/poly2tri/common/shapes.cc����������������������������������������������0000664�0000000�0000000�00000021770�13243544447�0022621�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "shapes.h" #include namespace p2t { Triangle::Triangle(Point& a, Point& b, Point& c) { points_[0] = &a; points_[1] = &b; points_[2] = &c; neighbors_[0] = NULL; neighbors_[1] = NULL; neighbors_[2] = NULL; constrained_edge[0] = constrained_edge[1] = constrained_edge[2] = false; delaunay_edge[0] = delaunay_edge[1] = delaunay_edge[2] = false; interior_ = false; } // Update neighbor pointers void Triangle::MarkNeighbor(Point* p1, Point* p2, Triangle* t) { if ((p1 == points_[2] && p2 == points_[1]) || (p1 == points_[1] && p2 == points_[2])) neighbors_[0] = t; else if ((p1 == points_[0] && p2 == points_[2]) || (p1 == points_[2] && p2 == points_[0])) neighbors_[1] = t; else if ((p1 == points_[0] && p2 == points_[1]) || (p1 == points_[1] && p2 == points_[0])) neighbors_[2] = t; else assert(0); } // Exhaustive search to update neighbor pointers void Triangle::MarkNeighbor(Triangle& t) { if (t.Contains(points_[1], points_[2])) { neighbors_[0] = &t; t.MarkNeighbor(points_[1], points_[2], this); } else if (t.Contains(points_[0], points_[2])) { neighbors_[1] = &t; t.MarkNeighbor(points_[0], points_[2], this); } else if (t.Contains(points_[0], points_[1])) { neighbors_[2] = &t; t.MarkNeighbor(points_[0], points_[1], this); } } /** * Clears all references to all other triangles and points */ void Triangle::Clear() { Triangle *t; for( int i=0; i<3; i++ ) { t = neighbors_[i]; if( t != NULL ) { t->ClearNeighbor( this ); } } ClearNeighbors(); points_[0]=points_[1]=points_[2] = NULL; } void Triangle::ClearNeighbor(const Triangle *triangle ) { if( neighbors_[0] == triangle ) { neighbors_[0] = NULL; } else if( neighbors_[1] == triangle ) { neighbors_[1] = NULL; } else { neighbors_[2] = NULL; } } void Triangle::ClearNeighbors() { neighbors_[0] = NULL; neighbors_[1] = NULL; neighbors_[2] = NULL; } void Triangle::ClearDelunayEdges() { delaunay_edge[0] = delaunay_edge[1] = delaunay_edge[2] = false; } Point* Triangle::OppositePoint(Triangle& t, const Point& p) { Point *cw = t.PointCW(p); return PointCW(*cw); } // Legalized triangle by rotating clockwise around point(0) void Triangle::Legalize(Point& point) { points_[1] = points_[0]; points_[0] = points_[2]; points_[2] = &point; } // Legalize triagnle by rotating clockwise around oPoint void Triangle::Legalize(Point& opoint, Point& npoint) { if (&opoint == points_[0]) { points_[1] = points_[0]; points_[0] = points_[2]; points_[2] = &npoint; } else if (&opoint == points_[1]) { points_[2] = points_[1]; points_[1] = points_[0]; points_[0] = &npoint; } else if (&opoint == points_[2]) { points_[0] = points_[2]; points_[2] = points_[1]; points_[1] = &npoint; } else { assert(0); } } int Triangle::Index(const Point* p) const { if (p == points_[0]) { return 0; } else if (p == points_[1]) { return 1; } else if (p == points_[2]) { return 2; } assert(0); return -1; } int Triangle::EdgeIndex(const Point* p1, const Point* p2) const { if (points_[0] == p1) { if (points_[1] == p2) { return 2; } else if (points_[2] == p2) { return 1; } } else if (points_[1] == p1) { if (points_[2] == p2) { return 0; } else if (points_[0] == p2) { return 2; } } else if (points_[2] == p1) { if (points_[0] == p2) { return 1; } else if (points_[1] == p2) { return 0; } } return -1; } void Triangle::MarkConstrainedEdge(int index) { constrained_edge[index] = true; } void Triangle::MarkConstrainedEdge(Edge& edge) { MarkConstrainedEdge(edge.p, edge.q); } // Mark edge as constrained void Triangle::MarkConstrainedEdge(Point* p, Point* q) { if ((q == points_[0] && p == points_[1]) || (q == points_[1] && p == points_[0])) { constrained_edge[2] = true; } else if ((q == points_[0] && p == points_[2]) || (q == points_[2] && p == points_[0])) { constrained_edge[1] = true; } else if ((q == points_[1] && p == points_[2]) || (q == points_[2] && p == points_[1])) { constrained_edge[0] = true; } } // The point counter-clockwise to given point Point* Triangle::PointCW(const Point& point) { if (&point == points_[0]) { return points_[2]; } else if (&point == points_[1]) { return points_[0]; } else if (&point == points_[2]) { return points_[1]; } assert(0); return NULL; } // The point counter-clockwise to given point Point* Triangle::PointCCW(const Point& point) { if (&point == points_[0]) { return points_[1]; } else if (&point == points_[1]) { return points_[2]; } else if (&point == points_[2]) { return points_[0]; } assert(0); return NULL; } // The neighbor clockwise to given point Triangle* Triangle::NeighborCW(const Point& point) { if (&point == points_[0]) { return neighbors_[1]; } else if (&point == points_[1]) { return neighbors_[2]; } return neighbors_[0]; } // The neighbor counter-clockwise to given point Triangle* Triangle::NeighborCCW(const Point& point) { if (&point == points_[0]) { return neighbors_[2]; } else if (&point == points_[1]) { return neighbors_[0]; } return neighbors_[1]; } bool Triangle::GetConstrainedEdgeCCW(const Point& p) const { if (&p == points_[0]) { return constrained_edge[2]; } else if (&p == points_[1]) { return constrained_edge[0]; } return constrained_edge[1]; } bool Triangle::GetConstrainedEdgeCW(const Point& p) const { if (&p == points_[0]) { return constrained_edge[1]; } else if (&p == points_[1]) { return constrained_edge[2]; } return constrained_edge[0]; } void Triangle::SetConstrainedEdgeCCW(const Point& p, bool ce) { if (&p == points_[0]) { constrained_edge[2] = ce; } else if (&p == points_[1]) { constrained_edge[0] = ce; } else { constrained_edge[1] = ce; } } void Triangle::SetConstrainedEdgeCW(const Point& p, bool ce) { if (&p == points_[0]) { constrained_edge[1] = ce; } else if (&p == points_[1]) { constrained_edge[2] = ce; } else { constrained_edge[0] = ce; } } bool Triangle::GetDelunayEdgeCCW(const Point& p) const { if (&p == points_[0]) { return delaunay_edge[2]; } else if (&p == points_[1]) { return delaunay_edge[0]; } return delaunay_edge[1]; } bool Triangle::GetDelunayEdgeCW(const Point& p) const { if (&p == points_[0]) { return delaunay_edge[1]; } else if (&p == points_[1]) { return delaunay_edge[2]; } return delaunay_edge[0]; } void Triangle::SetDelunayEdgeCCW(const Point& p, bool e) { if (&p == points_[0]) { delaunay_edge[2] = e; } else if (&p == points_[1]) { delaunay_edge[0] = e; } else { delaunay_edge[1] = e; } } void Triangle::SetDelunayEdgeCW(const Point& p, bool e) { if (&p == points_[0]) { delaunay_edge[1] = e; } else if (&p == points_[1]) { delaunay_edge[2] = e; } else { delaunay_edge[0] = e; } } // The neighbor across to given point Triangle& Triangle::NeighborAcross(const Point& opoint) { if (&opoint == points_[0]) { return *neighbors_[0]; } else if (&opoint == points_[1]) { return *neighbors_[1]; } return *neighbors_[2]; } void Triangle::DebugPrint() { using namespace std; cout << points_[0]->x << "," << points_[0]->y << " "; cout << points_[1]->x << "," << points_[1]->y << " "; cout << points_[2]->x << "," << points_[2]->y << endl; } }��������Slic3r-version_1.39.1/xs/src/poly2tri/common/shapes.h�����������������������������������������������0000664�0000000�0000000�00000016722�13243544447�0022464�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // Include guard #ifndef SHAPES_H #define SHAPES_H #include #include #include #include namespace p2t { struct Edge; struct Point { double x, y; /// Default constructor does nothing (for performance). Point() { x = 0.0; y = 0.0; } /// The edges this point constitutes an upper ending point std::vector edge_list; /// Construct using coordinates. Point(double x, double y) : x(x), y(y) {} /// Set this point to all zeros. void set_zero() { x = 0.0; y = 0.0; } /// Set this point to some specified coordinates. void set(double x_, double y_) { x = x_; y = y_; } /// Negate this point. Point operator -() const { Point v; v.set(-x, -y); return v; } /// Add a point to this point. void operator +=(const Point& v) { x += v.x; y += v.y; } /// Subtract a point from this point. void operator -=(const Point& v) { x -= v.x; y -= v.y; } /// Multiply this point by a scalar. void operator *=(double a) { x *= a; y *= a; } /// Get the length of this point (the norm). double Length() const { return sqrt(x * x + y * y); } /// Convert this point into a unit point. Returns the Length. double Normalize() { const double len = Length(); x /= len; y /= len; return len; } }; // Represents a simple polygon's edge struct Edge { Point* p, *q; /// Constructor Edge(Point& p1, Point& p2) : p(&p1), q(&p2) { if (p1.y > p2.y) { q = &p1; p = &p2; } else if (p1.y == p2.y) { if (p1.x > p2.x) { q = &p1; p = &p2; } else if (p1.x == p2.x) { // Repeat points assert(false); } } q->edge_list.push_back(this); } }; // Triangle-based data structures are know to have better performance than quad-edge structures // See: J. Shewchuk, "Triangle: Engineering a 2D Quality Mesh Generator and Delaunay Triangulator" // "Triangulations in CGAL" class Triangle { public: /// Constructor Triangle(Point& a, Point& b, Point& c); /// Flags to determine if an edge is a Constrained edge bool constrained_edge[3]; /// Flags to determine if an edge is a Delauney edge bool delaunay_edge[3]; Point* GetPoint(int index); Point* PointCW(const Point& point); Point* PointCCW(const Point& point); Point* OppositePoint(Triangle& t, const Point& p); Triangle* GetNeighbor(int index); void MarkNeighbor(Point* p1, Point* p2, Triangle* t); void MarkNeighbor(Triangle& t); void MarkConstrainedEdge(int index); void MarkConstrainedEdge(Edge& edge); void MarkConstrainedEdge(Point* p, Point* q); int Index(const Point* p) const; int EdgeIndex(const Point* p1, const Point* p2) const; Triangle* NeighborCW(const Point& point); Triangle* NeighborCCW(const Point& point); bool GetConstrainedEdgeCCW(const Point& p) const; bool GetConstrainedEdgeCW(const Point& p) const; void SetConstrainedEdgeCCW(const Point& p, bool ce); void SetConstrainedEdgeCW(const Point& p, bool ce); bool GetDelunayEdgeCCW(const Point& p) const; bool GetDelunayEdgeCW(const Point& p) const; void SetDelunayEdgeCCW(const Point& p, bool e); void SetDelunayEdgeCW(const Point& p, bool e); bool Contains(const Point* p) const; bool Contains(const Edge& e) const; bool Contains(const Point* p, const Point* q) const; void Legalize(Point& point); void Legalize(Point& opoint, Point& npoint); /** * Clears all references to all other triangles and points */ void Clear(); void ClearNeighbor(const Triangle *triangle); void ClearNeighbors(); void ClearDelunayEdges(); inline bool IsInterior() const; inline void IsInterior(bool b); Triangle& NeighborAcross(const Point& opoint); void DebugPrint(); private: /// Triangle points Point* points_[3]; /// Neighbor list Triangle* neighbors_[3]; /// Has this triangle been marked as an interior triangle? bool interior_; }; inline bool cmp(const Point* a, const Point* b) { if (a->y < b->y) { return true; } else if (a->y == b->y) { // Make sure q is point with greater x value if (a->x < b->x) { return true; } } return false; } /// Add two points_ component-wise. inline Point operator +(const Point& a, const Point& b) { return Point(a.x + b.x, a.y + b.y); } /// Subtract two points_ component-wise. inline Point operator -(const Point& a, const Point& b) { return Point(a.x - b.x, a.y - b.y); } /// Multiply point by scalar inline Point operator *(double s, const Point& a) { return Point(s * a.x, s * a.y); } inline bool operator ==(const Point& a, const Point& b) { return a.x == b.x && a.y == b.y; } inline bool operator !=(const Point& a, const Point& b) { return !(a.x == b.x) && !(a.y == b.y); } /// Peform the dot product on two vectors. inline double Dot(const Point& a, const Point& b) { return a.x * b.x + a.y * b.y; } /// Perform the cross product on two vectors. In 2D this produces a scalar. inline double Cross(const Point& a, const Point& b) { return a.x * b.y - a.y * b.x; } /// Perform the cross product on a point and a scalar. In 2D this produces /// a point. inline Point Cross(const Point& a, double s) { return Point(s * a.y, -s * a.x); } /// Perform the cross product on a scalar and a point. In 2D this produces /// a point. inline Point Cross(double s, const Point& a) { return Point(-s * a.y, s * a.x); } inline Point* Triangle::GetPoint(int index) { return points_[index]; } inline Triangle* Triangle::GetNeighbor(int index) { return neighbors_[index]; } inline bool Triangle::Contains(const Point* p) const { return p == points_[0] || p == points_[1] || p == points_[2]; } inline bool Triangle::Contains(const Edge& e) const { return Contains(e.p) && Contains(e.q); } inline bool Triangle::Contains(const Point* p, const Point* q) const { return Contains(p) && Contains(q); } inline bool Triangle::IsInterior() const { return interior_; } inline void Triangle::IsInterior(bool b) { interior_ = b; } } #endif����������������������������������������������Slic3r-version_1.39.1/xs/src/poly2tri/common/utils.h������������������������������������������������0000664�0000000�0000000�00000006637�13243544447�0022345�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTILS_H #define UTILS_H // Otherwise #defines like M_PI are undeclared under Visual Studio #define _USE_MATH_DEFINES #include #include namespace p2t { const double PI_3div4 = 3 * M_PI / 4; const double PI_div2 = 1.57079632679489661923; const double EPSILON = 1e-12; enum Orientation { CW, CCW, COLLINEAR }; /** * Forumla to calculate signed area
* Positive if CCW
* Negative if CW
* 0 if collinear
*
 * A[P1,P2,P3]  =  (x1*y2 - y1*x2) + (x2*y3 - y2*x3) + (x3*y1 - y3*x1)
 *              =  (x1-x3)*(y2-y3) - (y1-y3)*(x2-x3)
 * 
*/ Orientation Orient2d(const Point& pa, const Point& pb, const Point& pc) { double detleft = (pa.x - pc.x) * (pb.y - pc.y); double detright = (pa.y - pc.y) * (pb.x - pc.x); double val = detleft - detright; if (val > -EPSILON && val < EPSILON) { return COLLINEAR; } else if (val > 0) { return CCW; } return CW; } /* bool InScanArea(Point& pa, Point& pb, Point& pc, Point& pd) { double pdx = pd.x; double pdy = pd.y; double adx = pa.x - pdx; double ady = pa.y - pdy; double bdx = pb.x - pdx; double bdy = pb.y - pdy; double adxbdy = adx * bdy; double bdxady = bdx * ady; double oabd = adxbdy - bdxady; if (oabd <= EPSILON) { return false; } double cdx = pc.x - pdx; double cdy = pc.y - pdy; double cdxady = cdx * ady; double adxcdy = adx * cdy; double ocad = cdxady - adxcdy; if (ocad <= EPSILON) { return false; } return true; } */ bool InScanArea(const Point& pa, const Point& pb, const Point& pc, const Point& pd) { double oadb = (pa.x - pb.x)*(pd.y - pb.y) - (pd.x - pb.x)*(pa.y - pb.y); if (oadb >= -EPSILON) { return false; } double oadc = (pa.x - pc.x)*(pd.y - pc.y) - (pd.x - pc.x)*(pa.y - pc.y); if (oadc <= EPSILON) { return false; } return true; } } #endifSlic3r-version_1.39.1/xs/src/poly2tri/poly2tri.h000066400000000000000000000032661324354444700214740ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef POLY2TRI_H #define POLY2TRI_H #include "common/shapes.h" #include "sweep/cdt.h" #endifSlic3r-version_1.39.1/xs/src/poly2tri/sweep/000077500000000000000000000000001324354444700206535ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/poly2tri/sweep/advancing_front.cc000066400000000000000000000061361324354444700243320ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "advancing_front.h" namespace p2t { AdvancingFront::AdvancingFront(Node& head, Node& tail) { head_ = &head; tail_ = &tail; search_node_ = &head; } Node* AdvancingFront::LocateNode(double x) { Node* node = search_node_; if (x < node->value) { while ((node = node->prev) != NULL) { if (x >= node->value) { search_node_ = node; return node; } } } else { while ((node = node->next) != NULL) { if (x < node->value) { search_node_ = node->prev; return node->prev; } } } return NULL; } Node* AdvancingFront::FindSearchNode(double x) { (void)x; // suppress compiler warnings "unused parameter 'x'" // TODO: implement BST index return search_node_; } Node* AdvancingFront::LocatePoint(const Point* point) { const double px = point->x; Node* node = FindSearchNode(px); const double nx = node->point->x; if (px == nx) { if (point != node->point) { // We might have two nodes with same x value for a short time if (point == node->prev->point) { node = node->prev; } else if (point == node->next->point) { node = node->next; } else { assert(0); } } } else if (px < nx) { while ((node = node->prev) != NULL) { if (point == node->point) { break; } } } else { while ((node = node->next) != NULL) { if (point == node->point) break; } } if(node) search_node_ = node; return node; } AdvancingFront::~AdvancingFront() { } }Slic3r-version_1.39.1/xs/src/poly2tri/sweep/advancing_front.h000066400000000000000000000055411324354444700241730ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef ADVANCED_FRONT_H #define ADVANCED_FRONT_H #include "../common/shapes.h" namespace p2t { struct Node; // Advancing front node struct Node { Point* point; Triangle* triangle; Node* next; Node* prev; double value; Node(Point& p) : point(&p), triangle(NULL), next(NULL), prev(NULL), value(p.x) { } Node(Point& p, Triangle& t) : point(&p), triangle(&t), next(NULL), prev(NULL), value(p.x) { } }; // Advancing front class AdvancingFront { public: AdvancingFront(Node& head, Node& tail); // Destructor ~AdvancingFront(); Node* head(); void set_head(Node* node); Node* tail(); void set_tail(Node* node); Node* search(); void set_search(Node* node); /// Locate insertion point along advancing front Node* LocateNode(double x); Node* LocatePoint(const Point* point); private: Node* head_, *tail_, *search_node_; Node* FindSearchNode(double x); }; inline Node* AdvancingFront::head() { return head_; } inline void AdvancingFront::set_head(Node* node) { head_ = node; } inline Node* AdvancingFront::tail() { return tail_; } inline void AdvancingFront::set_tail(Node* node) { tail_ = node; } inline Node* AdvancingFront::search() { return search_node_; } inline void AdvancingFront::set_search(Node* node) { search_node_ = node; } } #endifSlic3r-version_1.39.1/xs/src/poly2tri/sweep/cdt.cc000066400000000000000000000043111324354444700217330ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "cdt.h" namespace p2t { CDT::CDT(const std::vector& polyline) { sweep_context_ = new SweepContext(polyline); sweep_ = new Sweep; } void CDT::AddHole(const std::vector& polyline) { sweep_context_->AddHole(polyline); } void CDT::AddPoint(Point* point) { sweep_context_->AddPoint(point); } void CDT::Triangulate() { sweep_->Triangulate(*sweep_context_); } std::vector CDT::GetTriangles() { return sweep_context_->GetTriangles(); } std::list CDT::GetMap() { return sweep_context_->GetMap(); } CDT::~CDT() { delete sweep_context_; delete sweep_; } }Slic3r-version_1.39.1/xs/src/poly2tri/sweep/cdt.h000066400000000000000000000050431324354444700216000ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef CDT_H #define CDT_H #include "advancing_front.h" #include "sweep_context.h" #include "sweep.h" /** * * @author Mason Green * */ namespace p2t { class CDT { public: /** * Constructor - add polyline with non repeating points * * @param polyline */ CDT(const std::vector& polyline); /** * Destructor - clean up memory */ ~CDT(); /** * Add a hole * * @param polyline */ void AddHole(const std::vector& polyline); /** * Add a steiner point * * @param point */ void AddPoint(Point* point); /** * Triangulate - do this AFTER you've added the polyline, holes, and Steiner points */ void Triangulate(); /** * Get CDT triangles */ std::vector GetTriangles(); /** * Get triangle map */ std::list GetMap(); private: /** * Internals */ SweepContext* sweep_context_; Sweep* sweep_; }; } #endifSlic3r-version_1.39.1/xs/src/poly2tri/sweep/sweep.cc000066400000000000000000000572201324354444700223130ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "sweep.h" #include "sweep_context.h" #include "advancing_front.h" #include "../common/utils.h" namespace p2t { // Triangulate simple polygon with holes void Sweep::Triangulate(SweepContext& tcx) { tcx.InitTriangulation(); tcx.CreateAdvancingFront(nodes_); // Sweep points; build mesh SweepPoints(tcx); // Clean up FinalizationPolygon(tcx); } void Sweep::SweepPoints(SweepContext& tcx) { for (size_t i = 1; i < tcx.point_count(); i++) { Point& point = *tcx.GetPoint(i); Node* node = &PointEvent(tcx, point); for (unsigned int i = 0; i < point.edge_list.size(); i++) { EdgeEvent(tcx, point.edge_list[i], node); } } } void Sweep::FinalizationPolygon(SweepContext& tcx) { // Get an Internal triangle to start with Triangle* t = tcx.front()->head()->next->triangle; Point* p = tcx.front()->head()->next->point; while (!t->GetConstrainedEdgeCW(*p)) { t = t->NeighborCCW(*p); } // Collect interior triangles constrained by edges tcx.MeshClean(*t); } Node& Sweep::PointEvent(SweepContext& tcx, Point& point) { Node& node = tcx.LocateNode(point); Node& new_node = NewFrontTriangle(tcx, point, node); // Only need to check +epsilon since point never have smaller // x value than node due to how we fetch nodes from the front if (point.x <= node.point->x + EPSILON) { Fill(tcx, node); } //tcx.AddNode(new_node); FillAdvancingFront(tcx, new_node); return new_node; } void Sweep::EdgeEvent(SweepContext& tcx, Edge* edge, Node* node) { tcx.edge_event.constrained_edge = edge; tcx.edge_event.right = (edge->p->x > edge->q->x); if (IsEdgeSideOfTriangle(*node->triangle, *edge->p, *edge->q)) { return; } // For now we will do all needed filling // TODO: integrate with flip process might give some better performance // but for now this avoid the issue with cases that needs both flips and fills FillEdgeEvent(tcx, edge, node); EdgeEvent(tcx, *edge->p, *edge->q, node->triangle, *edge->q); } void Sweep::EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangle, Point& point) { if (IsEdgeSideOfTriangle(*triangle, ep, eq)) { return; } Point* p1 = triangle->PointCCW(point); Orientation o1 = Orient2d(eq, *p1, ep); if (o1 == COLLINEAR) { if( triangle->Contains(&eq, p1)) { triangle->MarkConstrainedEdge(&eq, p1 ); // We are modifying the constraint maybe it would be better to // not change the given constraint and just keep a variable for the new constraint tcx.edge_event.constrained_edge->q = p1; triangle = &triangle->NeighborAcross(point); EdgeEvent( tcx, ep, *p1, triangle, *p1 ); } else { std::runtime_error("EdgeEvent - collinear points not supported"); assert(0); } return; } Point* p2 = triangle->PointCW(point); Orientation o2 = Orient2d(eq, *p2, ep); if (o2 == COLLINEAR) { if( triangle->Contains(&eq, p2)) { triangle->MarkConstrainedEdge(&eq, p2 ); // We are modifying the constraint maybe it would be better to // not change the given constraint and just keep a variable for the new constraint tcx.edge_event.constrained_edge->q = p2; triangle = &triangle->NeighborAcross(point); EdgeEvent( tcx, ep, *p2, triangle, *p2 ); } else { std::runtime_error("EdgeEvent - collinear points not supported"); assert(0); } return; } if (o1 == o2) { // Need to decide if we are rotating CW or CCW to get to a triangle // that will cross edge if (o1 == CW) { triangle = triangle->NeighborCCW(point); } else{ triangle = triangle->NeighborCW(point); } EdgeEvent(tcx, ep, eq, triangle, point); } else { // This triangle crosses constraint so lets flippin start! FlipEdgeEvent(tcx, ep, eq, triangle, point); } } bool Sweep::IsEdgeSideOfTriangle(Triangle& triangle, Point& ep, Point& eq) { const int index = triangle.EdgeIndex(&ep, &eq); if (index != -1) { triangle.MarkConstrainedEdge(index); Triangle* t = triangle.GetNeighbor(index); if (t) { t->MarkConstrainedEdge(&ep, &eq); } return true; } return false; } Node& Sweep::NewFrontTriangle(SweepContext& tcx, Point& point, Node& node) { Triangle* triangle = new Triangle(point, *node.point, *node.next->point); triangle->MarkNeighbor(*node.triangle); tcx.AddToMap(triangle); Node* new_node = new Node(point); nodes_.push_back(new_node); new_node->next = node.next; new_node->prev = &node; node.next->prev = new_node; node.next = new_node; if (!Legalize(tcx, *triangle)) { tcx.MapTriangleToNodes(*triangle); } return *new_node; } void Sweep::Fill(SweepContext& tcx, Node& node) { Triangle* triangle = new Triangle(*node.prev->point, *node.point, *node.next->point); // TODO: should copy the constrained_edge value from neighbor triangles // for now constrained_edge values are copied during the legalize triangle->MarkNeighbor(*node.prev->triangle); triangle->MarkNeighbor(*node.triangle); tcx.AddToMap(triangle); // Update the advancing front node.prev->next = node.next; node.next->prev = node.prev; // If it was legalized the triangle has already been mapped if (!Legalize(tcx, *triangle)) { tcx.MapTriangleToNodes(*triangle); } } void Sweep::FillAdvancingFront(SweepContext& tcx, Node& n) { // Fill right holes Node* node = n.next; while (node->next) { // if HoleAngle exceeds 90 degrees then break. if (LargeHole_DontFill(node)) break; Fill(tcx, *node); node = node->next; } // Fill left holes node = n.prev; while (node->prev) { // if HoleAngle exceeds 90 degrees then break. if (LargeHole_DontFill(node)) break; Fill(tcx, *node); node = node->prev; } // Fill right basins if (n.next && n.next->next) { const double angle = BasinAngle(n); if (angle < PI_3div4) { FillBasin(tcx, n); } } } // True if HoleAngle exceeds 90 degrees. bool Sweep::LargeHole_DontFill(const Node* node) const { const Node* nextNode = node->next; const Node* prevNode = node->prev; if (!AngleExceeds90Degrees(node->point, nextNode->point, prevNode->point)) return false; // Check additional points on front. const Node* next2Node = nextNode->next; // "..Plus.." because only want angles on same side as point being added. if ((next2Node != NULL) && !AngleExceedsPlus90DegreesOrIsNegative(node->point, next2Node->point, prevNode->point)) return false; const Node* prev2Node = prevNode->prev; // "..Plus.." because only want angles on same side as point being added. if ((prev2Node != NULL) && !AngleExceedsPlus90DegreesOrIsNegative(node->point, nextNode->point, prev2Node->point)) return false; return true; } bool Sweep::AngleExceeds90Degrees(const Point* origin, const Point* pa, const Point* pb) const { const double angle = Angle(origin, pa, pb); return ((angle > PI_div2) || (angle < -PI_div2)); } bool Sweep::AngleExceedsPlus90DegreesOrIsNegative(const Point* origin, const Point* pa, const Point* pb) const { const double angle = Angle(origin, pa, pb); return (angle > PI_div2) || (angle < 0); } double Sweep::Angle(const Point* origin, const Point* pa, const Point* pb) const { /* Complex plane * ab = cosA +i*sinA * ab = (ax + ay*i)(bx + by*i) = (ax*bx + ay*by) + i(ax*by-ay*bx) * atan2(y,x) computes the principal value of the argument function * applied to the complex number x+iy * Where x = ax*bx + ay*by * y = ax*by - ay*bx */ const double px = origin->x; const double py = origin->y; const double ax = pa->x- px; const double ay = pa->y - py; const double bx = pb->x - px; const double by = pb->y - py; const double x = ax * by - ay * bx; const double y = ax * bx + ay * by; return atan2(x, y); } double Sweep::BasinAngle(const Node& node) const { const double ax = node.point->x - node.next->next->point->x; const double ay = node.point->y - node.next->next->point->y; return atan2(ay, ax); } double Sweep::HoleAngle(const Node& node) const { /* Complex plane * ab = cosA +i*sinA * ab = (ax + ay*i)(bx + by*i) = (ax*bx + ay*by) + i(ax*by-ay*bx) * atan2(y,x) computes the principal value of the argument function * applied to the complex number x+iy * Where x = ax*bx + ay*by * y = ax*by - ay*bx */ const double ax = node.next->point->x - node.point->x; const double ay = node.next->point->y - node.point->y; const double bx = node.prev->point->x - node.point->x; const double by = node.prev->point->y - node.point->y; return atan2(ax * by - ay * bx, ax * bx + ay * by); } bool Sweep::Legalize(SweepContext& tcx, Triangle& t) { // To legalize a triangle we start by finding if any of the three edges // violate the Delaunay condition for (int i = 0; i < 3; i++) { if (t.delaunay_edge[i]) continue; Triangle* ot = t.GetNeighbor(i); if (ot) { Point* p = t.GetPoint(i); Point* op = ot->OppositePoint(t, *p); int oi = ot->Index(op); // If this is a Constrained Edge or a Delaunay Edge(only during recursive legalization) // then we should not try to legalize if (ot->constrained_edge[oi] || ot->delaunay_edge[oi]) { t.constrained_edge[i] = ot->constrained_edge[oi]; continue; } bool inside = Incircle(*p, *t.PointCCW(*p), *t.PointCW(*p), *op); if (inside) { // Lets mark this shared edge as Delaunay t.delaunay_edge[i] = true; ot->delaunay_edge[oi] = true; // Lets rotate shared edge one vertex CW to legalize it RotateTrianglePair(t, *p, *ot, *op); // We now got one valid Delaunay Edge shared by two triangles // This gives us 4 new edges to check for Delaunay // Make sure that triangle to node mapping is done only one time for a specific triangle bool not_legalized = !Legalize(tcx, t); if (not_legalized) { tcx.MapTriangleToNodes(t); } not_legalized = !Legalize(tcx, *ot); if (not_legalized) tcx.MapTriangleToNodes(*ot); // Reset the Delaunay edges, since they only are valid Delaunay edges // until we add a new triangle or point. // XXX: need to think about this. Can these edges be tried after we // return to previous recursive level? t.delaunay_edge[i] = false; ot->delaunay_edge[oi] = false; // If triangle have been legalized no need to check the other edges since // the recursive legalization will handles those so we can end here. return true; } } } return false; } bool Sweep::Incircle(const Point& pa, const Point& pb, const Point& pc, const Point& pd) const { const double adx = pa.x - pd.x; const double ady = pa.y - pd.y; const double bdx = pb.x - pd.x; const double bdy = pb.y - pd.y; const double adxbdy = adx * bdy; const double bdxady = bdx * ady; const double oabd = adxbdy - bdxady; if (oabd <= 0) return false; const double cdx = pc.x - pd.x; const double cdy = pc.y - pd.y; const double cdxady = cdx * ady; const double adxcdy = adx * cdy; const double ocad = cdxady - adxcdy; if (ocad <= 0) return false; const double bdxcdy = bdx * cdy; const double cdxbdy = cdx * bdy; const double alift = adx * adx + ady * ady; const double blift = bdx * bdx + bdy * bdy; const double clift = cdx * cdx + cdy * cdy; const double det = alift * (bdxcdy - cdxbdy) + blift * ocad + clift * oabd; return det > 0; } void Sweep::RotateTrianglePair(Triangle& t, Point& p, Triangle& ot, Point& op) const { Triangle* n1, *n2, *n3, *n4; n1 = t.NeighborCCW(p); n2 = t.NeighborCW(p); n3 = ot.NeighborCCW(op); n4 = ot.NeighborCW(op); bool ce1, ce2, ce3, ce4; ce1 = t.GetConstrainedEdgeCCW(p); ce2 = t.GetConstrainedEdgeCW(p); ce3 = ot.GetConstrainedEdgeCCW(op); ce4 = ot.GetConstrainedEdgeCW(op); bool de1, de2, de3, de4; de1 = t.GetDelunayEdgeCCW(p); de2 = t.GetDelunayEdgeCW(p); de3 = ot.GetDelunayEdgeCCW(op); de4 = ot.GetDelunayEdgeCW(op); t.Legalize(p, op); ot.Legalize(op, p); // Remap delaunay_edge ot.SetDelunayEdgeCCW(p, de1); t.SetDelunayEdgeCW(p, de2); t.SetDelunayEdgeCCW(op, de3); ot.SetDelunayEdgeCW(op, de4); // Remap constrained_edge ot.SetConstrainedEdgeCCW(p, ce1); t.SetConstrainedEdgeCW(p, ce2); t.SetConstrainedEdgeCCW(op, ce3); ot.SetConstrainedEdgeCW(op, ce4); // Remap neighbors // XXX: might optimize the markNeighbor by keeping track of // what side should be assigned to what neighbor after the // rotation. Now mark neighbor does lots of testing to find // the right side. t.ClearNeighbors(); ot.ClearNeighbors(); if (n1) ot.MarkNeighbor(*n1); if (n2) t.MarkNeighbor(*n2); if (n3) t.MarkNeighbor(*n3); if (n4) ot.MarkNeighbor(*n4); t.MarkNeighbor(ot); } void Sweep::FillBasin(SweepContext& tcx, Node& node) { if (Orient2d(*node.point, *node.next->point, *node.next->next->point) == CCW) { tcx.basin.left_node = node.next->next; } else { tcx.basin.left_node = node.next; } // Find the bottom and right node tcx.basin.bottom_node = tcx.basin.left_node; while (tcx.basin.bottom_node->next && tcx.basin.bottom_node->point->y >= tcx.basin.bottom_node->next->point->y) { tcx.basin.bottom_node = tcx.basin.bottom_node->next; } if (tcx.basin.bottom_node == tcx.basin.left_node) { // No valid basin return; } tcx.basin.right_node = tcx.basin.bottom_node; while (tcx.basin.right_node->next && tcx.basin.right_node->point->y < tcx.basin.right_node->next->point->y) { tcx.basin.right_node = tcx.basin.right_node->next; } if (tcx.basin.right_node == tcx.basin.bottom_node) { // No valid basins return; } tcx.basin.width = tcx.basin.right_node->point->x - tcx.basin.left_node->point->x; tcx.basin.left_highest = tcx.basin.left_node->point->y > tcx.basin.right_node->point->y; FillBasinReq(tcx, tcx.basin.bottom_node); } void Sweep::FillBasinReq(SweepContext& tcx, Node* node) { // if shallow stop filling if (IsShallow(tcx, *node)) { return; } Fill(tcx, *node); if (node->prev == tcx.basin.left_node && node->next == tcx.basin.right_node) { return; } else if (node->prev == tcx.basin.left_node) { Orientation o = Orient2d(*node->point, *node->next->point, *node->next->next->point); if (o == CW) { return; } node = node->next; } else if (node->next == tcx.basin.right_node) { Orientation o = Orient2d(*node->point, *node->prev->point, *node->prev->prev->point); if (o == CCW) { return; } node = node->prev; } else { // Continue with the neighbor node with lowest Y value if (node->prev->point->y < node->next->point->y) { node = node->prev; } else { node = node->next; } } FillBasinReq(tcx, node); } bool Sweep::IsShallow(SweepContext& tcx, Node& node) { double height; if (tcx.basin.left_highest) { height = tcx.basin.left_node->point->y - node.point->y; } else { height = tcx.basin.right_node->point->y - node.point->y; } // if shallow stop filling if (tcx.basin.width > height) { return true; } return false; } void Sweep::FillEdgeEvent(SweepContext& tcx, Edge* edge, Node* node) { if (tcx.edge_event.right) { FillRightAboveEdgeEvent(tcx, edge, node); } else { FillLeftAboveEdgeEvent(tcx, edge, node); } } void Sweep::FillRightAboveEdgeEvent(SweepContext& tcx, Edge* edge, Node* node) { while (node->next->point->x < edge->p->x) { // Check if next node is below the edge if (Orient2d(*edge->q, *node->next->point, *edge->p) == CCW) { FillRightBelowEdgeEvent(tcx, edge, *node); } else { node = node->next; } } } void Sweep::FillRightBelowEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) { if (node.point->x < edge->p->x) { if (Orient2d(*node.point, *node.next->point, *node.next->next->point) == CCW) { // Concave FillRightConcaveEdgeEvent(tcx, edge, node); } else{ // Convex FillRightConvexEdgeEvent(tcx, edge, node); // Retry this one FillRightBelowEdgeEvent(tcx, edge, node); } } } void Sweep::FillRightConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) { Fill(tcx, *node.next); if (node.next->point != edge->p) { // Next above or below edge? if (Orient2d(*edge->q, *node.next->point, *edge->p) == CCW) { // Below if (Orient2d(*node.point, *node.next->point, *node.next->next->point) == CCW) { // Next is concave FillRightConcaveEdgeEvent(tcx, edge, node); } else { // Next is convex } } } } void Sweep::FillRightConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) { // Next concave or convex? if (Orient2d(*node.next->point, *node.next->next->point, *node.next->next->next->point) == CCW) { // Concave FillRightConcaveEdgeEvent(tcx, edge, *node.next); } else{ // Convex // Next above or below edge? if (Orient2d(*edge->q, *node.next->next->point, *edge->p) == CCW) { // Below FillRightConvexEdgeEvent(tcx, edge, *node.next); } else{ // Above } } } void Sweep::FillLeftAboveEdgeEvent(SweepContext& tcx, Edge* edge, Node* node) { while (node->prev->point->x > edge->p->x) { // Check if next node is below the edge if (Orient2d(*edge->q, *node->prev->point, *edge->p) == CW) { FillLeftBelowEdgeEvent(tcx, edge, *node); } else { node = node->prev; } } } void Sweep::FillLeftBelowEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) { if (node.point->x > edge->p->x) { if (Orient2d(*node.point, *node.prev->point, *node.prev->prev->point) == CW) { // Concave FillLeftConcaveEdgeEvent(tcx, edge, node); } else { // Convex FillLeftConvexEdgeEvent(tcx, edge, node); // Retry this one FillLeftBelowEdgeEvent(tcx, edge, node); } } } void Sweep::FillLeftConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) { // Next concave or convex? if (Orient2d(*node.prev->point, *node.prev->prev->point, *node.prev->prev->prev->point) == CW) { // Concave FillLeftConcaveEdgeEvent(tcx, edge, *node.prev); } else{ // Convex // Next above or below edge? if (Orient2d(*edge->q, *node.prev->prev->point, *edge->p) == CW) { // Below FillLeftConvexEdgeEvent(tcx, edge, *node.prev); } else{ // Above } } } void Sweep::FillLeftConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node) { Fill(tcx, *node.prev); if (node.prev->point != edge->p) { // Next above or below edge? if (Orient2d(*edge->q, *node.prev->point, *edge->p) == CW) { // Below if (Orient2d(*node.point, *node.prev->point, *node.prev->prev->point) == CW) { // Next is concave FillLeftConcaveEdgeEvent(tcx, edge, node); } else{ // Next is convex } } } } void Sweep::FlipEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* t, Point& p) { Triangle& ot = t->NeighborAcross(p); Point& op = *ot.OppositePoint(*t, p); if (&ot == NULL) { // If we want to integrate the fillEdgeEvent do it here // With current implementation we should never get here //throw new RuntimeException( "[BUG:FIXME] FLIP failed due to missing triangle"); assert(0); } if (InScanArea(p, *t->PointCCW(p), *t->PointCW(p), op)) { // Lets rotate shared edge one vertex CW RotateTrianglePair(*t, p, ot, op); tcx.MapTriangleToNodes(*t); tcx.MapTriangleToNodes(ot); if (p == eq && op == ep) { if (eq == *tcx.edge_event.constrained_edge->q && ep == *tcx.edge_event.constrained_edge->p) { t->MarkConstrainedEdge(&ep, &eq); ot.MarkConstrainedEdge(&ep, &eq); Legalize(tcx, *t); Legalize(tcx, ot); } else { // XXX: I think one of the triangles should be legalized here? } } else { Orientation o = Orient2d(eq, op, ep); t = &NextFlipTriangle(tcx, (int)o, *t, ot, p, op); FlipEdgeEvent(tcx, ep, eq, t, p); } } else { Point& newP = NextFlipPoint(ep, eq, ot, op); FlipScanEdgeEvent(tcx, ep, eq, *t, ot, newP); EdgeEvent(tcx, ep, eq, t, p); } } Triangle& Sweep::NextFlipTriangle(SweepContext& tcx, int o, Triangle& t, Triangle& ot, Point& p, Point& op) { if (o == CCW) { // ot is not crossing edge after flip int edge_index = ot.EdgeIndex(&p, &op); ot.delaunay_edge[edge_index] = true; Legalize(tcx, ot); ot.ClearDelunayEdges(); return t; } // t is not crossing edge after flip int edge_index = t.EdgeIndex(&p, &op); t.delaunay_edge[edge_index] = true; Legalize(tcx, t); t.ClearDelunayEdges(); return ot; } Point& Sweep::NextFlipPoint(Point& ep, Point& eq, Triangle& ot, Point& op) { Orientation o2d = Orient2d(eq, op, ep); if (o2d == CW) { // Right return *ot.PointCCW(op); } else if (o2d == CCW) { // Left return *ot.PointCW(op); } throw std::runtime_error("[Unsupported] Opposing point on constrained edge"); } void Sweep::FlipScanEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle& flip_triangle, Triangle& t, Point& p) { Triangle& ot = t.NeighborAcross(p); Point& op = *ot.OppositePoint(t, p); if (&t.NeighborAcross(p) == NULL) { // If we want to integrate the fillEdgeEvent do it here // With current implementation we should never get here //throw new RuntimeException( "[BUG:FIXME] FLIP failed due to missing triangle"); assert(0); } if (InScanArea(eq, *flip_triangle.PointCCW(eq), *flip_triangle.PointCW(eq), op)) { // flip with new edge op->eq FlipEdgeEvent(tcx, eq, op, &ot, op); // TODO: Actually I just figured out that it should be possible to // improve this by getting the next ot and op before the the above // flip and continue the flipScanEdgeEvent here // set new ot and op here and loop back to inScanArea test // also need to set a new flip_triangle first // Turns out at first glance that this is somewhat complicated // so it will have to wait. } else{ Point& newP = NextFlipPoint(ep, eq, ot, op); FlipScanEdgeEvent(tcx, ep, eq, flip_triangle, ot, newP); } } Sweep::~Sweep() { // Clean up memory for(size_t i = 0; i < nodes_.size(); i++) { delete nodes_[i]; } } } Slic3r-version_1.39.1/xs/src/poly2tri/sweep/sweep.h000066400000000000000000000210611324354444700221470ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * Sweep-line, Constrained Delauney Triangulation (CDT) See: Domiter, V. and * Zalik, B.(2008)'Sweep-line algorithm for constrained Delaunay triangulation', * International Journal of Geographical Information Science * * "FlipScan" Constrained Edge Algorithm invented by Thomas ?hl?n, thahlen@gmail.com */ #ifndef SWEEP_H #define SWEEP_H #include namespace p2t { class SweepContext; struct Node; struct Point; struct Edge; class Triangle; class Sweep { public: /** * Triangulate * * @param tcx */ void Triangulate(SweepContext& tcx); /** * Destructor - clean up memory */ ~Sweep(); private: /** * Start sweeping the Y-sorted point set from bottom to top * * @param tcx */ void SweepPoints(SweepContext& tcx); /** * Find closes node to the left of the new point and * create a new triangle. If needed new holes and basins * will be filled to. * * @param tcx * @param point * @return */ Node& PointEvent(SweepContext& tcx, Point& point); /** * * * @param tcx * @param edge * @param node */ void EdgeEvent(SweepContext& tcx, Edge* edge, Node* node); void EdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* triangle, Point& point); /** * Creates a new front triangle and legalize it * * @param tcx * @param point * @param node * @return */ Node& NewFrontTriangle(SweepContext& tcx, Point& point, Node& node); /** * Adds a triangle to the advancing front to fill a hole. * @param tcx * @param node - middle node, that is the bottom of the hole */ void Fill(SweepContext& tcx, Node& node); /** * Returns true if triangle was legalized */ bool Legalize(SweepContext& tcx, Triangle& t); /** * Requirement:
* 1. a,b and c form a triangle.
* 2. a and d is know to be on opposite side of bc
*
   *                a
   *                +
   *               / \
   *              /   \
   *            b/     \c
   *            +-------+
   *           /    d    \
   *          /           \
   * 
* Fact: d has to be in area B to have a chance to be inside the circle formed by * a,b and c
* d is outside B if orient2d(a,b,d) or orient2d(c,a,d) is CW
* This preknowledge gives us a way to optimize the incircle test * @param a - triangle point, opposite d * @param b - triangle point * @param c - triangle point * @param d - point opposite a * @return true if d is inside circle, false if on circle edge */ bool Incircle(const Point& pa, const Point& pb, const Point& pc, const Point& pd) const; /** * Rotates a triangle pair one vertex CW *
   *       n2                    n2
   *  P +-----+             P +-----+
   *    | t  /|               |\  t |
   *    |   / |               | \   |
   *  n1|  /  |n3           n1|  \  |n3
   *    | /   |    after CW   |   \ |
   *    |/ oT |               | oT \|
   *    +-----+ oP            +-----+
   *       n4                    n4
   * 
*/ void RotateTrianglePair(Triangle& t, Point& p, Triangle& ot, Point& op) const; /** * Fills holes in the Advancing Front * * * @param tcx * @param n */ void FillAdvancingFront(SweepContext& tcx, Node& n); // Decision-making about when to Fill hole. // Contributed by ToolmakerSteve2 bool LargeHole_DontFill(const Node* node) const; bool AngleExceeds90Degrees(const Point* origin, const Point* pa, const Point* pb) const; bool AngleExceedsPlus90DegreesOrIsNegative(const Point* origin, const Point* pa, const Point* pb) const; double Angle(const Point* origin, const Point* pa, const Point* pb) const; /** * * @param node - middle node * @return the angle between 3 front nodes */ double HoleAngle(const Node& node) const; /** * The basin angle is decided against the horizontal line [1,0] */ double BasinAngle(const Node& node) const; /** * Fills a basin that has formed on the Advancing Front to the right * of given node.
* First we decide a left,bottom and right node that forms the * boundaries of the basin. Then we do a reqursive fill. * * @param tcx * @param node - starting node, this or next node will be left node */ void FillBasin(SweepContext& tcx, Node& node); /** * Recursive algorithm to fill a Basin with triangles * * @param tcx * @param node - bottom_node * @param cnt - counter used to alternate on even and odd numbers */ void FillBasinReq(SweepContext& tcx, Node* node); bool IsShallow(SweepContext& tcx, Node& node); bool IsEdgeSideOfTriangle(Triangle& triangle, Point& ep, Point& eq); void FillEdgeEvent(SweepContext& tcx, Edge* edge, Node* node); void FillRightAboveEdgeEvent(SweepContext& tcx, Edge* edge, Node* node); void FillRightBelowEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); void FillRightConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); void FillRightConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); void FillLeftAboveEdgeEvent(SweepContext& tcx, Edge* edge, Node* node); void FillLeftBelowEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); void FillLeftConcaveEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); void FillLeftConvexEdgeEvent(SweepContext& tcx, Edge* edge, Node& node); void FlipEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle* t, Point& p); /** * After a flip we have two triangles and know that only one will still be * intersecting the edge. So decide which to contiune with and legalize the other * * @param tcx * @param o - should be the result of an orient2d( eq, op, ep ) * @param t - triangle 1 * @param ot - triangle 2 * @param p - a point shared by both triangles * @param op - another point shared by both triangles * @return returns the triangle still intersecting the edge */ Triangle& NextFlipTriangle(SweepContext& tcx, int o, Triangle& t, Triangle& ot, Point& p, Point& op); /** * When we need to traverse from one triangle to the next we need * the point in current triangle that is the opposite point to the next * triangle. * * @param ep * @param eq * @param ot * @param op * @return */ Point& NextFlipPoint(Point& ep, Point& eq, Triangle& ot, Point& op); /** * Scan part of the FlipScan algorithm
* When a triangle pair isn't flippable we will scan for the next * point that is inside the flip triangle scan area. When found * we generate a new flipEdgeEvent * * @param tcx * @param ep - last point on the edge we are traversing * @param eq - first point on the edge we are traversing * @param flipTriangle - the current triangle sharing the point eq with edge * @param t * @param p */ void FlipScanEdgeEvent(SweepContext& tcx, Point& ep, Point& eq, Triangle& flip_triangle, Triangle& t, Point& p); void FinalizationPolygon(SweepContext& tcx); std::vector nodes_; }; } #endifSlic3r-version_1.39.1/xs/src/poly2tri/sweep/sweep_context.cc000066400000000000000000000125261324354444700240570ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "sweep_context.h" #include #include "advancing_front.h" namespace p2t { SweepContext::SweepContext(const std::vector& polyline) : points_(polyline), front_(0), head_(0), tail_(0), af_head_(0), af_middle_(0), af_tail_(0) { InitEdges(points_); } void SweepContext::AddHole(const std::vector& polyline) { InitEdges(polyline); for(unsigned int i = 0; i < polyline.size(); i++) { points_.push_back(polyline[i]); } } void SweepContext::AddPoint(Point* point) { points_.push_back(point); } std::vector &SweepContext::GetTriangles() { return triangles_; } std::list &SweepContext::GetMap() { return map_; } void SweepContext::InitTriangulation() { double xmax(points_[0]->x), xmin(points_[0]->x); double ymax(points_[0]->y), ymin(points_[0]->y); // Calculate bounds. for (unsigned int i = 0; i < points_.size(); i++) { Point& p = *points_[i]; if (p.x > xmax) xmax = p.x; if (p.x < xmin) xmin = p.x; if (p.y > ymax) ymax = p.y; if (p.y < ymin) ymin = p.y; } double dx = kAlpha * (xmax - xmin); double dy = kAlpha * (ymax - ymin); head_ = new Point(xmax + dx, ymin - dy); tail_ = new Point(xmin - dx, ymin - dy); // Sort points along y-axis std::sort(points_.begin(), points_.end(), cmp); } void SweepContext::InitEdges(const std::vector& polyline) { size_t num_points = polyline.size(); for (size_t i = 0; i < num_points; i++) { size_t j = i < num_points - 1 ? i + 1 : 0; edge_list.push_back(new Edge(*polyline[i], *polyline[j])); } } Point* SweepContext::GetPoint(size_t index) { return points_[index]; } void SweepContext::AddToMap(Triangle* triangle) { map_.push_back(triangle); } Node& SweepContext::LocateNode(const Point& point) { // TODO implement search tree return *front_->LocateNode(point.x); } void SweepContext::CreateAdvancingFront(const std::vector& nodes) { (void) nodes; // Initial triangle Triangle* triangle = new Triangle(*points_[0], *tail_, *head_); map_.push_back(triangle); af_head_ = new Node(*triangle->GetPoint(1), *triangle); af_middle_ = new Node(*triangle->GetPoint(0), *triangle); af_tail_ = new Node(*triangle->GetPoint(2)); front_ = new AdvancingFront(*af_head_, *af_tail_); // TODO: More intuitive if head is middles next and not previous? // so swap head and tail af_head_->next = af_middle_; af_middle_->next = af_tail_; af_middle_->prev = af_head_; af_tail_->prev = af_middle_; } void SweepContext::RemoveNode(Node* node) { delete node; } void SweepContext::MapTriangleToNodes(Triangle& t) { for (int i = 0; i < 3; i++) { if (!t.GetNeighbor(i)) { Node* n = front_->LocatePoint(t.PointCW(*t.GetPoint(i))); if (n) n->triangle = &t; } } } void SweepContext::RemoveFromMap(Triangle* triangle) { map_.remove(triangle); } void SweepContext::MeshClean(Triangle& triangle) { std::vector triangles; triangles.push_back(&triangle); while(!triangles.empty()){ Triangle *t = triangles.back(); triangles.pop_back(); if (t != NULL && !t->IsInterior()) { t->IsInterior(true); triangles_.push_back(t); for (int i = 0; i < 3; i++) { if (!t->constrained_edge[i]) triangles.push_back(t->GetNeighbor(i)); } } } } SweepContext::~SweepContext() { // Clean up memory delete head_; delete tail_; delete front_; delete af_head_; delete af_middle_; delete af_tail_; typedef std::list type_list; for(type_list::iterator iter = map_.begin(); iter != map_.end(); ++iter) { Triangle* ptr = *iter; delete ptr; } for(unsigned int i = 0; i < edge_list.size(); i++) { delete edge_list[i]; } } } Slic3r-version_1.39.1/xs/src/poly2tri/sweep/sweep_context.h000066400000000000000000000101201324354444700237050ustar00rootroot00000000000000/* * Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef SWEEP_CONTEXT_H #define SWEEP_CONTEXT_H #include #include #include namespace p2t { // Inital triangle factor, seed triangle will extend 30% of // PointSet width to both left and right. const double kAlpha = 0.3; struct Point; class Triangle; struct Node; struct Edge; class AdvancingFront; class SweepContext { public: /// Constructor SweepContext(const std::vector& polyline); /// Destructor ~SweepContext(); void set_head(Point* p1); Point* head() const; void set_tail(Point* p1); Point* tail() const; size_t point_count() const; Node& LocateNode(const Point& point); void RemoveNode(Node* node); void CreateAdvancingFront(const std::vector& nodes); /// Try to map a node to all sides of this triangle that don't have a neighbor void MapTriangleToNodes(Triangle& t); void AddToMap(Triangle* triangle); Point* GetPoint(size_t index); Point* GetPoints(); void RemoveFromMap(Triangle* triangle); void AddHole(const std::vector& polyline); void AddPoint(Point* point); AdvancingFront* front() const; void MeshClean(Triangle& triangle); std::vector &GetTriangles(); std::list &GetMap(); std::vector edge_list; struct Basin { Node* left_node; Node* bottom_node; Node* right_node; double width; bool left_highest; Basin() : left_node(NULL), bottom_node(NULL), right_node(NULL), width(0.0), left_highest(false) { } void Clear() { left_node = NULL; bottom_node = NULL; right_node = NULL; width = 0.0; left_highest = false; } }; struct EdgeEvent { Edge* constrained_edge; bool right; EdgeEvent() : constrained_edge(NULL), right(false) { } }; Basin basin; EdgeEvent edge_event; private: friend class Sweep; std::vector triangles_; std::list map_; std::vector points_; // Advancing front AdvancingFront* front_; // head point used with advancing front Point* head_; // tail point used with advancing front Point* tail_; Node *af_head_, *af_middle_, *af_tail_; void InitTriangulation(); void InitEdges(const std::vector& polyline); }; inline AdvancingFront* SweepContext::front() const { return front_; } inline size_t SweepContext::point_count() const { return points_.size(); } inline void SweepContext::set_head(Point* p1) { head_ = p1; } inline Point* SweepContext::head() const { return head_; } inline void SweepContext::set_tail(Point* p1) { tail_ = p1; } inline Point* SweepContext::tail() const { return tail_; } } #endif Slic3r-version_1.39.1/xs/src/polypartition.cpp000066400000000000000000001263201324354444700213710ustar00rootroot00000000000000//Copyright (C) 2011 by Ivan Fratric // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // //The above copyright notice and this permission notice shall be included in //all copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN //THE SOFTWARE. #include #include #include #include #include #include using namespace std; #include "polypartition.h" #define TPPL_VERTEXTYPE_REGULAR 0 #define TPPL_VERTEXTYPE_START 1 #define TPPL_VERTEXTYPE_END 2 #define TPPL_VERTEXTYPE_SPLIT 3 #define TPPL_VERTEXTYPE_MERGE 4 TPPLPoly::TPPLPoly() { hole = false; numpoints = 0; points = NULL; } TPPLPoly::~TPPLPoly() { if(points) delete [] points; } void TPPLPoly::Clear() { if(points) delete [] points; hole = false; numpoints = 0; points = NULL; } void TPPLPoly::Init(long numpoints) { Clear(); this->numpoints = numpoints; points = new TPPLPoint[numpoints]; } void TPPLPoly::Triangle(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3) { Init(3); points[0] = p1; points[1] = p2; points[2] = p3; } TPPLPoly::TPPLPoly(const TPPLPoly &src) { hole = src.hole; numpoints = src.numpoints; points = new TPPLPoint[numpoints]; memcpy(points, src.points, numpoints*sizeof(TPPLPoint)); } TPPLPoly& TPPLPoly::operator=(const TPPLPoly &src) { if(&src != this) { Clear(); hole = src.hole; numpoints = src.numpoints; points = new TPPLPoint[numpoints]; memcpy(points, src.points, numpoints*sizeof(TPPLPoint)); } return *this; } int TPPLPoly::GetOrientation() const { long i1,i2; tppl_float area = 0; for(i1=0; i10) return TPPL_CCW; if(area<0) return TPPL_CW; return 0; } void TPPLPoly::SetOrientation(int orientation) { int polyorientation = GetOrientation(); if(polyorientation&&(polyorientation!=orientation)) { Invert(); } } void TPPLPoly::Invert() { long i; TPPLPoint *invpoints; invpoints = new TPPLPoint[numpoints]; for(i=0;i0) return 0; if(dot21*dot22>0) return 0; return 1; } //removes holes from inpolys by merging them with non-holes int TPPLPartition::RemoveHoles(list *inpolys, list *outpolys) { list polys; list::iterator holeiter,polyiter,iter,iter2; long i,i2,holepointindex,polypointindex = 0; TPPLPoint holepoint,polypoint,bestpolypoint; TPPLPoint linep1,linep2; TPPLPoint v1,v2; TPPLPoly newpoly; bool hasholes; bool pointvisible; bool pointfound; //check for trivial case (no holes) hasholes = false; for(iter = inpolys->begin(); iter!=inpolys->end(); ++iter) { if(iter->IsHole()) { hasholes = true; break; } } if(!hasholes) { for(iter = inpolys->begin(); iter!=inpolys->end(); ++iter) { outpolys->push_back(*iter); } return 1; } polys = *inpolys; while(1) { //find the hole point with the largest x hasholes = false; for(iter = polys.begin(); iter!=polys.end(); ++iter) { if(!iter->IsHole()) continue; if(!hasholes) { hasholes = true; holeiter = iter; holepointindex = 0; } for(i=0; i < iter->GetNumPoints(); i++) { if(iter->GetPoint(i).x > holeiter->GetPoint(holepointindex).x) { holeiter = iter; holepointindex = i; } } } if(!hasholes) break; holepoint = holeiter->GetPoint(holepointindex); pointfound = false; for(iter = polys.begin(); iter!=polys.end(); ++iter) { if(iter->IsHole()) continue; for(i=0; i < iter->GetNumPoints(); i++) { if(iter->GetPoint(i).x <= holepoint.x) continue; if(!InCone(iter->GetPoint((i+iter->GetNumPoints()-1)%(iter->GetNumPoints())), iter->GetPoint(i), iter->GetPoint((i+1)%(iter->GetNumPoints())), holepoint)) continue; polypoint = iter->GetPoint(i); if(pointfound) { v1 = Normalize(polypoint-holepoint); v2 = Normalize(bestpolypoint-holepoint); if(v2.x > v1.x) continue; } pointvisible = true; for(iter2 = polys.begin(); iter2!=polys.end(); ++iter2) { if(iter2->IsHole()) continue; for(i2=0; i2 < iter2->GetNumPoints(); i2++) { linep1 = iter2->GetPoint(i2); linep2 = iter2->GetPoint((i2+1)%(iter2->GetNumPoints())); if(Intersects(holepoint,polypoint,linep1,linep2)) { pointvisible = false; break; } } if(!pointvisible) break; } if(pointvisible) { pointfound = true; bestpolypoint = polypoint; polyiter = iter; polypointindex = i; } } } if(!pointfound) return 0; newpoly.Init(holeiter->GetNumPoints() + polyiter->GetNumPoints() + 2); i2 = 0; for(i=0;i<=polypointindex;i++) { newpoly[i2] = polyiter->GetPoint(i); i2++; } for(i=0;i<=holeiter->GetNumPoints();i++) { newpoly[i2] = holeiter->GetPoint((i+holepointindex)%holeiter->GetNumPoints()); i2++; } for(i=polypointindex;iGetNumPoints();i++) { newpoly[i2] = polyiter->GetPoint(i); i2++; } polys.erase(holeiter); polys.erase(polyiter); polys.push_back(newpoly); } for(iter = polys.begin(); iter!=polys.end(); ++iter) { outpolys->push_back(*iter); } return 1; } bool TPPLPartition::IsConvex(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3) { tppl_float tmp; tmp = (p3.y-p1.y)*(p2.x-p1.x)-(p3.x-p1.x)*(p2.y-p1.y); if(tmp>0) return 1; else return 0; } bool TPPLPartition::IsReflex(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3) { tppl_float tmp; tmp = (p3.y-p1.y)*(p2.x-p1.x)-(p3.x-p1.x)*(p2.y-p1.y); if(tmp<0) return 1; else return 0; } bool TPPLPartition::IsInside(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3, TPPLPoint &p) { if(IsConvex(p1,p,p2)) return false; if(IsConvex(p2,p,p3)) return false; if(IsConvex(p3,p,p1)) return false; return true; } bool TPPLPartition::InCone(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3, TPPLPoint &p) { bool convex; convex = IsConvex(p1,p2,p3); if(convex) { if(!IsConvex(p1,p2,p)) return false; if(!IsConvex(p2,p3,p)) return false; return true; } else { if(IsConvex(p1,p2,p)) return true; if(IsConvex(p2,p3,p)) return true; return false; } } bool TPPLPartition::InCone(PartitionVertex *v, TPPLPoint &p) { TPPLPoint p1,p2,p3; p1 = v->previous->p; p2 = v->p; p3 = v->next->p; return InCone(p1,p2,p3,p); } void TPPLPartition::UpdateVertexReflexity(PartitionVertex *v) { PartitionVertex *v1,*v3; v1 = v->previous; v3 = v->next; v->isConvex = !IsReflex(v1->p,v->p,v3->p); } void TPPLPartition::UpdateVertex(PartitionVertex *v, PartitionVertex *vertices, long numvertices) { long i; PartitionVertex *v1,*v3; TPPLPoint vec1,vec3; v1 = v->previous; v3 = v->next; v->isConvex = IsConvex(v1->p,v->p,v3->p); vec1 = Normalize(v1->p - v->p); vec3 = Normalize(v3->p - v->p); v->angle = vec1.x*vec3.x + vec1.y*vec3.y; if(v->isConvex) { v->isEar = true; for(i=0;ip.x)&&(vertices[i].p.y==v->p.y)) continue; if((vertices[i].p.x==v1->p.x)&&(vertices[i].p.y==v1->p.y)) continue; if((vertices[i].p.x==v3->p.x)&&(vertices[i].p.y==v3->p.y)) continue; if(IsInside(v1->p,v->p,v3->p,vertices[i].p)) { v->isEar = false; break; } } } else { v->isEar = false; } } //triangulation by ear removal int TPPLPartition::Triangulate_EC(TPPLPoly *poly, list *triangles) { long numvertices; PartitionVertex *vertices; PartitionVertex *ear; TPPLPoly triangle; long i,j; bool earfound; if(poly->GetNumPoints() < 3) return 0; if(poly->GetNumPoints() == 3) { triangles->push_back(*poly); return 1; } numvertices = poly->GetNumPoints(); vertices = new PartitionVertex[numvertices]; for(i=0;iGetPoint(i); if(i==(numvertices-1)) vertices[i].next=&(vertices[0]); else vertices[i].next=&(vertices[i+1]); if(i==0) vertices[i].previous = &(vertices[numvertices-1]); else vertices[i].previous = &(vertices[i-1]); } for(i=0;i ear->angle) { ear = &(vertices[j]); } } } if(!earfound) { delete [] vertices; return 0; } triangle.Triangle(ear->previous->p,ear->p,ear->next->p); triangles->push_back(triangle); ear->isActive = false; ear->previous->next = ear->next; ear->next->previous = ear->previous; if(i==numvertices-4) break; UpdateVertex(ear->previous,vertices,numvertices); UpdateVertex(ear->next,vertices,numvertices); } for(i=0;ip,vertices[i].p,vertices[i].next->p); triangles->push_back(triangle); break; } } delete [] vertices; return 1; } int TPPLPartition::Triangulate_EC(list *inpolys, list *triangles) { list outpolys; list::iterator iter; if(!RemoveHoles(inpolys,&outpolys)) return 0; for(iter=outpolys.begin();iter!=outpolys.end();++iter) { if(!Triangulate_EC(&(*iter),triangles)) return 0; } return 1; } int TPPLPartition::ConvexPartition_HM(TPPLPoly *poly, list *parts) { list triangles; list::iterator iter1,iter2; TPPLPoly *poly1,*poly2; TPPLPoly newpoly; TPPLPoint d1,d2,p1,p2,p3; long i11,i12,i21,i22,i13,i23,j,k; bool isdiagonal; long numreflex; //check if the poly is already convex numreflex = 0; for(i11=0;i11GetNumPoints();i11++) { if(i11==0) i12 = poly->GetNumPoints()-1; else i12=i11-1; if(i11==(poly->GetNumPoints()-1)) i13=0; else i13=i11+1; if(IsReflex(poly->GetPoint(i12),poly->GetPoint(i11),poly->GetPoint(i13))) { numreflex = 1; break; } } if(numreflex == 0) { parts->push_back(*poly); return 1; } if(!Triangulate_EC(poly,&triangles)) return 0; for(iter1 = triangles.begin(); iter1 != triangles.end(); ++iter1) { poly1 = &(*iter1); for(i11=0;i11GetNumPoints();i11++) { d1 = poly1->GetPoint(i11); i12 = (i11+1)%(poly1->GetNumPoints()); d2 = poly1->GetPoint(i12); isdiagonal = false; for(iter2 = iter1; iter2 != triangles.end(); ++iter2) { if(iter1 == iter2) continue; poly2 = &(*iter2); for(i21=0;i21GetNumPoints();i21++) { if((d2.x != poly2->GetPoint(i21).x)||(d2.y != poly2->GetPoint(i21).y)) continue; i22 = (i21+1)%(poly2->GetNumPoints()); if((d1.x != poly2->GetPoint(i22).x)||(d1.y != poly2->GetPoint(i22).y)) continue; isdiagonal = true; break; } if(isdiagonal) break; } if(!isdiagonal) continue; p2 = poly1->GetPoint(i11); if(i11 == 0) i13 = poly1->GetNumPoints()-1; else i13 = i11-1; p1 = poly1->GetPoint(i13); if(i22 == (poly2->GetNumPoints()-1)) i23 = 0; else i23 = i22+1; p3 = poly2->GetPoint(i23); if(!IsConvex(p1,p2,p3)) continue; p2 = poly1->GetPoint(i12); if(i12 == (poly1->GetNumPoints()-1)) i13 = 0; else i13 = i12+1; p3 = poly1->GetPoint(i13); if(i21 == 0) i23 = poly2->GetNumPoints()-1; else i23 = i21-1; p1 = poly2->GetPoint(i23); if(!IsConvex(p1,p2,p3)) continue; newpoly.Init(poly1->GetNumPoints()+poly2->GetNumPoints()-2); k = 0; for(j=i12;j!=i11;j=(j+1)%(poly1->GetNumPoints())) { newpoly[k] = poly1->GetPoint(j); k++; } for(j=i22;j!=i21;j=(j+1)%(poly2->GetNumPoints())) { newpoly[k] = poly2->GetPoint(j); k++; } triangles.erase(iter2); *iter1 = newpoly; poly1 = &(*iter1); i11 = -1; continue; } } for(iter1 = triangles.begin(); iter1 != triangles.end(); ++iter1) { parts->push_back(*iter1); } return 1; } int TPPLPartition::ConvexPartition_HM(list *inpolys, list *parts) { list outpolys; list::iterator iter; if(!RemoveHoles(inpolys,&outpolys)) return 0; for(iter=outpolys.begin();iter!=outpolys.end();++iter) { if(!ConvexPartition_HM(&(*iter),parts)) return 0; } return 1; } //minimum-weight polygon triangulation by dynamic programming //O(n^3) time complexity //O(n^2) space complexity int TPPLPartition::Triangulate_OPT(TPPLPoly *poly, list *triangles) { long i,j,k,gap,n; DPState **dpstates; TPPLPoint p1,p2,p3,p4; long bestvertex; tppl_float weight,minweight,d1,d2; Diagonal diagonal,newdiagonal; list diagonals; TPPLPoly triangle; int ret = 1; n = poly->GetNumPoints(); dpstates = new DPState *[n]; for(i=1;iGetPoint(i); for(j=i+1;jGetPoint(j); //visibility check if(i==0) p3 = poly->GetPoint(n-1); else p3 = poly->GetPoint(i-1); if(i==(n-1)) p4 = poly->GetPoint(0); else p4 = poly->GetPoint(i+1); if(!InCone(p3,p1,p4,p2)) { dpstates[j][i].visible = false; continue; } if(j==0) p3 = poly->GetPoint(n-1); else p3 = poly->GetPoint(j-1); if(j==(n-1)) p4 = poly->GetPoint(0); else p4 = poly->GetPoint(j+1); if(!InCone(p3,p2,p4,p1)) { dpstates[j][i].visible = false; continue; } for(k=0;kGetPoint(k); if(k==(n-1)) p4 = poly->GetPoint(0); else p4 = poly->GetPoint(k+1); if(Intersects(p1,p2,p3,p4)) { dpstates[j][i].visible = false; break; } } } } } dpstates[n-1][0].visible = true; dpstates[n-1][0].weight = 0; dpstates[n-1][0].bestvertex = -1; for(gap = 2; gapGetPoint(i),poly->GetPoint(k)); if(j<=(k+1)) d2=0; else d2 = Distance(poly->GetPoint(k),poly->GetPoint(j)); weight = dpstates[k][i].weight + dpstates[j][k].weight + d1 + d2; if((bestvertex == -1)||(weightGetPoint(diagonal.index1),poly->GetPoint(bestvertex),poly->GetPoint(diagonal.index2)); triangles->push_back(triangle); if(bestvertex > (diagonal.index1+1)) { newdiagonal.index1 = diagonal.index1; newdiagonal.index2 = bestvertex; diagonals.push_back(newdiagonal); } if(diagonal.index2 > (bestvertex+1)) { newdiagonal.index1 = bestvertex; newdiagonal.index2 = diagonal.index2; diagonals.push_back(newdiagonal); } } for(i=1;i *pairs; long w2; w2 = dpstates[a][b].weight; if(w>w2) return; pairs = &(dpstates[a][b].pairs); newdiagonal.index1 = i; newdiagonal.index2 = j; if(wclear(); pairs->push_front(newdiagonal); dpstates[a][b].weight = w; } else { if((!pairs->empty())&&(i <= pairs->begin()->index1)) return; while((!pairs->empty())&&(pairs->begin()->index2 >= j)) pairs->pop_front(); pairs->push_front(newdiagonal); } } void TPPLPartition::TypeA(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates) { list *pairs; list::iterator iter,lastiter; long top; long w; if(!dpstates[i][j].visible) return; top = j; w = dpstates[i][j].weight; if(k-j > 1) { if (!dpstates[j][k].visible) return; w += dpstates[j][k].weight + 1; } if(j-i > 1) { pairs = &(dpstates[i][j].pairs); iter = pairs->end(); lastiter = pairs->end(); while(iter!=pairs->begin()) { --iter; if(!IsReflex(vertices[iter->index2].p,vertices[j].p,vertices[k].p)) lastiter = iter; else break; } if(lastiter == pairs->end()) w++; else { if(IsReflex(vertices[k].p,vertices[i].p,vertices[lastiter->index1].p)) w++; else top = lastiter->index1; } } UpdateState(i,k,w,top,j,dpstates); } void TPPLPartition::TypeB(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates) { list *pairs; list::iterator iter,lastiter; long top; long w; if(!dpstates[j][k].visible) return; top = j; w = dpstates[j][k].weight; if (j-i > 1) { if (!dpstates[i][j].visible) return; w += dpstates[i][j].weight + 1; } if (k-j > 1) { pairs = &(dpstates[j][k].pairs); iter = pairs->begin(); if((!pairs->empty())&&(!IsReflex(vertices[i].p,vertices[j].p,vertices[iter->index1].p))) { lastiter = iter; while(iter!=pairs->end()) { if(!IsReflex(vertices[i].p,vertices[j].p,vertices[iter->index1].p)) { lastiter = iter; ++iter; } else break; } if(IsReflex(vertices[lastiter->index2].p,vertices[k].p,vertices[i].p)) w++; else top = lastiter->index2; } else w++; } UpdateState(i,k,w,j,top,dpstates); } int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, list *parts) { TPPLPoint p1,p2,p3,p4; PartitionVertex *vertices; DPState2 **dpstates; long i,j,k,n,gap; list diagonals,diagonals2; Diagonal diagonal,newdiagonal; list *pairs,*pairs2; list::iterator iter,iter2; int ret; TPPLPoly newpoly; list indices; list::iterator iiter; bool ijreal,jkreal; n = poly->GetNumPoints(); vertices = new PartitionVertex[n]; dpstates = new DPState2 *[n]; for(i=0;iGetPoint(i); vertices[i].isActive = true; if(i==0) vertices[i].previous = &(vertices[n-1]); else vertices[i].previous = &(vertices[i-1]); if(i==(poly->GetNumPoints()-1)) vertices[i].next = &(vertices[0]); else vertices[i].next = &(vertices[i+1]); } for(i=1;iGetPoint(i); for(j=i+1;jGetPoint(j); //visibility check if(!InCone(&vertices[i],p2)) { dpstates[i][j].visible = false; continue; } if(!InCone(&vertices[j],p1)) { dpstates[i][j].visible = false; continue; } for(k=0;kGetPoint(k); if(k==(n-1)) p4 = poly->GetPoint(0); else p4 = poly->GetPoint(k+1); if(Intersects(p1,p2,p3,p4)) { dpstates[i][j].visible = false; break; } } } } } for(i=0;i<(n-2);i++) { j = i+2; if(dpstates[i][j].visible) { dpstates[i][j].weight = 0; newdiagonal.index1 = i+1; newdiagonal.index2 = i+1; dpstates[i][j].pairs.push_back(newdiagonal); } } dpstates[0][n-1].visible = true; vertices[0].isConvex = false; //by convention for(gap=3; gapempty()) { ret = 0; break; } if(!vertices[diagonal.index1].isConvex) { iter = pairs->end(); --iter; j = iter->index2; newdiagonal.index1 = j; newdiagonal.index2 = diagonal.index2; diagonals.push_front(newdiagonal); if((j - diagonal.index1)>1) { if(iter->index1 != iter->index2) { pairs2 = &(dpstates[diagonal.index1][j].pairs); while(1) { if(pairs2->empty()) { ret = 0; break; } iter2 = pairs2->end(); --iter2; if(iter->index1 != iter2->index1) pairs2->pop_back(); else break; } if(ret == 0) break; } newdiagonal.index1 = diagonal.index1; newdiagonal.index2 = j; diagonals.push_front(newdiagonal); } } else { iter = pairs->begin(); j = iter->index1; newdiagonal.index1 = diagonal.index1; newdiagonal.index2 = j; diagonals.push_front(newdiagonal); if((diagonal.index2 - j) > 1) { if(iter->index1 != iter->index2) { pairs2 = &(dpstates[j][diagonal.index2].pairs); while(1) { if(pairs2->empty()) { ret = 0; break; } iter2 = pairs2->begin(); if(iter->index2 != iter2->index2) pairs2->pop_front(); else break; } if(ret == 0) break; } newdiagonal.index1 = j; newdiagonal.index2 = diagonal.index2; diagonals.push_front(newdiagonal); } } } if(ret == 0) { for(i=0;iend(); --iter; j = iter->index2; if(iter->index1 != iter->index2) ijreal = false; } else { iter = pairs->begin(); j = iter->index1; if(iter->index1 != iter->index2) jkreal = false; } newdiagonal.index1 = diagonal.index1; newdiagonal.index2 = j; if(ijreal) { diagonals.push_back(newdiagonal); } else { diagonals2.push_back(newdiagonal); } newdiagonal.index1 = j; newdiagonal.index2 = diagonal.index2; if(jkreal) { diagonals.push_back(newdiagonal); } else { diagonals2.push_back(newdiagonal); } indices.push_back(j); } indices.sort(); newpoly.Init((long)indices.size()); k=0; for(iiter = indices.begin();iiter!=indices.end(); ++iiter) { newpoly[k] = vertices[*iiter].p; k++; } parts->push_back(newpoly); } for(i=0;i *inpolys, list *monotonePolys) { list::iterator iter; MonotoneVertex *vertices; long i,numvertices,vindex,vindex2,newnumvertices,maxnumvertices; long polystartindex, polyendindex; TPPLPoly *poly; MonotoneVertex *v,*v2,*vprev,*vnext; ScanLineEdge newedge; bool error = false; numvertices = 0; for(iter = inpolys->begin(); iter != inpolys->end(); ++iter) { numvertices += iter->GetNumPoints(); } maxnumvertices = numvertices*3; vertices = new MonotoneVertex[maxnumvertices]; newnumvertices = numvertices; polystartindex = 0; for(iter = inpolys->begin(); iter != inpolys->end(); ++iter) { poly = &(*iter); polyendindex = polystartindex + poly->GetNumPoints()-1; for(i=0;iGetNumPoints();i++) { vertices[i+polystartindex].p = poly->GetPoint(i); if(i==0) vertices[i+polystartindex].previous = polyendindex; else vertices[i+polystartindex].previous = i+polystartindex-1; if(i==(poly->GetNumPoints()-1)) vertices[i+polystartindex].next = polystartindex; else vertices[i+polystartindex].next = i+polystartindex+1; } polystartindex = polyendindex+1; } //construct the priority queue long *priority = new long [numvertices]; for(i=0;iprevious]); vnext = &(vertices[v->next]); if(Below(vprev->p,v->p)&&Below(vnext->p,v->p)) { if(IsConvex(vnext->p,vprev->p,v->p)) { vertextypes[i] = TPPL_VERTEXTYPE_START; } else { vertextypes[i] = TPPL_VERTEXTYPE_SPLIT; } } else if(Below(v->p,vprev->p)&&Below(v->p,vnext->p)) { if(IsConvex(vnext->p,vprev->p,v->p)) { vertextypes[i] = TPPL_VERTEXTYPE_END; } else { vertextypes[i] = TPPL_VERTEXTYPE_MERGE; } } else { vertextypes[i] = TPPL_VERTEXTYPE_REGULAR; } } //helpers long *helpers = new long[maxnumvertices]; //binary search tree that holds edges intersecting the scanline //note that while set doesn't actually have to be implemented as a tree //complexity requirements for operations are the same as for the balanced binary search tree set edgeTree; //store iterators to the edge tree elements //this makes deleting existing edges much faster set::iterator *edgeTreeIterators,edgeIter; edgeTreeIterators = new set::iterator[maxnumvertices]; pair::iterator,bool> edgeTreeRet; //for each vertex for(i=0;ip; newedge.p2 = vertices[v->next].p; newedge.index = vindex; edgeTreeRet = edgeTree.insert(newedge); edgeTreeIterators[vindex] = edgeTreeRet.first; helpers[vindex] = vindex; break; case TPPL_VERTEXTYPE_END: //if helper(ei-1) is a merge vertex if(vertextypes[helpers[v->previous]]==TPPL_VERTEXTYPE_MERGE) { //Insert the diagonal connecting vi to helper(ei-1) in D. AddDiagonal(vertices,&newnumvertices,vindex,helpers[v->previous]); vertextypes[newnumvertices-2] = vertextypes[vindex]; edgeTreeIterators[newnumvertices-2] = edgeTreeIterators[vindex]; helpers[newnumvertices-2] = helpers[vindex]; vertextypes[newnumvertices-1] = vertextypes[helpers[v->previous]]; edgeTreeIterators[newnumvertices-1] = edgeTreeIterators[helpers[v->previous]]; helpers[newnumvertices-1] = helpers[helpers[v->previous]]; } //Delete ei-1 from T edgeTree.erase(edgeTreeIterators[v->previous]); break; case TPPL_VERTEXTYPE_SPLIT: //Search in T to find the edge e j directly left of vi. newedge.p1 = v->p; newedge.p2 = v->p; edgeIter = edgeTree.lower_bound(newedge); if(edgeIter == edgeTree.begin()) { error = true; break; } --edgeIter; //Insert the diagonal connecting vi to helper(ej) in D. AddDiagonal(vertices,&newnumvertices,vindex,helpers[edgeIter->index]); vertextypes[newnumvertices-2] = vertextypes[vindex]; edgeTreeIterators[newnumvertices-2] = edgeTreeIterators[vindex]; helpers[newnumvertices-2] = helpers[vindex]; vertextypes[newnumvertices-1] = vertextypes[helpers[edgeIter->index]]; edgeTreeIterators[newnumvertices-1] = edgeTreeIterators[helpers[edgeIter->index]]; helpers[newnumvertices-1] = helpers[helpers[edgeIter->index]]; vindex2 = newnumvertices-2; v2 = &(vertices[vindex2]); //helper(e j)�vi helpers[edgeIter->index] = vindex; //Insert ei in T and set helper(ei) to vi. newedge.p1 = v2->p; newedge.p2 = vertices[v2->next].p; newedge.index = vindex2; edgeTreeRet = edgeTree.insert(newedge); edgeTreeIterators[vindex2] = edgeTreeRet.first; helpers[vindex2] = vindex2; break; case TPPL_VERTEXTYPE_MERGE: //if helper(ei-1) is a merge vertex if(vertextypes[helpers[v->previous]]==TPPL_VERTEXTYPE_MERGE) { //Insert the diagonal connecting vi to helper(ei-1) in D. AddDiagonal(vertices,&newnumvertices,vindex,helpers[v->previous]); vertextypes[newnumvertices-2] = vertextypes[vindex]; edgeTreeIterators[newnumvertices-2] = edgeTreeIterators[vindex]; helpers[newnumvertices-2] = helpers[vindex]; vertextypes[newnumvertices-1] = vertextypes[helpers[v->previous]]; edgeTreeIterators[newnumvertices-1] = edgeTreeIterators[helpers[v->previous]]; helpers[newnumvertices-1] = helpers[helpers[v->previous]]; vindex2 = newnumvertices-2; v2 = &(vertices[vindex2]); } //Delete ei-1 from T. edgeTree.erase(edgeTreeIterators[v->previous]); //Search in T to find the edge e j directly left of vi. newedge.p1 = v->p; newedge.p2 = v->p; edgeIter = edgeTree.lower_bound(newedge); if(edgeIter == edgeTree.begin()) { error = true; break; } --edgeIter; //if helper(ej) is a merge vertex if(vertextypes[helpers[edgeIter->index]]==TPPL_VERTEXTYPE_MERGE) { //Insert the diagonal connecting vi to helper(e j) in D. AddDiagonal(vertices,&newnumvertices,vindex2,helpers[edgeIter->index]); vertextypes[newnumvertices-2] = vertextypes[vindex2]; edgeTreeIterators[newnumvertices-2] = edgeTreeIterators[vindex2]; helpers[newnumvertices-2] = helpers[vindex2]; vertextypes[newnumvertices-1] = vertextypes[helpers[edgeIter->index]]; edgeTreeIterators[newnumvertices-1] = edgeTreeIterators[helpers[edgeIter->index]]; helpers[newnumvertices-1] = helpers[helpers[edgeIter->index]]; } //helper(e j)�vi helpers[edgeIter->index] = vindex2; break; case TPPL_VERTEXTYPE_REGULAR: //if the interior of P lies to the right of vi if(Below(v->p,vertices[v->previous].p)) { //if helper(ei-1) is a merge vertex if(vertextypes[helpers[v->previous]]==TPPL_VERTEXTYPE_MERGE) { //Insert the diagonal connecting vi to helper(ei-1) in D. AddDiagonal(vertices,&newnumvertices,vindex,helpers[v->previous]); vertextypes[newnumvertices-2] = vertextypes[vindex]; edgeTreeIterators[newnumvertices-2] = edgeTreeIterators[vindex]; helpers[newnumvertices-2] = helpers[vindex]; vertextypes[newnumvertices-1] = vertextypes[helpers[v->previous]]; edgeTreeIterators[newnumvertices-1] = edgeTreeIterators[helpers[v->previous]]; helpers[newnumvertices-1] = helpers[helpers[v->previous]]; vindex2 = newnumvertices-2; v2 = &(vertices[vindex2]); } //Delete ei-1 from T. edgeTree.erase(edgeTreeIterators[v->previous]); //Insert ei in T and set helper(ei) to vi. newedge.p1 = v2->p; newedge.p2 = vertices[v2->next].p; newedge.index = vindex2; edgeTreeRet = edgeTree.insert(newedge); edgeTreeIterators[vindex2] = edgeTreeRet.first; helpers[vindex2] = vindex; } else { //Search in T to find the edge ej directly left of vi. newedge.p1 = v->p; newedge.p2 = v->p; edgeIter = edgeTree.lower_bound(newedge); if(edgeIter == edgeTree.begin()) { error = true; break; } --edgeIter; //if helper(ej) is a merge vertex if(vertextypes[helpers[edgeIter->index]]==TPPL_VERTEXTYPE_MERGE) { //Insert the diagonal connecting vi to helper(e j) in D. AddDiagonal(vertices,&newnumvertices,vindex,helpers[edgeIter->index]); vertextypes[newnumvertices-2] = vertextypes[vindex]; edgeTreeIterators[newnumvertices-2] = edgeTreeIterators[vindex]; helpers[newnumvertices-2] = helpers[vindex]; vertextypes[newnumvertices-1] = vertextypes[helpers[edgeIter->index]]; edgeTreeIterators[newnumvertices-1] = edgeTreeIterators[helpers[edgeIter->index]]; helpers[newnumvertices-1] = helpers[helpers[edgeIter->index]]; } //helper(e j)�vi helpers[edgeIter->index] = vindex; } break; } if(error) break; } char *used = new char[newnumvertices]; memset(used,0,newnumvertices*sizeof(char)); if(!error) { //return result long size; TPPLPoly mpoly; for(i=0;inext]); size = 1; while(vnext!=v) { vnext = &(vertices[vnext->next]); size++; } mpoly.Init(size); v = &(vertices[i]); mpoly[0] = v->p; vnext = &(vertices[v->next]); size = 1; used[i] = 1; used[v->next] = 1; while(vnext!=v) { mpoly[size] = vnext->p; used[vnext->next] = 1; vnext = &(vertices[vnext->next]); size++; } monotonePolys->push_back(mpoly); } } //cleanup delete [] vertices; delete [] priority; delete [] vertextypes; delete [] edgeTreeIterators; delete [] helpers; delete [] used; if(error) { return 0; } else { return 1; } } //adds a diagonal to the doubly-connected list of vertices void TPPLPartition::AddDiagonal(MonotoneVertex *vertices, long *numvertices, long index1, long index2) { long newindex1,newindex2; newindex1 = *numvertices; (*numvertices)++; newindex2 = *numvertices; (*numvertices)++; vertices[newindex1].p = vertices[index1].p; vertices[newindex2].p = vertices[index2].p; vertices[newindex2].next = vertices[index2].next; vertices[newindex1].next = vertices[index1].next; vertices[vertices[index2].next].previous = newindex2; vertices[vertices[index1].next].previous = newindex1; vertices[index1].next = newindex2; vertices[newindex2].previous = index1; vertices[index2].next = newindex1; vertices[newindex1].previous = index2; } bool TPPLPartition::Below(TPPLPoint &p1, TPPLPoint &p2) { if(p1.y < p2.y) return true; else if(p1.y == p2.y) { if(p1.x < p2.x) return true; } return false; } //sorts in the falling order of y values, if y is equal, x is used instead bool TPPLPartition::VertexSorter::operator() (long index1, long index2) const { if(vertices[index1].p.y > vertices[index2].p.y) return true; else if(vertices[index1].p.y == vertices[index2].p.y) { if(vertices[index1].p.x > vertices[index2].p.x) return true; } return false; } bool TPPLPartition::ScanLineEdge::IsConvex(const TPPLPoint& p1, const TPPLPoint& p2, const TPPLPoint& p3) const { tppl_float tmp; tmp = (p3.y-p1.y)*(p2.x-p1.x)-(p3.x-p1.x)*(p2.y-p1.y); if(tmp>0) return 1; else return 0; } bool TPPLPartition::ScanLineEdge::operator < (const ScanLineEdge & other) const { if(other.p1.y == other.p2.y) { if(p1.y == p2.y) { if(p1.y < other.p1.y) return true; else return false; } if(IsConvex(p1,p2,other.p1)) return true; else return false; } else if(p1.y == p2.y) { if(IsConvex(other.p1,other.p2,p1)) return false; else return true; } else if(p1.y < other.p1.y) { if(IsConvex(other.p1,other.p2,p1)) return false; else return true; } else { if(IsConvex(p1,p2,other.p1)) return true; else return false; } } //triangulates monotone polygon //O(n) time, O(n) space complexity int TPPLPartition::TriangulateMonotone(TPPLPoly *inPoly, list *triangles) { long i,i2,j,topindex,bottomindex,leftindex,rightindex,vindex; TPPLPoint *points; long numpoints; TPPLPoly triangle; numpoints = inPoly->GetNumPoints(); points = inPoly->GetPoints(); //trivial calses if(numpoints < 3) return 0; if(numpoints == 3) { triangles->push_back(*inPoly); } topindex = 0; bottomindex=0; for(i=1;i=numpoints) i2 = 0; if(!Below(points[i2],points[i])) return 0; i = i2; } i = bottomindex; while(i!=topindex) { i2 = i+1; if(i2>=numpoints) i2 = 0; if(!Below(points[i],points[i2])) return 0; i = i2; } char *vertextypes = new char[numpoints]; long *priority = new long[numpoints]; //merge left and right vertex chains priority[0] = topindex; vertextypes[topindex] = 0; leftindex = topindex+1; if(leftindex>=numpoints) leftindex = 0; rightindex = topindex-1; if(rightindex<0) rightindex = numpoints-1; for(i=1;i<(numpoints-1);i++) { if(leftindex==bottomindex) { priority[i] = rightindex; rightindex--; if(rightindex<0) rightindex = numpoints-1; vertextypes[priority[i]] = -1; } else if(rightindex==bottomindex) { priority[i] = leftindex; leftindex++; if(leftindex>=numpoints) leftindex = 0; vertextypes[priority[i]] = 1; } else { if(Below(points[leftindex],points[rightindex])) { priority[i] = rightindex; rightindex--; if(rightindex<0) rightindex = numpoints-1; vertextypes[priority[i]] = -1; } else { priority[i] = leftindex; leftindex++; if(leftindex>=numpoints) leftindex = 0; vertextypes[priority[i]] = 1; } } } priority[i] = bottomindex; vertextypes[bottomindex] = 0; long *stack = new long[numpoints]; long stackptr = 0; stack[0] = priority[0]; stack[1] = priority[1]; stackptr = 2; //for each vertex from top to bottom trim as many triangles as possible for(i=2;i<(numpoints-1);i++) { vindex = priority[i]; if(vertextypes[vindex]!=vertextypes[stack[stackptr-1]]) { for(j=0;j<(stackptr-1);j++) { if(vertextypes[vindex]==1) { triangle.Triangle(points[stack[j+1]],points[stack[j]],points[vindex]); } else { triangle.Triangle(points[stack[j]],points[stack[j+1]],points[vindex]); } triangles->push_back(triangle); } stack[0] = priority[i-1]; stack[1] = priority[i]; stackptr = 2; } else { stackptr--; while(stackptr>0) { if(vertextypes[vindex]==1) { if(IsConvex(points[vindex],points[stack[stackptr-1]],points[stack[stackptr]])) { triangle.Triangle(points[vindex],points[stack[stackptr-1]],points[stack[stackptr]]); triangles->push_back(triangle); stackptr--; } else { break; } } else { if(IsConvex(points[vindex],points[stack[stackptr]],points[stack[stackptr-1]])) { triangle.Triangle(points[vindex],points[stack[stackptr]],points[stack[stackptr-1]]); triangles->push_back(triangle); stackptr--; } else { break; } } } stackptr++; stack[stackptr] = vindex; stackptr++; } } vindex = priority[i]; for(j=0;j<(stackptr-1);j++) { if(vertextypes[stack[j+1]]==1) { triangle.Triangle(points[stack[j]],points[stack[j+1]],points[vindex]); } else { triangle.Triangle(points[stack[j+1]],points[stack[j]],points[vindex]); } triangles->push_back(triangle); } delete [] priority; delete [] vertextypes; delete [] stack; return 1; } int TPPLPartition::Triangulate_MONO(list *inpolys, list *triangles) { list monotone; list::iterator iter; if(!MonotonePartition(inpolys,&monotone)) return 0; for(iter = monotone.begin(); iter!=monotone.end(); ++iter) { if(!TriangulateMonotone(&(*iter),triangles)) return 0; } return 1; } int TPPLPartition::Triangulate_MONO(TPPLPoly *poly, list *triangles) { list polys; polys.push_back(*poly); return Triangulate_MONO(&polys, triangles); } Slic3r-version_1.39.1/xs/src/polypartition.h000066400000000000000000000271271324354444700210430ustar00rootroot00000000000000//Copyright (C) 2011 by Ivan Fratric // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // //The above copyright notice and this permission notice shall be included in //all copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN //THE SOFTWARE. #include using namespace std; typedef double tppl_float; #define TPPL_CCW 1 #define TPPL_CW -1 //2D point structure struct TPPLPoint { tppl_float x; tppl_float y; TPPLPoint operator + (const TPPLPoint& p) const { TPPLPoint r; r.x = x + p.x; r.y = y + p.y; return r; } TPPLPoint operator - (const TPPLPoint& p) const { TPPLPoint r; r.x = x - p.x; r.y = y - p.y; return r; } TPPLPoint operator * (const tppl_float f ) const { TPPLPoint r; r.x = x*f; r.y = y*f; return r; } TPPLPoint operator / (const tppl_float f ) const { TPPLPoint r; r.x = x/f; r.y = y/f; return r; } bool operator==(const TPPLPoint& p) const { if((x == p.x)&&(y==p.y)) return true; else return false; } bool operator!=(const TPPLPoint& p) const { if((x == p.x)&&(y==p.y)) return false; else return true; } }; //Polygon implemented as an array of points with a 'hole' flag class TPPLPoly { protected: TPPLPoint *points; long numpoints; bool hole; public: //constructors/destructors TPPLPoly(); ~TPPLPoly(); TPPLPoly(const TPPLPoly &src); TPPLPoly& operator=(const TPPLPoly &src); //getters and setters long GetNumPoints() const { return numpoints; } bool IsHole() const { return hole; } void SetHole(bool hole) { this->hole = hole; } TPPLPoint &GetPoint(long i) { return points[i]; } TPPLPoint *GetPoints() { return points; } TPPLPoint& operator[] (int i) { return points[i]; } //clears the polygon points void Clear(); //inits the polygon with numpoints vertices void Init(long numpoints); //creates a triangle with points p1,p2,p3 void Triangle(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3); //inverts the orfer of vertices void Invert(); //returns the orientation of the polygon //possible values: // TPPL_CCW : polygon vertices are in counter-clockwise order // TPPL_CW : polygon vertices are in clockwise order // 0 : the polygon has no (measurable) area int GetOrientation() const; //sets the polygon orientation //orientation can be // TPPL_CCW : sets vertices in counter-clockwise order // TPPL_CW : sets vertices in clockwise order void SetOrientation(int orientation); }; class TPPLPartition { protected: struct PartitionVertex { bool isActive; bool isConvex; bool isEar; TPPLPoint p; tppl_float angle; PartitionVertex *previous; PartitionVertex *next; }; struct MonotoneVertex { TPPLPoint p; long previous; long next; }; class VertexSorter{ MonotoneVertex *vertices; public: VertexSorter(MonotoneVertex *v) : vertices(v) {} bool operator() (long index1, long index2) const; }; struct Diagonal { long index1; long index2; }; //dynamic programming state for minimum-weight triangulation struct DPState { bool visible; tppl_float weight; long bestvertex; }; //dynamic programming state for convex partitioning struct DPState2 { bool visible; long weight; list pairs; }; //edge that intersects the scanline struct ScanLineEdge { long index; TPPLPoint p1; TPPLPoint p2; //determines if the edge is to the left of another edge bool operator< (const ScanLineEdge & other) const; bool IsConvex(const TPPLPoint& p1, const TPPLPoint& p2, const TPPLPoint& p3) const; }; //standard helper functions bool IsConvex(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3); bool IsReflex(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3); bool IsInside(TPPLPoint& p1, TPPLPoint& p2, TPPLPoint& p3, TPPLPoint &p); bool InCone(TPPLPoint &p1, TPPLPoint &p2, TPPLPoint &p3, TPPLPoint &p); bool InCone(PartitionVertex *v, TPPLPoint &p); int Intersects(TPPLPoint &p11, TPPLPoint &p12, TPPLPoint &p21, TPPLPoint &p22); TPPLPoint Normalize(const TPPLPoint &p); tppl_float Distance(const TPPLPoint &p1, const TPPLPoint &p2); //helper functions for Triangulate_EC void UpdateVertexReflexity(PartitionVertex *v); void UpdateVertex(PartitionVertex *v,PartitionVertex *vertices, long numvertices); //helper functions for ConvexPartition_OPT void UpdateState(long a, long b, long w, long i, long j, DPState2 **dpstates); void TypeA(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates); void TypeB(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates); //helper functions for MonotonePartition bool Below(TPPLPoint &p1, TPPLPoint &p2); void AddDiagonal(MonotoneVertex *vertices, long *numvertices, long index1, long index2); //triangulates a monotone polygon, used in Triangulate_MONO int TriangulateMonotone(TPPLPoly *inPoly, list *triangles); public: //simple heuristic procedure for removing holes from a list of polygons //works by creating a diagonal from the rightmost hole vertex to some visible vertex //time complexity: O(h*(n^2)), h is the number of holes, n is the number of vertices //space complexity: O(n) //params: // inpolys : a list of polygons that can contain holes // vertices of all non-hole polys have to be in counter-clockwise order // vertices of all hole polys have to be in clockwise order // outpolys : a list of polygons without holes //returns 1 on success, 0 on failure int RemoveHoles(list *inpolys, list *outpolys); //triangulates a polygon by ear clipping //time complexity O(n^2), n is the number of vertices //space complexity: O(n) //params: // poly : an input polygon to be triangulated // vertices have to be in counter-clockwise order // triangles : a list of triangles (result) //returns 1 on success, 0 on failure int Triangulate_EC(TPPLPoly *poly, list *triangles); //triangulates a list of polygons that may contain holes by ear clipping algorithm //first calls RemoveHoles to get rid of the holes, and then Triangulate_EC for each resulting polygon //time complexity: O(h*(n^2)), h is the number of holes, n is the number of vertices //space complexity: O(n) //params: // inpolys : a list of polygons to be triangulated (can contain holes) // vertices of all non-hole polys have to be in counter-clockwise order // vertices of all hole polys have to be in clockwise order // triangles : a list of triangles (result) //returns 1 on success, 0 on failure int Triangulate_EC(list *inpolys, list *triangles); //creates an optimal polygon triangulation in terms of minimal edge length //time complexity: O(n^3), n is the number of vertices //space complexity: O(n^2) //params: // poly : an input polygon to be triangulated // vertices have to be in counter-clockwise order // triangles : a list of triangles (result) //returns 1 on success, 0 on failure int Triangulate_OPT(TPPLPoly *poly, list *triangles); //triangulates a polygons by firstly partitioning it into monotone polygons //time complexity: O(n*log(n)), n is the number of vertices //space complexity: O(n) //params: // poly : an input polygon to be triangulated // vertices have to be in counter-clockwise order // triangles : a list of triangles (result) //returns 1 on success, 0 on failure int Triangulate_MONO(TPPLPoly *poly, list *triangles); //triangulates a list of polygons by firstly partitioning them into monotone polygons //time complexity: O(n*log(n)), n is the number of vertices //space complexity: O(n) //params: // inpolys : a list of polygons to be triangulated (can contain holes) // vertices of all non-hole polys have to be in counter-clockwise order // vertices of all hole polys have to be in clockwise order // triangles : a list of triangles (result) //returns 1 on success, 0 on failure int Triangulate_MONO(list *inpolys, list *triangles); //creates a monotone partition of a list of polygons that can contain holes //time complexity: O(n*log(n)), n is the number of vertices //space complexity: O(n) //params: // inpolys : a list of polygons to be triangulated (can contain holes) // vertices of all non-hole polys have to be in counter-clockwise order // vertices of all hole polys have to be in clockwise order // monotonePolys : a list of monotone polygons (result) //returns 1 on success, 0 on failure int MonotonePartition(list *inpolys, list *monotonePolys); //partitions a polygon into convex polygons by using Hertel-Mehlhorn algorithm //the algorithm gives at most four times the number of parts as the optimal algorithm //however, in practice it works much better than that and often gives optimal partition //uses triangulation obtained by ear clipping as intermediate result //time complexity O(n^2), n is the number of vertices //space complexity: O(n) //params: // poly : an input polygon to be partitioned // vertices have to be in counter-clockwise order // parts : resulting list of convex polygons //returns 1 on success, 0 on failure int ConvexPartition_HM(TPPLPoly *poly, list *parts); //partitions a list of polygons into convex parts by using Hertel-Mehlhorn algorithm //the algorithm gives at most four times the number of parts as the optimal algorithm //however, in practice it works much better than that and often gives optimal partition //uses triangulation obtained by ear clipping as intermediate result //time complexity O(n^2), n is the number of vertices //space complexity: O(n) //params: // inpolys : an input list of polygons to be partitioned // vertices of all non-hole polys have to be in counter-clockwise order // vertices of all hole polys have to be in clockwise order // parts : resulting list of convex polygons //returns 1 on success, 0 on failure int ConvexPartition_HM(list *inpolys, list *parts); //optimal convex partitioning (in terms of number of resulting convex polygons) //using the Keil-Snoeyink algorithm //M. Keil, J. Snoeyink, "On the time bound for convex decomposition of simple polygons", 1998 //time complexity O(n^3), n is the number of vertices //space complexity: O(n^3) // poly : an input polygon to be partitioned // vertices have to be in counter-clockwise order // parts : resulting list of convex polygons //returns 1 on success, 0 on failure int ConvexPartition_OPT(TPPLPoly *poly, list *parts); }; Slic3r-version_1.39.1/xs/src/ppport.h000066400000000000000000005254061324354444700174550ustar00rootroot00000000000000#if 0 <<'SKIP'; #endif /* ---------------------------------------------------------------------- ppport.h -- Perl/Pollution/Portability Version 3.19 Automatically created by Devel::PPPort running under perl 5.010000. Do NOT edit this file directly! -- Edit PPPort_pm.PL and the includes in parts/inc/ instead. Use 'perldoc ppport.h' to view the documentation below. ---------------------------------------------------------------------- SKIP =pod =head1 NAME ppport.h - Perl/Pollution/Portability version 3.19 =head1 SYNOPSIS perl ppport.h [options] [source files] Searches current directory for files if no [source files] are given --help show short help --version show version --patch=file write one patch file with changes --copy=suffix write changed copies with suffix --diff=program use diff program and options --compat-version=version provide compatibility with Perl version --cplusplus accept C++ comments --quiet don't output anything except fatal errors --nodiag don't show diagnostics --nohints don't show hints --nochanges don't suggest changes --nofilter don't filter input files --strip strip all script and doc functionality from ppport.h --list-provided list provided API --list-unsupported list unsupported API --api-info=name show Perl API portability information =head1 COMPATIBILITY This version of F is designed to support operation with Perl installations back to 5.003, and has been tested up to 5.10.0. =head1 OPTIONS =head2 --help Display a brief usage summary. =head2 --version Display the version of F. =head2 --patch=I If this option is given, a single patch file will be created if any changes are suggested. This requires a working diff program to be installed on your system. =head2 --copy=I If this option is given, a copy of each file will be saved with the given suffix that contains the suggested changes. This does not require any external programs. Note that this does not automagially add a dot between the original filename and the suffix. If you want the dot, you have to include it in the option argument. If neither C<--patch> or C<--copy> are given, the default is to simply print the diffs for each file. This requires either C or a C program to be installed. =head2 --diff=I Manually set the diff program and options to use. The default is to use C, when installed, and output unified context diffs. =head2 --compat-version=I Tell F to check for compatibility with the given Perl version. The default is to check for compatibility with Perl version 5.003. You can use this option to reduce the output of F if you intend to be backward compatible only down to a certain Perl version. =head2 --cplusplus Usually, F will detect C++ style comments and replace them with C style comments for portability reasons. Using this option instructs F to leave C++ comments untouched. =head2 --quiet Be quiet. Don't print anything except fatal errors. =head2 --nodiag Don't output any diagnostic messages. Only portability alerts will be printed. =head2 --nohints Don't output any hints. Hints often contain useful portability notes. Warnings will still be displayed. =head2 --nochanges Don't suggest any changes. Only give diagnostic output and hints unless these are also deactivated. =head2 --nofilter Don't filter the list of input files. By default, files not looking like source code (i.e. not *.xs, *.c, *.cc, *.cpp or *.h) are skipped. =head2 --strip Strip all script and documentation functionality from F. This reduces the size of F dramatically and may be useful if you want to include F in smaller modules without increasing their distribution size too much. The stripped F will have a C<--unstrip> option that allows you to undo the stripping, but only if an appropriate C module is installed. =head2 --list-provided Lists the API elements for which compatibility is provided by F. Also lists if it must be explicitly requested, if it has dependencies, and if there are hints or warnings for it. =head2 --list-unsupported Lists the API elements that are known not to be supported by F and below which version of Perl they probably won't be available or work. =head2 --api-info=I Show portability information for API elements matching I. If I is surrounded by slashes, it is interpreted as a regular expression. =head1 DESCRIPTION In order for a Perl extension (XS) module to be as portable as possible across differing versions of Perl itself, certain steps need to be taken. =over 4 =item * Including this header is the first major one. This alone will give you access to a large part of the Perl API that hasn't been available in earlier Perl releases. Use perl ppport.h --list-provided to see which API elements are provided by ppport.h. =item * You should avoid using deprecated parts of the API. For example, using global Perl variables without the C prefix is deprecated. Also, some API functions used to have a C prefix. Using this form is also deprecated. You can safely use the supported API, as F will provide wrappers for older Perl versions. =item * If you use one of a few functions or variables that were not present in earlier versions of Perl, and that can't be provided using a macro, you have to explicitly request support for these functions by adding one or more C<#define>s in your source code before the inclusion of F. These functions or variables will be marked C in the list shown by C<--list-provided>. Depending on whether you module has a single or multiple files that use such functions or variables, you want either C or global variants. For a C function or variable (used only in a single source file), use: #define NEED_function #define NEED_variable For a global function or variable (used in multiple source files), use: #define NEED_function_GLOBAL #define NEED_variable_GLOBAL Note that you mustn't have more than one global request for the same function or variable in your project. Function / Variable Static Request Global Request ----------------------------------------------------------------------------------------- PL_parser NEED_PL_parser NEED_PL_parser_GLOBAL PL_signals NEED_PL_signals NEED_PL_signals_GLOBAL eval_pv() NEED_eval_pv NEED_eval_pv_GLOBAL grok_bin() NEED_grok_bin NEED_grok_bin_GLOBAL grok_hex() NEED_grok_hex NEED_grok_hex_GLOBAL grok_number() NEED_grok_number NEED_grok_number_GLOBAL grok_numeric_radix() NEED_grok_numeric_radix NEED_grok_numeric_radix_GLOBAL grok_oct() NEED_grok_oct NEED_grok_oct_GLOBAL load_module() NEED_load_module NEED_load_module_GLOBAL my_snprintf() NEED_my_snprintf NEED_my_snprintf_GLOBAL my_sprintf() NEED_my_sprintf NEED_my_sprintf_GLOBAL my_strlcat() NEED_my_strlcat NEED_my_strlcat_GLOBAL my_strlcpy() NEED_my_strlcpy NEED_my_strlcpy_GLOBAL newCONSTSUB() NEED_newCONSTSUB NEED_newCONSTSUB_GLOBAL newRV_noinc() NEED_newRV_noinc NEED_newRV_noinc_GLOBAL newSV_type() NEED_newSV_type NEED_newSV_type_GLOBAL newSVpvn_flags() NEED_newSVpvn_flags NEED_newSVpvn_flags_GLOBAL newSVpvn_share() NEED_newSVpvn_share NEED_newSVpvn_share_GLOBAL pv_display() NEED_pv_display NEED_pv_display_GLOBAL pv_escape() NEED_pv_escape NEED_pv_escape_GLOBAL pv_pretty() NEED_pv_pretty NEED_pv_pretty_GLOBAL sv_2pv_flags() NEED_sv_2pv_flags NEED_sv_2pv_flags_GLOBAL sv_2pvbyte() NEED_sv_2pvbyte NEED_sv_2pvbyte_GLOBAL sv_catpvf_mg() NEED_sv_catpvf_mg NEED_sv_catpvf_mg_GLOBAL sv_catpvf_mg_nocontext() NEED_sv_catpvf_mg_nocontext NEED_sv_catpvf_mg_nocontext_GLOBAL sv_pvn_force_flags() NEED_sv_pvn_force_flags NEED_sv_pvn_force_flags_GLOBAL sv_setpvf_mg() NEED_sv_setpvf_mg NEED_sv_setpvf_mg_GLOBAL sv_setpvf_mg_nocontext() NEED_sv_setpvf_mg_nocontext NEED_sv_setpvf_mg_nocontext_GLOBAL vload_module() NEED_vload_module NEED_vload_module_GLOBAL vnewSVpvf() NEED_vnewSVpvf NEED_vnewSVpvf_GLOBAL warner() NEED_warner NEED_warner_GLOBAL To avoid namespace conflicts, you can change the namespace of the explicitly exported functions / variables using the C macro. Just C<#define> the macro before including C: #define DPPP_NAMESPACE MyOwnNamespace_ #include "ppport.h" The default namespace is C. =back The good thing is that most of the above can be checked by running F on your source code. See the next section for details. =head1 EXAMPLES To verify whether F is needed for your module, whether you should make any changes to your code, and whether any special defines should be used, F can be run as a Perl script to check your source code. Simply say: perl ppport.h The result will usually be a list of patches suggesting changes that should at least be acceptable, if not necessarily the most efficient solution, or a fix for all possible problems. If you know that your XS module uses features only available in newer Perl releases, if you're aware that it uses C++ comments, and if you want all suggestions as a single patch file, you could use something like this: perl ppport.h --compat-version=5.6.0 --cplusplus --patch=test.diff If you only want your code to be scanned without any suggestions for changes, use: perl ppport.h --nochanges You can specify a different C program or options, using the C<--diff> option: perl ppport.h --diff='diff -C 10' This would output context diffs with 10 lines of context. If you want to create patched copies of your files instead, use: perl ppport.h --copy=.new To display portability information for the C function, use: perl ppport.h --api-info=newSVpvn Since the argument to C<--api-info> can be a regular expression, you can use perl ppport.h --api-info=/_nomg$/ to display portability information for all C<_nomg> functions or perl ppport.h --api-info=/./ to display information for all known API elements. =head1 BUGS If this version of F is causing failure during the compilation of this module, please check if newer versions of either this module or C are available on CPAN before sending a bug report. If F was generated using the latest version of C and is causing failure of this module, please file a bug report using the CPAN Request Tracker at L. Please include the following information: =over 4 =item 1. The complete output from running "perl -V" =item 2. This file. =item 3. The name and version of the module you were trying to build. =item 4. A full log of the build that failed. =item 5. Any other information that you think could be relevant. =back For the latest version of this code, please get the C module from CPAN. =head1 COPYRIGHT Version 3.x, Copyright (c) 2004-2009, Marcus Holland-Moritz. Version 2.x, Copyright (C) 2001, Paul Marquess. Version 1.x, Copyright (C) 1999, Kenneth Albanowski. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO See L. =cut use strict; # Disable broken TRIE-optimization BEGIN { eval '${^RE_TRIE_MAXBUF} = -1' if $] >= 5.009004 && $] <= 5.009005 } my $VERSION = 3.19; my %opt = ( quiet => 0, diag => 1, hints => 1, changes => 1, cplusplus => 0, filter => 1, strip => 0, version => 0, ); my($ppport) = $0 =~ /([\w.]+)$/; my $LF = '(?:\r\n|[\r\n])'; # line feed my $HS = "[ \t]"; # horizontal whitespace # Never use C comments in this file! my $ccs = '/'.'*'; my $cce = '*'.'/'; my $rccs = quotemeta $ccs; my $rcce = quotemeta $cce; eval { require Getopt::Long; Getopt::Long::GetOptions(\%opt, qw( help quiet diag! filter! hints! changes! cplusplus strip version patch=s copy=s diff=s compat-version=s list-provided list-unsupported api-info=s )) or usage(); }; if ($@ and grep /^-/, @ARGV) { usage() if "@ARGV" =~ /^--?h(?:elp)?$/; die "Getopt::Long not found. Please don't use any options.\n"; } if ($opt{version}) { print "This is $0 $VERSION.\n"; exit 0; } usage() if $opt{help}; strip() if $opt{strip}; if (exists $opt{'compat-version'}) { my($r,$v,$s) = eval { parse_version($opt{'compat-version'}) }; if ($@) { die "Invalid version number format: '$opt{'compat-version'}'\n"; } die "Only Perl 5 is supported\n" if $r != 5; die "Invalid version number: $opt{'compat-version'}\n" if $v >= 1000 || $s >= 1000; $opt{'compat-version'} = sprintf "%d.%03d%03d", $r, $v, $s; } else { $opt{'compat-version'} = 5; } my %API = map { /^(\w+)\|([^|]*)\|([^|]*)\|(\w*)$/ ? ( $1 => { ($2 ? ( base => $2 ) : ()), ($3 ? ( todo => $3 ) : ()), (index($4, 'v') >= 0 ? ( varargs => 1 ) : ()), (index($4, 'p') >= 0 ? ( provided => 1 ) : ()), (index($4, 'n') >= 0 ? ( nothxarg => 1 ) : ()), } ) : die "invalid spec: $_" } qw( AvFILLp|5.004050||p AvFILL||| CLASS|||n CPERLscope|5.005000||p CX_CURPAD_SAVE||| CX_CURPAD_SV||| CopFILEAV|5.006000||p CopFILEGV_set|5.006000||p CopFILEGV|5.006000||p CopFILESV|5.006000||p CopFILE_set|5.006000||p CopFILE|5.006000||p CopSTASHPV_set|5.006000||p CopSTASHPV|5.006000||p CopSTASH_eq|5.006000||p CopSTASH_set|5.006000||p CopSTASH|5.006000||p CopyD|5.009002||p Copy||| CvPADLIST||| CvSTASH||| CvWEAKOUTSIDE||| DEFSV_set|5.011000||p DEFSV|5.004050||p END_EXTERN_C|5.005000||p ENTER||| ERRSV|5.004050||p EXTEND||| EXTERN_C|5.005000||p F0convert|||n FREETMPS||| GIMME_V||5.004000|n GIMME|||n GROK_NUMERIC_RADIX|5.007002||p G_ARRAY||| G_DISCARD||| G_EVAL||| G_METHOD|5.006001||p G_NOARGS||| G_SCALAR||| G_VOID||5.004000| GetVars||| GvSVn|5.009003||p GvSV||| Gv_AMupdate||| HEf_SVKEY||5.004000| HeHASH||5.004000| HeKEY||5.004000| HeKLEN||5.004000| HePV||5.004000| HeSVKEY_force||5.004000| HeSVKEY_set||5.004000| HeSVKEY||5.004000| HeUTF8||5.011000| HeVAL||5.004000| HvNAMELEN_get|5.009003||p HvNAME_get|5.009003||p HvNAME||| INT2PTR|5.006000||p IN_LOCALE_COMPILETIME|5.007002||p IN_LOCALE_RUNTIME|5.007002||p IN_LOCALE|5.007002||p IN_PERL_COMPILETIME|5.008001||p IS_NUMBER_GREATER_THAN_UV_MAX|5.007002||p IS_NUMBER_INFINITY|5.007002||p IS_NUMBER_IN_UV|5.007002||p IS_NUMBER_NAN|5.007003||p IS_NUMBER_NEG|5.007002||p IS_NUMBER_NOT_INT|5.007002||p IVSIZE|5.006000||p IVTYPE|5.006000||p IVdf|5.006000||p LEAVE||| LVRET||| MARK||| MULTICALL||5.011000| MY_CXT_CLONE|5.009002||p MY_CXT_INIT|5.007003||p MY_CXT|5.007003||p MoveD|5.009002||p Move||| NOOP|5.005000||p NUM2PTR|5.006000||p NVTYPE|5.006000||p NVef|5.006001||p NVff|5.006001||p NVgf|5.006001||p Newxc|5.009003||p Newxz|5.009003||p Newx|5.009003||p Nullav||| Nullch||| Nullcv||| Nullhv||| Nullsv||| ORIGMARK||| PAD_BASE_SV||| PAD_CLONE_VARS||| PAD_COMPNAME_FLAGS||| PAD_COMPNAME_GEN_set||| PAD_COMPNAME_GEN||| PAD_COMPNAME_OURSTASH||| PAD_COMPNAME_PV||| PAD_COMPNAME_TYPE||| PAD_DUP||| PAD_RESTORE_LOCAL||| PAD_SAVE_LOCAL||| PAD_SAVE_SETNULLPAD||| PAD_SETSV||| PAD_SET_CUR_NOSAVE||| PAD_SET_CUR||| PAD_SVl||| PAD_SV||| PERLIO_FUNCS_CAST|5.009003||p PERLIO_FUNCS_DECL|5.009003||p PERL_ABS|5.008001||p PERL_BCDVERSION|5.011000||p PERL_GCC_BRACE_GROUPS_FORBIDDEN|5.008001||p PERL_HASH|5.004000||p PERL_INT_MAX|5.004000||p PERL_INT_MIN|5.004000||p PERL_LONG_MAX|5.004000||p PERL_LONG_MIN|5.004000||p PERL_MAGIC_arylen|5.007002||p PERL_MAGIC_backref|5.007002||p PERL_MAGIC_bm|5.007002||p PERL_MAGIC_collxfrm|5.007002||p PERL_MAGIC_dbfile|5.007002||p PERL_MAGIC_dbline|5.007002||p PERL_MAGIC_defelem|5.007002||p PERL_MAGIC_envelem|5.007002||p PERL_MAGIC_env|5.007002||p PERL_MAGIC_ext|5.007002||p PERL_MAGIC_fm|5.007002||p PERL_MAGIC_glob|5.011000||p PERL_MAGIC_isaelem|5.007002||p PERL_MAGIC_isa|5.007002||p PERL_MAGIC_mutex|5.011000||p PERL_MAGIC_nkeys|5.007002||p PERL_MAGIC_overload_elem|5.007002||p PERL_MAGIC_overload_table|5.007002||p PERL_MAGIC_overload|5.007002||p PERL_MAGIC_pos|5.007002||p PERL_MAGIC_qr|5.007002||p PERL_MAGIC_regdata|5.007002||p PERL_MAGIC_regdatum|5.007002||p PERL_MAGIC_regex_global|5.007002||p PERL_MAGIC_shared_scalar|5.007003||p PERL_MAGIC_shared|5.007003||p PERL_MAGIC_sigelem|5.007002||p PERL_MAGIC_sig|5.007002||p PERL_MAGIC_substr|5.007002||p PERL_MAGIC_sv|5.007002||p PERL_MAGIC_taint|5.007002||p PERL_MAGIC_tiedelem|5.007002||p PERL_MAGIC_tiedscalar|5.007002||p PERL_MAGIC_tied|5.007002||p PERL_MAGIC_utf8|5.008001||p PERL_MAGIC_uvar_elem|5.007003||p PERL_MAGIC_uvar|5.007002||p PERL_MAGIC_vec|5.007002||p PERL_MAGIC_vstring|5.008001||p PERL_PV_ESCAPE_ALL|5.009004||p PERL_PV_ESCAPE_FIRSTCHAR|5.009004||p PERL_PV_ESCAPE_NOBACKSLASH|5.009004||p PERL_PV_ESCAPE_NOCLEAR|5.009004||p PERL_PV_ESCAPE_QUOTE|5.009004||p PERL_PV_ESCAPE_RE|5.009005||p PERL_PV_ESCAPE_UNI_DETECT|5.009004||p PERL_PV_ESCAPE_UNI|5.009004||p PERL_PV_PRETTY_DUMP|5.009004||p PERL_PV_PRETTY_ELLIPSES|5.010000||p PERL_PV_PRETTY_LTGT|5.009004||p PERL_PV_PRETTY_NOCLEAR|5.010000||p PERL_PV_PRETTY_QUOTE|5.009004||p PERL_PV_PRETTY_REGPROP|5.009004||p PERL_QUAD_MAX|5.004000||p PERL_QUAD_MIN|5.004000||p PERL_REVISION|5.006000||p PERL_SCAN_ALLOW_UNDERSCORES|5.007003||p PERL_SCAN_DISALLOW_PREFIX|5.007003||p PERL_SCAN_GREATER_THAN_UV_MAX|5.007003||p PERL_SCAN_SILENT_ILLDIGIT|5.008001||p PERL_SHORT_MAX|5.004000||p PERL_SHORT_MIN|5.004000||p PERL_SIGNALS_UNSAFE_FLAG|5.008001||p PERL_SUBVERSION|5.006000||p PERL_SYS_INIT3||5.006000| PERL_SYS_INIT||| PERL_SYS_TERM||5.011000| PERL_UCHAR_MAX|5.004000||p PERL_UCHAR_MIN|5.004000||p PERL_UINT_MAX|5.004000||p PERL_UINT_MIN|5.004000||p PERL_ULONG_MAX|5.004000||p PERL_ULONG_MIN|5.004000||p PERL_UNUSED_ARG|5.009003||p PERL_UNUSED_CONTEXT|5.009004||p PERL_UNUSED_DECL|5.007002||p PERL_UNUSED_VAR|5.007002||p PERL_UQUAD_MAX|5.004000||p PERL_UQUAD_MIN|5.004000||p PERL_USE_GCC_BRACE_GROUPS|5.009004||p PERL_USHORT_MAX|5.004000||p PERL_USHORT_MIN|5.004000||p PERL_VERSION|5.006000||p PL_DBsignal|5.005000||p PL_DBsingle|||pn PL_DBsub|||pn PL_DBtrace|||pn PL_Sv|5.005000||p PL_bufend|5.011000||p PL_bufptr|5.011000||p PL_compiling|5.004050||p PL_copline|5.011000||p PL_curcop|5.004050||p PL_curstash|5.004050||p PL_debstash|5.004050||p PL_defgv|5.004050||p PL_diehook|5.004050||p PL_dirty|5.004050||p PL_dowarn|||pn PL_errgv|5.004050||p PL_error_count|5.011000||p PL_expect|5.011000||p PL_hexdigit|5.005000||p PL_hints|5.005000||p PL_in_my_stash|5.011000||p PL_in_my|5.011000||p PL_last_in_gv|||n PL_laststatval|5.005000||p PL_lex_state|5.011000||p PL_lex_stuff|5.011000||p PL_linestr|5.011000||p PL_modglobal||5.005000|n PL_na|5.004050||pn PL_no_modify|5.006000||p PL_ofsgv|||n PL_parser|5.009005||p PL_perl_destruct_level|5.004050||p PL_perldb|5.004050||p PL_ppaddr|5.006000||p PL_rsfp_filters|5.004050||p PL_rsfp|5.004050||p PL_rs|||n PL_signals|5.008001||p PL_stack_base|5.004050||p PL_stack_sp|5.004050||p PL_statcache|5.005000||p PL_stdingv|5.004050||p PL_sv_arenaroot|5.004050||p PL_sv_no|5.004050||pn PL_sv_undef|5.004050||pn PL_sv_yes|5.004050||pn PL_tainted|5.004050||p PL_tainting|5.004050||p PL_tokenbuf|5.011000||p POP_MULTICALL||5.011000| POPi|||n POPl|||n POPn|||n POPpbytex||5.007001|n POPpx||5.005030|n POPp|||n POPs|||n PTR2IV|5.006000||p PTR2NV|5.006000||p PTR2UV|5.006000||p PTR2nat|5.009003||p PTR2ul|5.007001||p PTRV|5.006000||p PUSHMARK||| PUSH_MULTICALL||5.011000| PUSHi||| PUSHmortal|5.009002||p PUSHn||| PUSHp||| PUSHs||| PUSHu|5.004000||p PUTBACK||| PerlIO_clearerr||5.007003| PerlIO_close||5.007003| PerlIO_context_layers||5.009004| PerlIO_eof||5.007003| PerlIO_error||5.007003| PerlIO_fileno||5.007003| PerlIO_fill||5.007003| PerlIO_flush||5.007003| PerlIO_get_base||5.007003| PerlIO_get_bufsiz||5.007003| PerlIO_get_cnt||5.007003| PerlIO_get_ptr||5.007003| PerlIO_read||5.007003| PerlIO_seek||5.007003| PerlIO_set_cnt||5.007003| PerlIO_set_ptrcnt||5.007003| PerlIO_setlinebuf||5.007003| PerlIO_stderr||5.007003| PerlIO_stdin||5.007003| PerlIO_stdout||5.007003| PerlIO_tell||5.007003| PerlIO_unread||5.007003| PerlIO_write||5.007003| Perl_signbit||5.009005|n PoisonFree|5.009004||p PoisonNew|5.009004||p PoisonWith|5.009004||p Poison|5.008000||p RETVAL|||n Renewc||| Renew||| SAVECLEARSV||| SAVECOMPPAD||| SAVEPADSV||| SAVETMPS||| SAVE_DEFSV|5.004050||p SPAGAIN||| SP||| START_EXTERN_C|5.005000||p START_MY_CXT|5.007003||p STMT_END|||p STMT_START|||p STR_WITH_LEN|5.009003||p ST||| SV_CONST_RETURN|5.009003||p SV_COW_DROP_PV|5.008001||p SV_COW_SHARED_HASH_KEYS|5.009005||p SV_GMAGIC|5.007002||p SV_HAS_TRAILING_NUL|5.009004||p SV_IMMEDIATE_UNREF|5.007001||p SV_MUTABLE_RETURN|5.009003||p SV_NOSTEAL|5.009002||p SV_SMAGIC|5.009003||p SV_UTF8_NO_ENCODING|5.008001||p SVfARG|5.009005||p SVf_UTF8|5.006000||p SVf|5.006000||p SVt_IV||| SVt_NV||| SVt_PVAV||| SVt_PVCV||| SVt_PVHV||| SVt_PVMG||| SVt_PV||| Safefree||| Slab_Alloc||| Slab_Free||| Slab_to_rw||| StructCopy||| SvCUR_set||| SvCUR||| SvEND||| SvGAMAGIC||5.006001| SvGETMAGIC|5.004050||p SvGROW||| SvIOK_UV||5.006000| SvIOK_notUV||5.006000| SvIOK_off||| SvIOK_only_UV||5.006000| SvIOK_only||| SvIOK_on||| SvIOKp||| SvIOK||| SvIVX||| SvIV_nomg|5.009001||p SvIV_set||| SvIVx||| SvIV||| SvIsCOW_shared_hash||5.008003| SvIsCOW||5.008003| SvLEN_set||| SvLEN||| SvLOCK||5.007003| SvMAGIC_set|5.009003||p SvNIOK_off||| SvNIOKp||| SvNIOK||| SvNOK_off||| SvNOK_only||| SvNOK_on||| SvNOKp||| SvNOK||| SvNVX||| SvNV_set||| SvNVx||| SvNV||| SvOK||| SvOOK_offset||5.011000| SvOOK||| SvPOK_off||| SvPOK_only_UTF8||5.006000| SvPOK_only||| SvPOK_on||| SvPOKp||| SvPOK||| SvPVX_const|5.009003||p SvPVX_mutable|5.009003||p SvPVX||| SvPV_const|5.009003||p SvPV_flags_const_nolen|5.009003||p SvPV_flags_const|5.009003||p SvPV_flags_mutable|5.009003||p SvPV_flags|5.007002||p SvPV_force_flags_mutable|5.009003||p SvPV_force_flags_nolen|5.009003||p SvPV_force_flags|5.007002||p SvPV_force_mutable|5.009003||p SvPV_force_nolen|5.009003||p SvPV_force_nomg_nolen|5.009003||p SvPV_force_nomg|5.007002||p SvPV_force|||p SvPV_mutable|5.009003||p SvPV_nolen_const|5.009003||p SvPV_nolen|5.006000||p SvPV_nomg_const_nolen|5.009003||p SvPV_nomg_const|5.009003||p SvPV_nomg|5.007002||p SvPV_renew|5.009003||p SvPV_set||| SvPVbyte_force||5.009002| SvPVbyte_nolen||5.006000| SvPVbytex_force||5.006000| SvPVbytex||5.006000| SvPVbyte|5.006000||p SvPVutf8_force||5.006000| SvPVutf8_nolen||5.006000| SvPVutf8x_force||5.006000| SvPVutf8x||5.006000| SvPVutf8||5.006000| SvPVx||| SvPV||| SvREFCNT_dec||| SvREFCNT_inc_NN|5.009004||p SvREFCNT_inc_simple_NN|5.009004||p SvREFCNT_inc_simple_void_NN|5.009004||p SvREFCNT_inc_simple_void|5.009004||p SvREFCNT_inc_simple|5.009004||p SvREFCNT_inc_void_NN|5.009004||p SvREFCNT_inc_void|5.009004||p SvREFCNT_inc|||p SvREFCNT||| SvROK_off||| SvROK_on||| SvROK||| SvRV_set|5.009003||p SvRV||| SvRXOK||5.009005| SvRX||5.009005| SvSETMAGIC||| SvSHARED_HASH|5.009003||p SvSHARE||5.007003| SvSTASH_set|5.009003||p SvSTASH||| SvSetMagicSV_nosteal||5.004000| SvSetMagicSV||5.004000| SvSetSV_nosteal||5.004000| SvSetSV||| SvTAINTED_off||5.004000| SvTAINTED_on||5.004000| SvTAINTED||5.004000| SvTAINT||| SvTRUE||| SvTYPE||| SvUNLOCK||5.007003| SvUOK|5.007001|5.006000|p SvUPGRADE||| SvUTF8_off||5.006000| SvUTF8_on||5.006000| SvUTF8||5.006000| SvUVXx|5.004000||p SvUVX|5.004000||p SvUV_nomg|5.009001||p SvUV_set|5.009003||p SvUVx|5.004000||p SvUV|5.004000||p SvVOK||5.008001| SvVSTRING_mg|5.009004||p THIS|||n UNDERBAR|5.009002||p UTF8_MAXBYTES|5.009002||p UVSIZE|5.006000||p UVTYPE|5.006000||p UVXf|5.007001||p UVof|5.006000||p UVuf|5.006000||p UVxf|5.006000||p WARN_ALL|5.006000||p WARN_AMBIGUOUS|5.006000||p WARN_ASSERTIONS|5.011000||p WARN_BAREWORD|5.006000||p WARN_CLOSED|5.006000||p WARN_CLOSURE|5.006000||p WARN_DEBUGGING|5.006000||p WARN_DEPRECATED|5.006000||p WARN_DIGIT|5.006000||p WARN_EXEC|5.006000||p WARN_EXITING|5.006000||p WARN_GLOB|5.006000||p WARN_INPLACE|5.006000||p WARN_INTERNAL|5.006000||p WARN_IO|5.006000||p WARN_LAYER|5.008000||p WARN_MALLOC|5.006000||p WARN_MISC|5.006000||p WARN_NEWLINE|5.006000||p WARN_NUMERIC|5.006000||p WARN_ONCE|5.006000||p WARN_OVERFLOW|5.006000||p WARN_PACK|5.006000||p WARN_PARENTHESIS|5.006000||p WARN_PIPE|5.006000||p WARN_PORTABLE|5.006000||p WARN_PRECEDENCE|5.006000||p WARN_PRINTF|5.006000||p WARN_PROTOTYPE|5.006000||p WARN_QW|5.006000||p WARN_RECURSION|5.006000||p WARN_REDEFINE|5.006000||p WARN_REGEXP|5.006000||p WARN_RESERVED|5.006000||p WARN_SEMICOLON|5.006000||p WARN_SEVERE|5.006000||p WARN_SIGNAL|5.006000||p WARN_SUBSTR|5.006000||p WARN_SYNTAX|5.006000||p WARN_TAINT|5.006000||p WARN_THREADS|5.008000||p WARN_UNINITIALIZED|5.006000||p WARN_UNOPENED|5.006000||p WARN_UNPACK|5.006000||p WARN_UNTIE|5.006000||p WARN_UTF8|5.006000||p WARN_VOID|5.006000||p XCPT_CATCH|5.009002||p XCPT_RETHROW|5.009002||p XCPT_TRY_END|5.009002||p XCPT_TRY_START|5.009002||p XPUSHi||| XPUSHmortal|5.009002||p XPUSHn||| XPUSHp||| XPUSHs||| XPUSHu|5.004000||p XSPROTO|5.010000||p XSRETURN_EMPTY||| XSRETURN_IV||| XSRETURN_NO||| XSRETURN_NV||| XSRETURN_PV||| XSRETURN_UNDEF||| XSRETURN_UV|5.008001||p XSRETURN_YES||| XSRETURN|||p XST_mIV||| XST_mNO||| XST_mNV||| XST_mPV||| XST_mUNDEF||| XST_mUV|5.008001||p XST_mYES||| XS_VERSION_BOOTCHECK||| XS_VERSION||| XSprePUSH|5.006000||p XS||| ZeroD|5.009002||p Zero||| _aMY_CXT|5.007003||p _pMY_CXT|5.007003||p aMY_CXT_|5.007003||p aMY_CXT|5.007003||p aTHXR_|5.011000||p aTHXR|5.011000||p aTHX_|5.006000||p aTHX|5.006000||p add_data|||n addmad||| allocmy||| amagic_call||| amagic_cmp_locale||| amagic_cmp||| amagic_i_ncmp||| amagic_ncmp||| any_dup||| ao||| append_elem||| append_list||| append_madprops||| apply_attrs_my||| apply_attrs_string||5.006001| apply_attrs||| apply||| atfork_lock||5.007003|n atfork_unlock||5.007003|n av_arylen_p||5.009003| av_clear||| av_create_and_push||5.009005| av_create_and_unshift_one||5.009005| av_delete||5.006000| av_exists||5.006000| av_extend||| av_fetch||| av_fill||| av_iter_p||5.011000| av_len||| av_make||| av_pop||| av_push||| av_reify||| av_shift||| av_store||| av_undef||| av_unshift||| ax|||n bad_type||| bind_match||| block_end||| block_gimme||5.004000| block_start||| boolSV|5.004000||p boot_core_PerlIO||| boot_core_UNIVERSAL||| boot_core_mro||| bytes_from_utf8||5.007001| bytes_to_uni|||n bytes_to_utf8||5.006001| call_argv|5.006000||p call_atexit||5.006000| call_list||5.004000| call_method|5.006000||p call_pv|5.006000||p call_sv|5.006000||p calloc||5.007002|n cando||| cast_i32||5.006000| cast_iv||5.006000| cast_ulong||5.006000| cast_uv||5.006000| check_type_and_open||| check_uni||| checkcomma||| checkposixcc||| ckWARN|5.006000||p ck_anoncode||| ck_bitop||| ck_concat||| ck_defined||| ck_delete||| ck_die||| ck_each||| ck_eof||| ck_eval||| ck_exec||| ck_exists||| ck_exit||| ck_ftst||| ck_fun||| ck_glob||| ck_grep||| ck_index||| ck_join||| ck_lfun||| ck_listiob||| ck_match||| ck_method||| ck_null||| ck_open||| ck_readline||| ck_repeat||| ck_require||| ck_return||| ck_rfun||| ck_rvconst||| ck_sassign||| ck_select||| ck_shift||| ck_sort||| ck_spair||| ck_split||| ck_subr||| ck_substr||| ck_svconst||| ck_trunc||| ck_unpack||| ckwarn_d||5.009003| ckwarn||5.009003| cl_and|||n cl_anything|||n cl_init_zero|||n cl_init|||n cl_is_anything|||n cl_or|||n clear_placeholders||| closest_cop||| convert||| cop_free||| cr_textfilter||| create_eval_scope||| croak_nocontext|||vn croak_xs_usage||5.011000| croak|||v csighandler||5.009003|n curmad||| custom_op_desc||5.007003| custom_op_name||5.007003| cv_ckproto_len||| cv_clone||| cv_const_sv||5.004000| cv_dump||| cv_undef||| cx_dump||5.005000| cx_dup||| cxinc||| dAXMARK|5.009003||p dAX|5.007002||p dITEMS|5.007002||p dMARK||| dMULTICALL||5.009003| dMY_CXT_SV|5.007003||p dMY_CXT|5.007003||p dNOOP|5.006000||p dORIGMARK||| dSP||| dTHR|5.004050||p dTHXR|5.011000||p dTHXa|5.006000||p dTHXoa|5.006000||p dTHX|5.006000||p dUNDERBAR|5.009002||p dVAR|5.009003||p dXCPT|5.009002||p dXSARGS||| dXSI32||| dXSTARG|5.006000||p deb_curcv||| deb_nocontext|||vn deb_stack_all||| deb_stack_n||| debop||5.005000| debprofdump||5.005000| debprof||| debstackptrs||5.007003| debstack||5.007003| debug_start_match||| deb||5.007003|v del_sv||| delete_eval_scope||| delimcpy||5.004000| deprecate_old||| deprecate||| despatch_signals||5.007001| destroy_matcher||| die_nocontext|||vn die_where||| die|||v dirp_dup||| div128||| djSP||| do_aexec5||| do_aexec||| do_aspawn||| do_binmode||5.004050| do_chomp||| do_chop||| do_close||| do_dump_pad||| do_eof||| do_exec3||| do_execfree||| do_exec||| do_gv_dump||5.006000| do_gvgv_dump||5.006000| do_hv_dump||5.006000| do_ipcctl||| do_ipcget||| do_join||| do_kv||| do_magic_dump||5.006000| do_msgrcv||| do_msgsnd||| do_oddball||| do_op_dump||5.006000| do_op_xmldump||| do_open9||5.006000| do_openn||5.007001| do_open||5.004000| do_pmop_dump||5.006000| do_pmop_xmldump||| do_print||| do_readline||| do_seek||| do_semop||| do_shmio||| do_smartmatch||| do_spawn_nowait||| do_spawn||| do_sprintf||| do_sv_dump||5.006000| do_sysseek||| do_tell||| do_trans_complex_utf8||| do_trans_complex||| do_trans_count_utf8||| do_trans_count||| do_trans_simple_utf8||| do_trans_simple||| do_trans||| do_vecget||| do_vecset||| do_vop||| docatch||| doeval||| dofile||| dofindlabel||| doform||| doing_taint||5.008001|n dooneliner||| doopen_pm||| doparseform||| dopoptoeval||| dopoptogiven||| dopoptolabel||| dopoptoloop||| dopoptosub_at||| dopoptowhen||| doref||5.009003| dounwind||| dowantarray||| dump_all||5.006000| dump_eval||5.006000| dump_exec_pos||| dump_fds||| dump_form||5.006000| dump_indent||5.006000|v dump_mstats||| dump_packsubs||5.006000| dump_sub||5.006000| dump_sv_child||| dump_trie_interim_list||| dump_trie_interim_table||| dump_trie||| dump_vindent||5.006000| dumpuntil||| dup_attrlist||| emulate_cop_io||| eval_pv|5.006000||p eval_sv|5.006000||p exec_failed||| expect_number||| fbm_compile||5.005000| fbm_instr||5.005000| feature_is_enabled||| fetch_cop_label||5.011000| filter_add||| filter_del||| filter_gets||| filter_read||| find_and_forget_pmops||| find_array_subscript||| find_beginning||| find_byclass||| find_hash_subscript||| find_in_my_stash||| find_runcv||5.008001| find_rundefsvoffset||5.009002| find_script||| find_uninit_var||| first_symbol|||n fold_constants||| forbid_setid||| force_ident||| force_list||| force_next||| force_version||| force_word||| forget_pmop||| form_nocontext|||vn form||5.004000|v fp_dup||| fprintf_nocontext|||vn free_global_struct||| free_tied_hv_pool||| free_tmps||| gen_constant_list||| get_arena||| get_aux_mg||| get_av|5.006000||p get_context||5.006000|n get_cvn_flags||5.009005| get_cv|5.006000||p get_db_sub||| get_debug_opts||| get_hash_seed||| get_hv|5.006000||p get_isa_hash||| get_mstats||| get_no_modify||| get_num||| get_op_descs||5.005000| get_op_names||5.005000| get_opargs||| get_ppaddr||5.006000| get_re_arg||| get_sv|5.006000||p get_vtbl||5.005030| getcwd_sv||5.007002| getenv_len||| glob_2number||| glob_assign_glob||| glob_assign_ref||| gp_dup||| gp_free||| gp_ref||| grok_bin|5.007003||p grok_hex|5.007003||p grok_number|5.007002||p grok_numeric_radix|5.007002||p grok_oct|5.007003||p group_end||| gv_AVadd||| gv_HVadd||| gv_IOadd||| gv_SVadd||| gv_autoload4||5.004000| gv_check||| gv_const_sv||5.009003| gv_dump||5.006000| gv_efullname3||5.004000| gv_efullname4||5.006001| gv_efullname||| gv_ename||| gv_fetchfile_flags||5.009005| gv_fetchfile||| gv_fetchmeth_autoload||5.007003| gv_fetchmethod_autoload||5.004000| gv_fetchmethod_flags||5.011000| gv_fetchmethod||| gv_fetchmeth||| gv_fetchpvn_flags|5.009002||p gv_fetchpvs|5.009004||p gv_fetchpv||| gv_fetchsv||5.009002| gv_fullname3||5.004000| gv_fullname4||5.006001| gv_fullname||| gv_get_super_pkg||| gv_handler||5.007001| gv_init_sv||| gv_init||| gv_name_set||5.009004| gv_stashpvn|5.004000||p gv_stashpvs|5.009003||p gv_stashpv||| gv_stashsv||| he_dup||| hek_dup||| hfreeentries||| hsplit||| hv_assert||5.011000| hv_auxinit|||n hv_backreferences_p||| hv_clear_placeholders||5.009001| hv_clear||| hv_common_key_len||5.010000| hv_common||5.010000| hv_copy_hints_hv||| hv_delayfree_ent||5.004000| hv_delete_common||| hv_delete_ent||5.004000| hv_delete||| hv_eiter_p||5.009003| hv_eiter_set||5.009003| hv_exists_ent||5.004000| hv_exists||| hv_fetch_ent||5.004000| hv_fetchs|5.009003||p hv_fetch||| hv_free_ent||5.004000| hv_iterinit||| hv_iterkeysv||5.004000| hv_iterkey||| hv_iternext_flags||5.008000| hv_iternextsv||| hv_iternext||| hv_iterval||| hv_kill_backrefs||| hv_ksplit||5.004000| hv_magic_check|||n hv_magic||| hv_name_set||5.009003| hv_notallowed||| hv_placeholders_get||5.009003| hv_placeholders_p||5.009003| hv_placeholders_set||5.009003| hv_riter_p||5.009003| hv_riter_set||5.009003| hv_scalar||5.009001| hv_store_ent||5.004000| hv_store_flags||5.008000| hv_stores|5.009004||p hv_store||| hv_undef||| ibcmp_locale||5.004000| ibcmp_utf8||5.007003| ibcmp||| incline||| incpush_if_exists||| incpush_use_sep||| incpush||| ingroup||| init_argv_symbols||| init_debugger||| init_global_struct||| init_i18nl10n||5.006000| init_i18nl14n||5.006000| init_ids||| init_interp||| init_main_stash||| init_perllib||| init_postdump_symbols||| init_predump_symbols||| init_stacks||5.005000| init_tm||5.007002| instr||| intro_my||| intuit_method||| intuit_more||| invert||| io_close||| isALNUMC|5.006000||p isALNUM||| isALPHA||| isASCII|5.006000||p isBLANK|5.006001||p isCNTRL|5.006000||p isDIGIT||| isGRAPH|5.006000||p isGV_with_GP|5.009004||p isLOWER||| isPRINT|5.004000||p isPSXSPC|5.006001||p isPUNCT|5.006000||p isSPACE||| isUPPER||| isXDIGIT|5.006000||p is_an_int||| is_gv_magical_sv||| is_handle_constructor|||n is_list_assignment||| is_lvalue_sub||5.007001| is_uni_alnum_lc||5.006000| is_uni_alnumc_lc||5.006000| is_uni_alnumc||5.006000| is_uni_alnum||5.006000| is_uni_alpha_lc||5.006000| is_uni_alpha||5.006000| is_uni_ascii_lc||5.006000| is_uni_ascii||5.006000| is_uni_cntrl_lc||5.006000| is_uni_cntrl||5.006000| is_uni_digit_lc||5.006000| is_uni_digit||5.006000| is_uni_graph_lc||5.006000| is_uni_graph||5.006000| is_uni_idfirst_lc||5.006000| is_uni_idfirst||5.006000| is_uni_lower_lc||5.006000| is_uni_lower||5.006000| is_uni_print_lc||5.006000| is_uni_print||5.006000| is_uni_punct_lc||5.006000| is_uni_punct||5.006000| is_uni_space_lc||5.006000| is_uni_space||5.006000| is_uni_upper_lc||5.006000| is_uni_upper||5.006000| is_uni_xdigit_lc||5.006000| is_uni_xdigit||5.006000| is_utf8_alnumc||5.006000| is_utf8_alnum||5.006000| is_utf8_alpha||5.006000| is_utf8_ascii||5.006000| is_utf8_char_slow|||n is_utf8_char||5.006000| is_utf8_cntrl||5.006000| is_utf8_common||| is_utf8_digit||5.006000| is_utf8_graph||5.006000| is_utf8_idcont||5.008000| is_utf8_idfirst||5.006000| is_utf8_lower||5.006000| is_utf8_mark||5.006000| is_utf8_print||5.006000| is_utf8_punct||5.006000| is_utf8_space||5.006000| is_utf8_string_loclen||5.009003| is_utf8_string_loc||5.008001| is_utf8_string||5.006001| is_utf8_upper||5.006000| is_utf8_xdigit||5.006000| isa_lookup||| items|||n ix|||n jmaybe||| join_exact||| keyword||| leave_scope||| lex_end||| lex_start||| linklist||| listkids||| list||| load_module_nocontext|||vn load_module|5.006000||pv localize||| looks_like_bool||| looks_like_number||| lop||| mPUSHi|5.009002||p mPUSHn|5.009002||p mPUSHp|5.009002||p mPUSHs|5.011000||p mPUSHu|5.009002||p mXPUSHi|5.009002||p mXPUSHn|5.009002||p mXPUSHp|5.009002||p mXPUSHs|5.011000||p mXPUSHu|5.009002||p mad_free||| madlex||| madparse||| magic_clear_all_env||| magic_clearenv||| magic_clearhint||| magic_clearisa||| magic_clearpack||| magic_clearsig||| magic_dump||5.006000| magic_existspack||| magic_freearylen_p||| magic_freeovrld||| magic_getarylen||| magic_getdefelem||| magic_getnkeys||| magic_getpack||| magic_getpos||| magic_getsig||| magic_getsubstr||| magic_gettaint||| magic_getuvar||| magic_getvec||| magic_get||| magic_killbackrefs||| magic_len||| magic_methcall||| magic_methpack||| magic_nextpack||| magic_regdata_cnt||| magic_regdatum_get||| magic_regdatum_set||| magic_scalarpack||| magic_set_all_env||| magic_setamagic||| magic_setarylen||| magic_setcollxfrm||| magic_setdbline||| magic_setdefelem||| magic_setenv||| magic_sethint||| magic_setisa||| magic_setmglob||| magic_setnkeys||| magic_setpack||| magic_setpos||| magic_setregexp||| magic_setsig||| magic_setsubstr||| magic_settaint||| magic_setutf8||| magic_setuvar||| magic_setvec||| magic_set||| magic_sizepack||| magic_wipepack||| make_matcher||| make_trie_failtable||| make_trie||| malloc_good_size|||n malloced_size|||n malloc||5.007002|n markstack_grow||| matcher_matches_sv||| measure_struct||| memEQ|5.004000||p memNE|5.004000||p mem_collxfrm||| mem_log_common|||n mess_alloc||| mess_nocontext|||vn mess||5.006000|v method_common||| mfree||5.007002|n mg_clear||| mg_copy||| mg_dup||| mg_find||| mg_free||| mg_get||| mg_length||5.005000| mg_localize||| mg_magical||| mg_set||| mg_size||5.005000| mini_mktime||5.007002| missingterm||| mode_from_discipline||| modkids||| mod||| more_bodies||| more_sv||| moreswitches||| mro_get_from_name||5.011000| mro_get_linear_isa_dfs||| mro_get_linear_isa||5.009005| mro_get_private_data||5.011000| mro_isa_changed_in||| mro_meta_dup||| mro_meta_init||| mro_method_changed_in||5.009005| mro_register||5.011000| mro_set_mro||5.011000| mro_set_private_data||5.011000| mul128||| mulexp10|||n my_atof2||5.007002| my_atof||5.006000| my_attrs||| my_bcopy|||n my_betoh16|||n my_betoh32|||n my_betoh64|||n my_betohi|||n my_betohl|||n my_betohs|||n my_bzero|||n my_chsize||| my_clearenv||| my_cxt_index||| my_cxt_init||| my_dirfd||5.009005| my_exit_jump||| my_exit||| my_failure_exit||5.004000| my_fflush_all||5.006000| my_fork||5.007003|n my_htobe16|||n my_htobe32|||n my_htobe64|||n my_htobei|||n my_htobel|||n my_htobes|||n my_htole16|||n my_htole32|||n my_htole64|||n my_htolei|||n my_htolel|||n my_htoles|||n my_htonl||| my_kid||| my_letoh16|||n my_letoh32|||n my_letoh64|||n my_letohi|||n my_letohl|||n my_letohs|||n my_lstat||| my_memcmp||5.004000|n my_memset|||n my_ntohl||| my_pclose||5.004000| my_popen_list||5.007001| my_popen||5.004000| my_setenv||| my_snprintf|5.009004||pvn my_socketpair||5.007003|n my_sprintf|5.009003||pvn my_stat||| my_strftime||5.007002| my_strlcat|5.009004||pn my_strlcpy|5.009004||pn my_swabn|||n my_swap||| my_unexec||| my_vsnprintf||5.009004|n need_utf8|||n newANONATTRSUB||5.006000| newANONHASH||| newANONLIST||| newANONSUB||| newASSIGNOP||| newATTRSUB||5.006000| newAVREF||| newAV||| newBINOP||| newCONDOP||| newCONSTSUB|5.004050||p newCVREF||| newDEFSVOP||| newFORM||| newFOROP||| newGIVENOP||5.009003| newGIVWHENOP||| newGP||| newGVOP||| newGVREF||| newGVgen||| newHVREF||| newHVhv||5.005000| newHV||| newIO||| newLISTOP||| newLOGOP||| newLOOPEX||| newLOOPOP||| newMADPROP||| newMADsv||| newMYSUB||| newNULLLIST||| newOP||| newPADOP||| newPMOP||| newPROG||| newPVOP||| newRANGE||| newRV_inc|5.004000||p newRV_noinc|5.004000||p newRV||| newSLICEOP||| newSTATEOP||| newSUB||| newSVOP||| newSVREF||| newSV_type|5.009005||p newSVhek||5.009003| newSViv||| newSVnv||| newSVpvf_nocontext|||vn newSVpvf||5.004000|v newSVpvn_flags|5.011000||p newSVpvn_share|5.007001||p newSVpvn_utf8|5.011000||p newSVpvn|5.004050||p newSVpvs_flags|5.011000||p newSVpvs_share||5.009003| newSVpvs|5.009003||p newSVpv||| newSVrv||| newSVsv||| newSVuv|5.006000||p newSV||| newTOKEN||| newUNOP||| newWHENOP||5.009003| newWHILEOP||5.009003| newXS_flags||5.009004| newXSproto||5.006000| newXS||5.006000| new_collate||5.006000| new_constant||| new_ctype||5.006000| new_he||| new_logop||| new_numeric||5.006000| new_stackinfo||5.005000| new_version||5.009000| new_warnings_bitfield||| next_symbol||| nextargv||| nextchar||| ninstr||| no_bareword_allowed||| no_fh_allowed||| no_op||| not_a_number||| nothreadhook||5.008000| nuke_stacks||| num_overflow|||n offer_nice_chunk||| oopsAV||| oopsHV||| op_clear||| op_const_sv||| op_dump||5.006000| op_free||| op_getmad_weak||| op_getmad||| op_null||5.007002| op_refcnt_dec||| op_refcnt_inc||| op_refcnt_lock||5.009002| op_refcnt_unlock||5.009002| op_xmldump||| open_script||| pMY_CXT_|5.007003||p pMY_CXT|5.007003||p pTHX_|5.006000||p pTHX|5.006000||p packWARN|5.007003||p pack_cat||5.007003| pack_rec||| package||| packlist||5.008001| pad_add_anon||| pad_add_name||| pad_alloc||| pad_block_start||| pad_check_dup||| pad_compname_type||| pad_findlex||| pad_findmy||| pad_fixup_inner_anons||| pad_free||| pad_leavemy||| pad_new||| pad_peg|||n pad_push||| pad_reset||| pad_setsv||| pad_sv||5.011000| pad_swipe||| pad_tidy||| pad_undef||| parse_body||| parse_unicode_opts||| parser_dup||| parser_free||| path_is_absolute|||n peep||| pending_Slabs_to_ro||| perl_alloc_using|||n perl_alloc|||n perl_clone_using|||n perl_clone|||n perl_construct|||n perl_destruct||5.007003|n perl_free|||n perl_parse||5.006000|n perl_run|||n pidgone||| pm_description||| pmflag||| pmop_dump||5.006000| pmop_xmldump||| pmruntime||| pmtrans||| pop_scope||| pregcomp||5.009005| pregexec||| pregfree2||5.011000| pregfree||| prepend_elem||| prepend_madprops||| printbuf||| printf_nocontext|||vn process_special_blocks||| ptr_table_clear||5.009005| ptr_table_fetch||5.009005| ptr_table_find|||n ptr_table_free||5.009005| ptr_table_new||5.009005| ptr_table_split||5.009005| ptr_table_store||5.009005| push_scope||| put_byte||| pv_display|5.006000||p pv_escape|5.009004||p pv_pretty|5.009004||p pv_uni_display||5.007003| qerror||| qsortsvu||| re_compile||5.009005| re_croak2||| re_dup_guts||| re_intuit_start||5.009005| re_intuit_string||5.006000| readpipe_override||| realloc||5.007002|n reentrant_free||| reentrant_init||| reentrant_retry|||vn reentrant_size||| ref_array_or_hash||| refcounted_he_chain_2hv||| refcounted_he_fetch||| refcounted_he_free||| refcounted_he_new_common||| refcounted_he_new||| refcounted_he_value||| refkids||| refto||| ref||5.011000| reg_check_named_buff_matched||| reg_named_buff_all||5.009005| reg_named_buff_exists||5.009005| reg_named_buff_fetch||5.009005| reg_named_buff_firstkey||5.009005| reg_named_buff_iter||| reg_named_buff_nextkey||5.009005| reg_named_buff_scalar||5.009005| reg_named_buff||| reg_namedseq||| reg_node||| reg_numbered_buff_fetch||| reg_numbered_buff_length||| reg_numbered_buff_store||| reg_qr_package||| reg_recode||| reg_scan_name||| reg_skipcomment||| reg_temp_copy||| reganode||| regatom||| regbranch||| regclass_swash||5.009004| regclass||| regcppop||| regcppush||| regcurly|||n regdump_extflags||| regdump||5.005000| regdupe_internal||| regexec_flags||5.005000| regfree_internal||5.009005| reghop3|||n reghop4|||n reghopmaybe3|||n reginclass||| reginitcolors||5.006000| reginsert||| regmatch||| regnext||5.005000| regpiece||| regpposixcc||| regprop||| regrepeat||| regtail_study||| regtail||| regtry||| reguni||| regwhite|||n reg||| repeatcpy||| report_evil_fh||| report_uninit||| require_pv||5.006000| require_tie_mod||| restore_magic||| rninstr||| rsignal_restore||| rsignal_save||| rsignal_state||5.004000| rsignal||5.004000| run_body||| run_user_filter||| runops_debug||5.005000| runops_standard||5.005000| rvpv_dup||| rxres_free||| rxres_restore||| rxres_save||| safesyscalloc||5.006000|n safesysfree||5.006000|n safesysmalloc||5.006000|n safesysrealloc||5.006000|n same_dirent||| save_I16||5.004000| save_I32||| save_I8||5.006000| save_adelete||5.011000| save_aelem||5.004050| save_alloc||5.006000| save_aptr||| save_ary||| save_bool||5.008001| save_clearsv||| save_delete||| save_destructor_x||5.006000| save_destructor||5.006000| save_freeop||| save_freepv||| save_freesv||| save_generic_pvref||5.006001| save_generic_svref||5.005030| save_gp||5.004000| save_hash||| save_hek_flags|||n save_helem_flags||5.011000| save_helem||5.004050| save_hints||| save_hptr||| save_int||| save_item||| save_iv||5.005000| save_lines||| save_list||| save_long||| save_magic||| save_mortalizesv||5.007001| save_nogv||| save_op||| save_padsv_and_mortalize||5.011000| save_pptr||| save_pushi32ptr||| save_pushptri32ptr||| save_pushptrptr||| save_pushptr||5.011000| save_re_context||5.006000| save_scalar_at||| save_scalar||| save_set_svflags||5.009000| save_shared_pvref||5.007003| save_sptr||| save_svref||| save_vptr||5.006000| savepvn||| savepvs||5.009003| savepv||| savesharedpvn||5.009005| savesharedpv||5.007003| savestack_grow_cnt||5.008001| savestack_grow||| savesvpv||5.009002| sawparens||| scalar_mod_type|||n scalarboolean||| scalarkids||| scalarseq||| scalarvoid||| scalar||| scan_bin||5.006000| scan_commit||| scan_const||| scan_formline||| scan_heredoc||| scan_hex||| scan_ident||| scan_inputsymbol||| scan_num||5.007001| scan_oct||| scan_pat||| scan_str||| scan_subst||| scan_trans||| scan_version||5.009001| scan_vstring||5.009005| scan_word||| scope||| screaminstr||5.005000| search_const||| seed||5.008001| sequence_num||| sequence_tail||| sequence||| set_context||5.006000|n set_numeric_local||5.006000| set_numeric_radix||5.006000| set_numeric_standard||5.006000| setdefout||| share_hek_flags||| share_hek||5.004000| si_dup||| sighandler|||n simplify_sort||| skipspace0||| skipspace1||| skipspace2||| skipspace||| softref2xv||| sortcv_stacked||| sortcv_xsub||| sortcv||| sortsv_flags||5.009003| sortsv||5.007003| space_join_names_mortal||| ss_dup||| stack_grow||| start_force||| start_glob||| start_subparse||5.004000| stashpv_hvname_match||5.011000| stdize_locale||| store_cop_label||| strEQ||| strGE||| strGT||| strLE||| strLT||| strNE||| str_to_version||5.006000| strip_return||| strnEQ||| strnNE||| study_chunk||| sub_crush_depth||| sublex_done||| sublex_push||| sublex_start||| sv_2bool||| sv_2cv||| sv_2io||| sv_2iuv_common||| sv_2iuv_non_preserve||| sv_2iv_flags||5.009001| sv_2iv||| sv_2mortal||| sv_2num||| sv_2nv||| sv_2pv_flags|5.007002||p sv_2pv_nolen|5.006000||p sv_2pvbyte_nolen|5.006000||p sv_2pvbyte|5.006000||p sv_2pvutf8_nolen||5.006000| sv_2pvutf8||5.006000| sv_2pv||| sv_2uv_flags||5.009001| sv_2uv|5.004000||p sv_add_arena||| sv_add_backref||| sv_backoff||| sv_bless||| sv_cat_decode||5.008001| sv_catpv_mg|5.004050||p sv_catpvf_mg_nocontext|||pvn sv_catpvf_mg|5.006000|5.004000|pv sv_catpvf_nocontext|||vn sv_catpvf||5.004000|v sv_catpvn_flags||5.007002| sv_catpvn_mg|5.004050||p sv_catpvn_nomg|5.007002||p sv_catpvn||| sv_catpvs|5.009003||p sv_catpv||| sv_catsv_flags||5.007002| sv_catsv_mg|5.004050||p sv_catsv_nomg|5.007002||p sv_catsv||| sv_catxmlpvn||| sv_catxmlsv||| sv_chop||| sv_clean_all||| sv_clean_objs||| sv_clear||| sv_cmp_locale||5.004000| sv_cmp||| sv_collxfrm||| sv_compile_2op||5.008001| sv_copypv||5.007003| sv_dec||| sv_del_backref||| sv_derived_from||5.004000| sv_destroyable||5.010000| sv_does||5.009004| sv_dump||| sv_dup_inc_multiple||| sv_dup||| sv_eq||| sv_exp_grow||| sv_force_normal_flags||5.007001| sv_force_normal||5.006000| sv_free2||| sv_free_arenas||| sv_free||| sv_gets||5.004000| sv_grow||| sv_i_ncmp||| sv_inc||| sv_insert_flags||5.011000| sv_insert||| sv_isa||| sv_isobject||| sv_iv||5.005000| sv_kill_backrefs||| sv_len_utf8||5.006000| sv_len||| sv_magic_portable|5.011000|5.004000|p sv_magicext||5.007003| sv_magic||| sv_mortalcopy||| sv_ncmp||| sv_newmortal||| sv_newref||| sv_nolocking||5.007003| sv_nosharing||5.007003| sv_nounlocking||| sv_nv||5.005000| sv_peek||5.005000| sv_pos_b2u_midway||| sv_pos_b2u||5.006000| sv_pos_u2b_cached||| sv_pos_u2b_forwards|||n sv_pos_u2b_midway|||n sv_pos_u2b||5.006000| sv_pvbyten_force||5.006000| sv_pvbyten||5.006000| sv_pvbyte||5.006000| sv_pvn_force_flags|5.007002||p sv_pvn_force||| sv_pvn_nomg|5.007003|5.005000|p sv_pvn||5.005000| sv_pvutf8n_force||5.006000| sv_pvutf8n||5.006000| sv_pvutf8||5.006000| sv_pv||5.006000| sv_recode_to_utf8||5.007003| sv_reftype||| sv_release_COW||| sv_replace||| sv_report_used||| sv_reset||| sv_rvweaken||5.006000| sv_setiv_mg|5.004050||p sv_setiv||| sv_setnv_mg|5.006000||p sv_setnv||| sv_setpv_mg|5.004050||p sv_setpvf_mg_nocontext|||pvn sv_setpvf_mg|5.006000|5.004000|pv sv_setpvf_nocontext|||vn sv_setpvf||5.004000|v sv_setpviv_mg||5.008001| sv_setpviv||5.008001| sv_setpvn_mg|5.004050||p sv_setpvn||| sv_setpvs|5.009004||p sv_setpv||| sv_setref_iv||| sv_setref_nv||| sv_setref_pvn||| sv_setref_pv||| sv_setref_uv||5.007001| sv_setsv_cow||| sv_setsv_flags||5.007002| sv_setsv_mg|5.004050||p sv_setsv_nomg|5.007002||p sv_setsv||| sv_setuv_mg|5.004050||p sv_setuv|5.004000||p sv_tainted||5.004000| sv_taint||5.004000| sv_true||5.005000| sv_unglob||| sv_uni_display||5.007003| sv_unmagic||| sv_unref_flags||5.007001| sv_unref||| sv_untaint||5.004000| sv_upgrade||| sv_usepvn_flags||5.009004| sv_usepvn_mg|5.004050||p sv_usepvn||| sv_utf8_decode||5.006000| sv_utf8_downgrade||5.006000| sv_utf8_encode||5.006000| sv_utf8_upgrade_flags_grow||5.011000| sv_utf8_upgrade_flags||5.007002| sv_utf8_upgrade_nomg||5.007002| sv_utf8_upgrade||5.007001| sv_uv|5.005000||p sv_vcatpvf_mg|5.006000|5.004000|p sv_vcatpvfn||5.004000| sv_vcatpvf|5.006000|5.004000|p sv_vsetpvf_mg|5.006000|5.004000|p sv_vsetpvfn||5.004000| sv_vsetpvf|5.006000|5.004000|p sv_xmlpeek||| svtype||| swallow_bom||| swap_match_buff||| swash_fetch||5.007002| swash_get||| swash_init||5.006000| sys_init3||5.010000|n sys_init||5.010000|n sys_intern_clear||| sys_intern_dup||| sys_intern_init||| sys_term||5.010000|n taint_env||| taint_proper||| tmps_grow||5.006000| toLOWER||| toUPPER||| to_byte_substr||| to_uni_fold||5.007003| to_uni_lower_lc||5.006000| to_uni_lower||5.007003| to_uni_title_lc||5.006000| to_uni_title||5.007003| to_uni_upper_lc||5.006000| to_uni_upper||5.007003| to_utf8_case||5.007003| to_utf8_fold||5.007003| to_utf8_lower||5.007003| to_utf8_substr||| to_utf8_title||5.007003| to_utf8_upper||5.007003| token_free||| token_getmad||| tokenize_use||| tokeq||| tokereport||| too_few_arguments||| too_many_arguments||| uiv_2buf|||n unlnk||| unpack_rec||| unpack_str||5.007003| unpackstring||5.008001| unshare_hek_or_pvn||| unshare_hek||| unsharepvn||5.004000| unwind_handler_stack||| update_debugger_info||| upg_version||5.009005| usage||| utf16_to_utf8_reversed||5.006001| utf16_to_utf8||5.006001| utf8_distance||5.006000| utf8_hop||5.006000| utf8_length||5.007001| utf8_mg_pos_cache_update||| utf8_to_bytes||5.006001| utf8_to_uvchr||5.007001| utf8_to_uvuni||5.007001| utf8n_to_uvchr||| utf8n_to_uvuni||5.007001| utilize||| uvchr_to_utf8_flags||5.007003| uvchr_to_utf8||| uvuni_to_utf8_flags||5.007003| uvuni_to_utf8||5.007001| validate_suid||| varname||| vcmp||5.009000| vcroak||5.006000| vdeb||5.007003| vdie_common||| vdie_croak_common||| vdie||| vform||5.006000| visit||| vivify_defelem||| vivify_ref||| vload_module|5.006000||p vmess||5.006000| vnewSVpvf|5.006000|5.004000|p vnormal||5.009002| vnumify||5.009000| vstringify||5.009000| vverify||5.009003| vwarner||5.006000| vwarn||5.006000| wait4pid||| warn_nocontext|||vn warner_nocontext|||vn warner|5.006000|5.004000|pv warn|||v watch||| whichsig||| write_no_mem||| write_to_stderr||| xmldump_all||| xmldump_attr||| xmldump_eval||| xmldump_form||| xmldump_indent|||v xmldump_packsubs||| xmldump_sub||| xmldump_vindent||| yyerror||| yylex||| yyparse||| yywarn||| ); if (exists $opt{'list-unsupported'}) { my $f; for $f (sort { lc $a cmp lc $b } keys %API) { next unless $API{$f}{todo}; print "$f ", '.'x(40-length($f)), " ", format_version($API{$f}{todo}), "\n"; } exit 0; } # Scan for possible replacement candidates my(%replace, %need, %hints, %warnings, %depends); my $replace = 0; my($hint, $define, $function); sub find_api { my $code = shift; $code =~ s{ / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]*) | "[^"\\]*(?:\\.[^"\\]*)*" | '[^'\\]*(?:\\.[^'\\]*)*' }{}egsx; grep { exists $API{$_} } $code =~ /(\w+)/mg; } while () { if ($hint) { my $h = $hint->[0] eq 'Hint' ? \%hints : \%warnings; if (m{^\s*\*\s(.*?)\s*$}) { for (@{$hint->[1]}) { $h->{$_} ||= ''; # suppress warning with older perls $h->{$_} .= "$1\n"; } } else { undef $hint } } $hint = [$1, [split /,?\s+/, $2]] if m{^\s*$rccs\s+(Hint|Warning):\s+(\w+(?:,?\s+\w+)*)\s*$}; if ($define) { if ($define->[1] =~ /\\$/) { $define->[1] .= $_; } else { if (exists $API{$define->[0]} && $define->[1] !~ /^DPPP_\(/) { my @n = find_api($define->[1]); push @{$depends{$define->[0]}}, @n if @n } undef $define; } } $define = [$1, $2] if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(.*)}; if ($function) { if (/^}/) { if (exists $API{$function->[0]}) { my @n = find_api($function->[1]); push @{$depends{$function->[0]}}, @n if @n } undef $function; } else { $function->[1] .= $_; } } $function = [$1, ''] if m{^DPPP_\(my_(\w+)\)}; $replace = $1 if m{^\s*$rccs\s+Replace:\s+(\d+)\s+$rcce\s*$}; $replace{$2} = $1 if $replace and m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+)}; $replace{$2} = $1 if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+).*$rccs\s+Replace\s+$rcce}; $replace{$1} = $2 if m{^\s*$rccs\s+Replace (\w+) with (\w+)\s+$rcce\s*$}; if (m{^\s*$rccs\s+(\w+(\s*,\s*\w+)*)\s+depends\s+on\s+(\w+(\s*,\s*\w+)*)\s+$rcce\s*$}) { my @deps = map { s/\s+//g; $_ } split /,/, $3; my $d; for $d (map { s/\s+//g; $_ } split /,/, $1) { push @{$depends{$d}}, @deps; } } $need{$1} = 1 if m{^#if\s+defined\(NEED_(\w+)(?:_GLOBAL)?\)}; } for (values %depends) { my %s; $_ = [sort grep !$s{$_}++, @$_]; } if (exists $opt{'api-info'}) { my $f; my $count = 0; my $match = $opt{'api-info'} =~ m!^/(.*)/$! ? $1 : "^\Q$opt{'api-info'}\E\$"; for $f (sort { lc $a cmp lc $b } keys %API) { next unless $f =~ /$match/; print "\n=== $f ===\n\n"; my $info = 0; if ($API{$f}{base} || $API{$f}{todo}) { my $base = format_version($API{$f}{base} || $API{$f}{todo}); print "Supported at least starting from perl-$base.\n"; $info++; } if ($API{$f}{provided}) { my $todo = $API{$f}{todo} ? format_version($API{$f}{todo}) : "5.003"; print "Support by $ppport provided back to perl-$todo.\n"; print "Support needs to be explicitly requested by NEED_$f.\n" if exists $need{$f}; print "Depends on: ", join(', ', @{$depends{$f}}), ".\n" if exists $depends{$f}; print "\n$hints{$f}" if exists $hints{$f}; print "\nWARNING:\n$warnings{$f}" if exists $warnings{$f}; $info++; } print "No portability information available.\n" unless $info; $count++; } $count or print "Found no API matching '$opt{'api-info'}'."; print "\n"; exit 0; } if (exists $opt{'list-provided'}) { my $f; for $f (sort { lc $a cmp lc $b } keys %API) { next unless $API{$f}{provided}; my @flags; push @flags, 'explicit' if exists $need{$f}; push @flags, 'depend' if exists $depends{$f}; push @flags, 'hint' if exists $hints{$f}; push @flags, 'warning' if exists $warnings{$f}; my $flags = @flags ? ' ['.join(', ', @flags).']' : ''; print "$f$flags\n"; } exit 0; } my @files; my @srcext = qw( .xs .c .h .cc .cpp -c.inc -xs.inc ); my $srcext = join '|', map { quotemeta $_ } @srcext; if (@ARGV) { my %seen; for (@ARGV) { if (-e) { if (-f) { push @files, $_ unless $seen{$_}++; } else { warn "'$_' is not a file.\n" } } else { my @new = grep { -f } glob $_ or warn "'$_' does not exist.\n"; push @files, grep { !$seen{$_}++ } @new; } } } else { eval { require File::Find; File::Find::find(sub { $File::Find::name =~ /($srcext)$/i and push @files, $File::Find::name; }, '.'); }; if ($@) { @files = map { glob "*$_" } @srcext; } } if (!@ARGV || $opt{filter}) { my(@in, @out); my %xsc = map { /(.*)\.xs$/ ? ("$1.c" => 1, "$1.cc" => 1) : () } @files; for (@files) { my $out = exists $xsc{$_} || /\b\Q$ppport\E$/i || !/($srcext)$/i; push @{ $out ? \@out : \@in }, $_; } if (@ARGV && @out) { warning("Skipping the following files (use --nofilter to avoid this):\n| ", join "\n| ", @out); } @files = @in; } die "No input files given!\n" unless @files; my(%files, %global, %revreplace); %revreplace = reverse %replace; my $filename; my $patch_opened = 0; for $filename (@files) { unless (open IN, "<$filename") { warn "Unable to read from $filename: $!\n"; next; } info("Scanning $filename ..."); my $c = do { local $/; }; close IN; my %file = (orig => $c, changes => 0); # Temporarily remove C/XS comments and strings from the code my @ccom; $c =~ s{ ( ^$HS*\#$HS*include\b[^\r\n]+\b(?:\Q$ppport\E|XSUB\.h)\b[^\r\n]* | ^$HS*\#$HS*(?:define|elif|if(?:def)?)\b[^\r\n]* ) | ( ^$HS*\#[^\r\n]* | "[^"\\]*(?:\\.[^"\\]*)*" | '[^'\\]*(?:\\.[^'\\]*)*' | / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]* ) ) }{ defined $2 and push @ccom, $2; defined $1 ? $1 : "$ccs$#ccom$cce" }mgsex; $file{ccom} = \@ccom; $file{code} = $c; $file{has_inc_ppport} = $c =~ /^$HS*#$HS*include[^\r\n]+\b\Q$ppport\E\b/m; my $func; for $func (keys %API) { my $match = $func; $match .= "|$revreplace{$func}" if exists $revreplace{$func}; if ($c =~ /\b(?:Perl_)?($match)\b/) { $file{uses_replace}{$1}++ if exists $revreplace{$func} && $1 eq $revreplace{$func}; $file{uses_Perl}{$func}++ if $c =~ /\bPerl_$func\b/; if (exists $API{$func}{provided}) { $file{uses_provided}{$func}++; if (!exists $API{$func}{base} || $API{$func}{base} > $opt{'compat-version'}) { $file{uses}{$func}++; my @deps = rec_depend($func); if (@deps) { $file{uses_deps}{$func} = \@deps; for (@deps) { $file{uses}{$_} = 0 unless exists $file{uses}{$_}; } } for ($func, @deps) { $file{needs}{$_} = 'static' if exists $need{$_}; } } } if (exists $API{$func}{todo} && $API{$func}{todo} > $opt{'compat-version'}) { if ($c =~ /\b$func\b/) { $file{uses_todo}{$func}++; } } } } while ($c =~ /^$HS*#$HS*define$HS+(NEED_(\w+?)(_GLOBAL)?)\b/mg) { if (exists $need{$2}) { $file{defined $3 ? 'needed_global' : 'needed_static'}{$2}++; } else { warning("Possibly wrong #define $1 in $filename") } } for (qw(uses needs uses_todo needed_global needed_static)) { for $func (keys %{$file{$_}}) { push @{$global{$_}{$func}}, $filename; } } $files{$filename} = \%file; } # Globally resolve NEED_'s my $need; for $need (keys %{$global{needs}}) { if (@{$global{needs}{$need}} > 1) { my @targets = @{$global{needs}{$need}}; my @t = grep $files{$_}{needed_global}{$need}, @targets; @targets = @t if @t; @t = grep /\.xs$/i, @targets; @targets = @t if @t; my $target = shift @targets; $files{$target}{needs}{$need} = 'global'; for (@{$global{needs}{$need}}) { $files{$_}{needs}{$need} = 'extern' if $_ ne $target; } } } for $filename (@files) { exists $files{$filename} or next; info("=== Analyzing $filename ==="); my %file = %{$files{$filename}}; my $func; my $c = $file{code}; my $warnings = 0; for $func (sort keys %{$file{uses_Perl}}) { if ($API{$func}{varargs}) { unless ($API{$func}{nothxarg}) { my $changes = ($c =~ s{\b(Perl_$func\s*\(\s*)(?!aTHX_?)(\)|[^\s)]*\))} { $1 . ($2 eq ')' ? 'aTHX' : 'aTHX_ ') . $2 }ge); if ($changes) { warning("Doesn't pass interpreter argument aTHX to Perl_$func"); $file{changes} += $changes; } } } else { warning("Uses Perl_$func instead of $func"); $file{changes} += ($c =~ s{\bPerl_$func(\s*)\((\s*aTHX_?)?\s*} {$func$1(}g); } } for $func (sort keys %{$file{uses_replace}}) { warning("Uses $func instead of $replace{$func}"); $file{changes} += ($c =~ s/\b$func\b/$replace{$func}/g); } for $func (sort keys %{$file{uses_provided}}) { if ($file{uses}{$func}) { if (exists $file{uses_deps}{$func}) { diag("Uses $func, which depends on ", join(', ', @{$file{uses_deps}{$func}})); } else { diag("Uses $func"); } } $warnings += hint($func); } unless ($opt{quiet}) { for $func (sort keys %{$file{uses_todo}}) { print "*** WARNING: Uses $func, which may not be portable below perl ", format_version($API{$func}{todo}), ", even with '$ppport'\n"; $warnings++; } } for $func (sort keys %{$file{needed_static}}) { my $message = ''; if (not exists $file{uses}{$func}) { $message = "No need to define NEED_$func if $func is never used"; } elsif (exists $file{needs}{$func} && $file{needs}{$func} ne 'static') { $message = "No need to define NEED_$func when already needed globally"; } if ($message) { diag($message); $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_$func\b.*$LF//mg); } } for $func (sort keys %{$file{needed_global}}) { my $message = ''; if (not exists $global{uses}{$func}) { $message = "No need to define NEED_${func}_GLOBAL if $func is never used"; } elsif (exists $file{needs}{$func}) { if ($file{needs}{$func} eq 'extern') { $message = "No need to define NEED_${func}_GLOBAL when already needed globally"; } elsif ($file{needs}{$func} eq 'static') { $message = "No need to define NEED_${func}_GLOBAL when only used in this file"; } } if ($message) { diag($message); $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_${func}_GLOBAL\b.*$LF//mg); } } $file{needs_inc_ppport} = keys %{$file{uses}}; if ($file{needs_inc_ppport}) { my $pp = ''; for $func (sort keys %{$file{needs}}) { my $type = $file{needs}{$func}; next if $type eq 'extern'; my $suffix = $type eq 'global' ? '_GLOBAL' : ''; unless (exists $file{"needed_$type"}{$func}) { if ($type eq 'global') { diag("Files [@{$global{needs}{$func}}] need $func, adding global request"); } else { diag("File needs $func, adding static request"); } $pp .= "#define NEED_$func$suffix\n"; } } if ($pp && ($c =~ s/^(?=$HS*#$HS*define$HS+NEED_\w+)/$pp/m)) { $pp = ''; $file{changes}++; } unless ($file{has_inc_ppport}) { diag("Needs to include '$ppport'"); $pp .= qq(#include "$ppport"\n) } if ($pp) { $file{changes} += ($c =~ s/^($HS*#$HS*define$HS+NEED_\w+.*?)^/$1$pp/ms) || ($c =~ s/^(?=$HS*#$HS*include.*\Q$ppport\E)/$pp/m) || ($c =~ s/^($HS*#$HS*include.*XSUB.*\s*?)^/$1$pp/m) || ($c =~ s/^/$pp/); } } else { if ($file{has_inc_ppport}) { diag("No need to include '$ppport'"); $file{changes} += ($c =~ s/^$HS*?#$HS*include.*\Q$ppport\E.*?$LF//m); } } # put back in our C comments my $ix; my $cppc = 0; my @ccom = @{$file{ccom}}; for $ix (0 .. $#ccom) { if (!$opt{cplusplus} && $ccom[$ix] =~ s!^//!!) { $cppc++; $file{changes} += $c =~ s/$rccs$ix$rcce/$ccs$ccom[$ix] $cce/; } else { $c =~ s/$rccs$ix$rcce/$ccom[$ix]/; } } if ($cppc) { my $s = $cppc != 1 ? 's' : ''; warning("Uses $cppc C++ style comment$s, which is not portable"); } my $s = $warnings != 1 ? 's' : ''; my $warn = $warnings ? " ($warnings warning$s)" : ''; info("Analysis completed$warn"); if ($file{changes}) { if (exists $opt{copy}) { my $newfile = "$filename$opt{copy}"; if (-e $newfile) { error("'$newfile' already exists, refusing to write copy of '$filename'"); } else { local *F; if (open F, ">$newfile") { info("Writing copy of '$filename' with changes to '$newfile'"); print F $c; close F; } else { error("Cannot open '$newfile' for writing: $!"); } } } elsif (exists $opt{patch} || $opt{changes}) { if (exists $opt{patch}) { unless ($patch_opened) { if (open PATCH, ">$opt{patch}") { $patch_opened = 1; } else { error("Cannot open '$opt{patch}' for writing: $!"); delete $opt{patch}; $opt{changes} = 1; goto fallback; } } mydiff(\*PATCH, $filename, $c); } else { fallback: info("Suggested changes:"); mydiff(\*STDOUT, $filename, $c); } } else { my $s = $file{changes} == 1 ? '' : 's'; info("$file{changes} potentially required change$s detected"); } } else { info("Looks good"); } } close PATCH if $patch_opened; exit 0; sub try_use { eval "use @_;"; return $@ eq '' } sub mydiff { local *F = shift; my($file, $str) = @_; my $diff; if (exists $opt{diff}) { $diff = run_diff($opt{diff}, $file, $str); } if (!defined $diff and try_use('Text::Diff')) { $diff = Text::Diff::diff($file, \$str, { STYLE => 'Unified' }); $diff = <
$tmp") { print F $str; close F; if (open F, "$prog $file $tmp |") { while () { s/\Q$tmp\E/$file.patched/; $diff .= $_; } close F; unlink $tmp; return $diff; } unlink $tmp; } else { error("Cannot open '$tmp' for writing: $!"); } return undef; } sub rec_depend { my($func, $seen) = @_; return () unless exists $depends{$func}; $seen = {%{$seen||{}}}; return () if $seen->{$func}++; my %s; grep !$s{$_}++, map { ($_, rec_depend($_, $seen)) } @{$depends{$func}}; } sub parse_version { my $ver = shift; if ($ver =~ /^(\d+)\.(\d+)\.(\d+)$/) { return ($1, $2, $3); } elsif ($ver !~ /^\d+\.[\d_]+$/) { die "cannot parse version '$ver'\n"; } $ver =~ s/_//g; $ver =~ s/$/000000/; my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/; $v = int $v; $s = int $s; if ($r < 5 || ($r == 5 && $v < 6)) { if ($s % 10) { die "cannot parse version '$ver'\n"; } } return ($r, $v, $s); } sub format_version { my $ver = shift; $ver =~ s/$/000000/; my($r,$v,$s) = $ver =~ /(\d+)\.(\d{3})(\d{3})/; $v = int $v; $s = int $s; if ($r < 5 || ($r == 5 && $v < 6)) { if ($s % 10) { die "invalid version '$ver'\n"; } $s /= 10; $ver = sprintf "%d.%03d", $r, $v; $s > 0 and $ver .= sprintf "_%02d", $s; return $ver; } return sprintf "%d.%d.%d", $r, $v, $s; } sub info { $opt{quiet} and return; print @_, "\n"; } sub diag { $opt{quiet} and return; $opt{diag} and print @_, "\n"; } sub warning { $opt{quiet} and return; print "*** ", @_, "\n"; } sub error { print "*** ERROR: ", @_, "\n"; } my %given_hints; my %given_warnings; sub hint { $opt{quiet} and return; my $func = shift; my $rv = 0; if (exists $warnings{$func} && !$given_warnings{$func}++) { my $warn = $warnings{$func}; $warn =~ s!^!*** !mg; print "*** WARNING: $func\n", $warn; $rv++; } if ($opt{hints} && exists $hints{$func} && !$given_hints{$func}++) { my $hint = $hints{$func}; $hint =~ s/^/ /mg; print " --- hint for $func ---\n", $hint; } $rv; } sub usage { my($usage) = do { local(@ARGV,$/)=($0); <> } =~ /^=head\d$HS+SYNOPSIS\s*^(.*?)\s*^=/ms; my %M = ( 'I' => '*' ); $usage =~ s/^\s*perl\s+\S+/$^X $0/; $usage =~ s/([A-Z])<([^>]+)>/$M{$1}$2$M{$1}/g; print < }; my($copy) = $self =~ /^=head\d\s+COPYRIGHT\s*^(.*?)^=\w+/ms; $copy =~ s/^(?=\S+)/ /gms; $self =~ s/^$HS+Do NOT edit.*?(?=^-)/$copy/ms; $self =~ s/^SKIP.*(?=^__DATA__)/SKIP if (\@ARGV && \$ARGV[0] eq '--unstrip') { eval { require Devel::PPPort }; \$@ and die "Cannot require Devel::PPPort, please install.\\n"; if (eval \$Devel::PPPort::VERSION < $VERSION) { die "$0 was originally generated with Devel::PPPort $VERSION.\\n" . "Your Devel::PPPort is only version \$Devel::PPPort::VERSION.\\n" . "Please install a newer version, or --unstrip will not work.\\n"; } Devel::PPPort::WriteFile(\$0); exit 0; } print <$0" or die "cannot strip $0: $!\n"; print OUT "$pl$c\n"; exit 0; } __DATA__ */ #ifndef _P_P_PORTABILITY_H_ #define _P_P_PORTABILITY_H_ #ifndef DPPP_NAMESPACE # define DPPP_NAMESPACE DPPP_ #endif #define DPPP_CAT2(x,y) CAT2(x,y) #define DPPP_(name) DPPP_CAT2(DPPP_NAMESPACE, name) #ifndef PERL_REVISION # if !defined(__PATCHLEVEL_H_INCLUDED__) && !(defined(PATCHLEVEL) && defined(SUBVERSION)) # define PERL_PATCHLEVEL_H_IMPLICIT # include # endif # if !(defined(PERL_VERSION) || (defined(SUBVERSION) && defined(PATCHLEVEL))) # include # endif # ifndef PERL_REVISION # define PERL_REVISION (5) /* Replace: 1 */ # define PERL_VERSION PATCHLEVEL # define PERL_SUBVERSION SUBVERSION /* Replace PERL_PATCHLEVEL with PERL_VERSION */ /* Replace: 0 */ # endif #endif #define _dpppDEC2BCD(dec) ((((dec)/100)<<8)|((((dec)%100)/10)<<4)|((dec)%10)) #define PERL_BCDVERSION ((_dpppDEC2BCD(PERL_REVISION)<<24)|(_dpppDEC2BCD(PERL_VERSION)<<12)|_dpppDEC2BCD(PERL_SUBVERSION)) /* It is very unlikely that anyone will try to use this with Perl 6 (or greater), but who knows. */ #if PERL_REVISION != 5 # error ppport.h only works with Perl version 5 #endif /* PERL_REVISION != 5 */ #ifndef dTHR # define dTHR dNOOP #endif #ifndef dTHX # define dTHX dNOOP #endif #ifndef dTHXa # define dTHXa(x) dNOOP #endif #ifndef pTHX # define pTHX void #endif #ifndef pTHX_ # define pTHX_ #endif #ifndef aTHX # define aTHX #endif #ifndef aTHX_ # define aTHX_ #endif #if (PERL_BCDVERSION < 0x5006000) # ifdef USE_THREADS # define aTHXR thr # define aTHXR_ thr, # else # define aTHXR # define aTHXR_ # endif # define dTHXR dTHR #else # define aTHXR aTHX # define aTHXR_ aTHX_ # define dTHXR dTHX #endif #ifndef dTHXoa # define dTHXoa(x) dTHXa(x) #endif #ifdef I_LIMITS # include #endif #ifndef PERL_UCHAR_MIN # define PERL_UCHAR_MIN ((unsigned char)0) #endif #ifndef PERL_UCHAR_MAX # ifdef UCHAR_MAX # define PERL_UCHAR_MAX ((unsigned char)UCHAR_MAX) # else # ifdef MAXUCHAR # define PERL_UCHAR_MAX ((unsigned char)MAXUCHAR) # else # define PERL_UCHAR_MAX ((unsigned char)~(unsigned)0) # endif # endif #endif #ifndef PERL_USHORT_MIN # define PERL_USHORT_MIN ((unsigned short)0) #endif #ifndef PERL_USHORT_MAX # ifdef USHORT_MAX # define PERL_USHORT_MAX ((unsigned short)USHORT_MAX) # else # ifdef MAXUSHORT # define PERL_USHORT_MAX ((unsigned short)MAXUSHORT) # else # ifdef USHRT_MAX # define PERL_USHORT_MAX ((unsigned short)USHRT_MAX) # else # define PERL_USHORT_MAX ((unsigned short)~(unsigned)0) # endif # endif # endif #endif #ifndef PERL_SHORT_MAX # ifdef SHORT_MAX # define PERL_SHORT_MAX ((short)SHORT_MAX) # else # ifdef MAXSHORT /* Often used in */ # define PERL_SHORT_MAX ((short)MAXSHORT) # else # ifdef SHRT_MAX # define PERL_SHORT_MAX ((short)SHRT_MAX) # else # define PERL_SHORT_MAX ((short) (PERL_USHORT_MAX >> 1)) # endif # endif # endif #endif #ifndef PERL_SHORT_MIN # ifdef SHORT_MIN # define PERL_SHORT_MIN ((short)SHORT_MIN) # else # ifdef MINSHORT # define PERL_SHORT_MIN ((short)MINSHORT) # else # ifdef SHRT_MIN # define PERL_SHORT_MIN ((short)SHRT_MIN) # else # define PERL_SHORT_MIN (-PERL_SHORT_MAX - ((3 & -1) == 3)) # endif # endif # endif #endif #ifndef PERL_UINT_MAX # ifdef UINT_MAX # define PERL_UINT_MAX ((unsigned int)UINT_MAX) # else # ifdef MAXUINT # define PERL_UINT_MAX ((unsigned int)MAXUINT) # else # define PERL_UINT_MAX (~(unsigned int)0) # endif # endif #endif #ifndef PERL_UINT_MIN # define PERL_UINT_MIN ((unsigned int)0) #endif #ifndef PERL_INT_MAX # ifdef INT_MAX # define PERL_INT_MAX ((int)INT_MAX) # else # ifdef MAXINT /* Often used in */ # define PERL_INT_MAX ((int)MAXINT) # else # define PERL_INT_MAX ((int)(PERL_UINT_MAX >> 1)) # endif # endif #endif #ifndef PERL_INT_MIN # ifdef INT_MIN # define PERL_INT_MIN ((int)INT_MIN) # else # ifdef MININT # define PERL_INT_MIN ((int)MININT) # else # define PERL_INT_MIN (-PERL_INT_MAX - ((3 & -1) == 3)) # endif # endif #endif #ifndef PERL_ULONG_MAX # ifdef ULONG_MAX # define PERL_ULONG_MAX ((unsigned long)ULONG_MAX) # else # ifdef MAXULONG # define PERL_ULONG_MAX ((unsigned long)MAXULONG) # else # define PERL_ULONG_MAX (~(unsigned long)0) # endif # endif #endif #ifndef PERL_ULONG_MIN # define PERL_ULONG_MIN ((unsigned long)0L) #endif #ifndef PERL_LONG_MAX # ifdef LONG_MAX # define PERL_LONG_MAX ((long)LONG_MAX) # else # ifdef MAXLONG # define PERL_LONG_MAX ((long)MAXLONG) # else # define PERL_LONG_MAX ((long) (PERL_ULONG_MAX >> 1)) # endif # endif #endif #ifndef PERL_LONG_MIN # ifdef LONG_MIN # define PERL_LONG_MIN ((long)LONG_MIN) # else # ifdef MINLONG # define PERL_LONG_MIN ((long)MINLONG) # else # define PERL_LONG_MIN (-PERL_LONG_MAX - ((3 & -1) == 3)) # endif # endif #endif #if defined(HAS_QUAD) && (defined(convex) || defined(uts)) # ifndef PERL_UQUAD_MAX # ifdef ULONGLONG_MAX # define PERL_UQUAD_MAX ((unsigned long long)ULONGLONG_MAX) # else # ifdef MAXULONGLONG # define PERL_UQUAD_MAX ((unsigned long long)MAXULONGLONG) # else # define PERL_UQUAD_MAX (~(unsigned long long)0) # endif # endif # endif # ifndef PERL_UQUAD_MIN # define PERL_UQUAD_MIN ((unsigned long long)0L) # endif # ifndef PERL_QUAD_MAX # ifdef LONGLONG_MAX # define PERL_QUAD_MAX ((long long)LONGLONG_MAX) # else # ifdef MAXLONGLONG # define PERL_QUAD_MAX ((long long)MAXLONGLONG) # else # define PERL_QUAD_MAX ((long long) (PERL_UQUAD_MAX >> 1)) # endif # endif # endif # ifndef PERL_QUAD_MIN # ifdef LONGLONG_MIN # define PERL_QUAD_MIN ((long long)LONGLONG_MIN) # else # ifdef MINLONGLONG # define PERL_QUAD_MIN ((long long)MINLONGLONG) # else # define PERL_QUAD_MIN (-PERL_QUAD_MAX - ((3 & -1) == 3)) # endif # endif # endif #endif /* This is based on code from 5.003 perl.h */ #ifdef HAS_QUAD # ifdef cray #ifndef IVTYPE # define IVTYPE int #endif #ifndef IV_MIN # define IV_MIN PERL_INT_MIN #endif #ifndef IV_MAX # define IV_MAX PERL_INT_MAX #endif #ifndef UV_MIN # define UV_MIN PERL_UINT_MIN #endif #ifndef UV_MAX # define UV_MAX PERL_UINT_MAX #endif # ifdef INTSIZE #ifndef IVSIZE # define IVSIZE INTSIZE #endif # endif # else # if defined(convex) || defined(uts) #ifndef IVTYPE # define IVTYPE long long #endif #ifndef IV_MIN # define IV_MIN PERL_QUAD_MIN #endif #ifndef IV_MAX # define IV_MAX PERL_QUAD_MAX #endif #ifndef UV_MIN # define UV_MIN PERL_UQUAD_MIN #endif #ifndef UV_MAX # define UV_MAX PERL_UQUAD_MAX #endif # ifdef LONGLONGSIZE #ifndef IVSIZE # define IVSIZE LONGLONGSIZE #endif # endif # else #ifndef IVTYPE # define IVTYPE long #endif #ifndef IV_MIN # define IV_MIN PERL_LONG_MIN #endif #ifndef IV_MAX # define IV_MAX PERL_LONG_MAX #endif #ifndef UV_MIN # define UV_MIN PERL_ULONG_MIN #endif #ifndef UV_MAX # define UV_MAX PERL_ULONG_MAX #endif # ifdef LONGSIZE #ifndef IVSIZE # define IVSIZE LONGSIZE #endif # endif # endif # endif #ifndef IVSIZE # define IVSIZE 8 #endif #ifndef PERL_QUAD_MIN # define PERL_QUAD_MIN IV_MIN #endif #ifndef PERL_QUAD_MAX # define PERL_QUAD_MAX IV_MAX #endif #ifndef PERL_UQUAD_MIN # define PERL_UQUAD_MIN UV_MIN #endif #ifndef PERL_UQUAD_MAX # define PERL_UQUAD_MAX UV_MAX #endif #else #ifndef IVTYPE # define IVTYPE long #endif #ifndef IV_MIN # define IV_MIN PERL_LONG_MIN #endif #ifndef IV_MAX # define IV_MAX PERL_LONG_MAX #endif #ifndef UV_MIN # define UV_MIN PERL_ULONG_MIN #endif #ifndef UV_MAX # define UV_MAX PERL_ULONG_MAX #endif #endif #ifndef IVSIZE # ifdef LONGSIZE # define IVSIZE LONGSIZE # else # define IVSIZE 4 /* A bold guess, but the best we can make. */ # endif #endif #ifndef UVTYPE # define UVTYPE unsigned IVTYPE #endif #ifndef UVSIZE # define UVSIZE IVSIZE #endif #ifndef sv_setuv # define sv_setuv(sv, uv) \ STMT_START { \ UV TeMpUv = uv; \ if (TeMpUv <= IV_MAX) \ sv_setiv(sv, TeMpUv); \ else \ sv_setnv(sv, (double)TeMpUv); \ } STMT_END #endif #ifndef newSVuv # define newSVuv(uv) ((uv) <= IV_MAX ? newSViv((IV)uv) : newSVnv((NV)uv)) #endif #ifndef sv_2uv # define sv_2uv(sv) ((PL_Sv = (sv)), (UV) (SvNOK(PL_Sv) ? SvNV(PL_Sv) : sv_2nv(PL_Sv))) #endif #ifndef SvUVX # define SvUVX(sv) ((UV)SvIVX(sv)) #endif #ifndef SvUVXx # define SvUVXx(sv) SvUVX(sv) #endif #ifndef SvUV # define SvUV(sv) (SvIOK(sv) ? SvUVX(sv) : sv_2uv(sv)) #endif #ifndef SvUVx # define SvUVx(sv) ((PL_Sv = (sv)), SvUV(PL_Sv)) #endif /* Hint: sv_uv * Always use the SvUVx() macro instead of sv_uv(). */ #ifndef sv_uv # define sv_uv(sv) SvUVx(sv) #endif #if !defined(SvUOK) && defined(SvIOK_UV) # define SvUOK(sv) SvIOK_UV(sv) #endif #ifndef XST_mUV # define XST_mUV(i,v) (ST(i) = sv_2mortal(newSVuv(v)) ) #endif #ifndef XSRETURN_UV # define XSRETURN_UV(v) STMT_START { XST_mUV(0,v); XSRETURN(1); } STMT_END #endif #ifndef PUSHu # define PUSHu(u) STMT_START { sv_setuv(TARG, (UV)(u)); PUSHTARG; } STMT_END #endif #ifndef XPUSHu # define XPUSHu(u) STMT_START { sv_setuv(TARG, (UV)(u)); XPUSHTARG; } STMT_END #endif #ifdef HAS_MEMCMP #ifndef memNE # define memNE(s1,s2,l) (memcmp(s1,s2,l)) #endif #ifndef memEQ # define memEQ(s1,s2,l) (!memcmp(s1,s2,l)) #endif #else #ifndef memNE # define memNE(s1,s2,l) (bcmp(s1,s2,l)) #endif #ifndef memEQ # define memEQ(s1,s2,l) (!bcmp(s1,s2,l)) #endif #endif #ifndef MoveD # define MoveD(s,d,n,t) memmove((char*)(d),(char*)(s), (n) * sizeof(t)) #endif #ifndef CopyD # define CopyD(s,d,n,t) memcpy((char*)(d),(char*)(s), (n) * sizeof(t)) #endif #ifdef HAS_MEMSET #ifndef ZeroD # define ZeroD(d,n,t) memzero((char*)(d), (n) * sizeof(t)) #endif #else #ifndef ZeroD # define ZeroD(d,n,t) ((void)memzero((char*)(d), (n) * sizeof(t)), d) #endif #endif #ifndef PoisonWith # define PoisonWith(d,n,t,b) (void)memset((char*)(d), (U8)(b), (n) * sizeof(t)) #endif #ifndef PoisonNew # define PoisonNew(d,n,t) PoisonWith(d,n,t,0xAB) #endif #ifndef PoisonFree # define PoisonFree(d,n,t) PoisonWith(d,n,t,0xEF) #endif #ifndef Poison # define Poison(d,n,t) PoisonFree(d,n,t) #endif #ifndef Newx # define Newx(v,n,t) New(0,v,n,t) #endif #ifndef Newxc # define Newxc(v,n,t,c) Newc(0,v,n,t,c) #endif #ifndef Newxz # define Newxz(v,n,t) Newz(0,v,n,t) #endif #ifndef PERL_UNUSED_DECL # ifdef HASATTRIBUTE # if (defined(__GNUC__) && defined(__cplusplus)) || defined(__INTEL_COMPILER) # define PERL_UNUSED_DECL # else # define PERL_UNUSED_DECL __attribute__((unused)) # endif # else # define PERL_UNUSED_DECL # endif #endif #ifndef PERL_UNUSED_ARG # if defined(lint) && defined(S_SPLINT_S) /* www.splint.org */ # include # define PERL_UNUSED_ARG(x) NOTE(ARGUNUSED(x)) # else # define PERL_UNUSED_ARG(x) ((void)x) # endif #endif #ifndef PERL_UNUSED_VAR # define PERL_UNUSED_VAR(x) ((void)x) #endif #ifndef PERL_UNUSED_CONTEXT # ifdef USE_ITHREADS # define PERL_UNUSED_CONTEXT PERL_UNUSED_ARG(my_perl) # else # define PERL_UNUSED_CONTEXT # endif #endif #ifndef NOOP # define NOOP /*EMPTY*/(void)0 #endif #ifndef dNOOP # define dNOOP extern int /*@unused@*/ Perl___notused PERL_UNUSED_DECL #endif #ifndef NVTYPE # if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE) # define NVTYPE long double # else # define NVTYPE double # endif typedef NVTYPE NV; #endif #ifndef INT2PTR # if (IVSIZE == PTRSIZE) && (UVSIZE == PTRSIZE) # define PTRV UV # define INT2PTR(any,d) (any)(d) # else # if PTRSIZE == LONGSIZE # define PTRV unsigned long # else # define PTRV unsigned # endif # define INT2PTR(any,d) (any)(PTRV)(d) # endif #endif #ifndef PTR2ul # if PTRSIZE == LONGSIZE # define PTR2ul(p) (unsigned long)(p) # else # define PTR2ul(p) INT2PTR(unsigned long,p) # endif #endif #ifndef PTR2nat # define PTR2nat(p) (PTRV)(p) #endif #ifndef NUM2PTR # define NUM2PTR(any,d) (any)PTR2nat(d) #endif #ifndef PTR2IV # define PTR2IV(p) INT2PTR(IV,p) #endif #ifndef PTR2UV # define PTR2UV(p) INT2PTR(UV,p) #endif #ifndef PTR2NV # define PTR2NV(p) NUM2PTR(NV,p) #endif #undef START_EXTERN_C #undef END_EXTERN_C #undef EXTERN_C #ifdef __cplusplus # define START_EXTERN_C extern "C" { # define END_EXTERN_C } # define EXTERN_C extern "C" #else # define START_EXTERN_C # define END_EXTERN_C # define EXTERN_C extern #endif #if defined(PERL_GCC_PEDANTIC) # ifndef PERL_GCC_BRACE_GROUPS_FORBIDDEN # define PERL_GCC_BRACE_GROUPS_FORBIDDEN # endif #endif #if defined(__GNUC__) && !defined(PERL_GCC_BRACE_GROUPS_FORBIDDEN) && !defined(__cplusplus) # ifndef PERL_USE_GCC_BRACE_GROUPS # define PERL_USE_GCC_BRACE_GROUPS # endif #endif #undef STMT_START #undef STMT_END #ifdef PERL_USE_GCC_BRACE_GROUPS # define STMT_START (void)( /* gcc supports ``({ STATEMENTS; })'' */ # define STMT_END ) #else # if defined(VOIDFLAGS) && (VOIDFLAGS) && (defined(sun) || defined(__sun__)) && !defined(__GNUC__) # define STMT_START if (1) # define STMT_END else (void)0 # else # define STMT_START do # define STMT_END while (0) # endif #endif #ifndef boolSV # define boolSV(b) ((b) ? &PL_sv_yes : &PL_sv_no) #endif /* DEFSV appears first in 5.004_56 */ #ifndef DEFSV # define DEFSV GvSV(PL_defgv) #endif #ifndef SAVE_DEFSV # define SAVE_DEFSV SAVESPTR(GvSV(PL_defgv)) #endif #ifndef DEFSV_set # define DEFSV_set(sv) (DEFSV = (sv)) #endif /* Older perls (<=5.003) lack AvFILLp */ #ifndef AvFILLp # define AvFILLp AvFILL #endif #ifndef ERRSV # define ERRSV get_sv("@",FALSE) #endif /* Hint: gv_stashpvn * This function's backport doesn't support the length parameter, but * rather ignores it. Portability can only be ensured if the length * parameter is used for speed reasons, but the length can always be * correctly computed from the string argument. */ #ifndef gv_stashpvn # define gv_stashpvn(str,len,create) gv_stashpv(str,create) #endif /* Replace: 1 */ #ifndef get_cv # define get_cv perl_get_cv #endif #ifndef get_sv # define get_sv perl_get_sv #endif #ifndef get_av # define get_av perl_get_av #endif #ifndef get_hv # define get_hv perl_get_hv #endif /* Replace: 0 */ #ifndef dUNDERBAR # define dUNDERBAR dNOOP #endif #ifndef UNDERBAR # define UNDERBAR DEFSV #endif #ifndef dAX # define dAX I32 ax = MARK - PL_stack_base + 1 #endif #ifndef dITEMS # define dITEMS I32 items = SP - MARK #endif #ifndef dXSTARG # define dXSTARG SV * targ = sv_newmortal() #endif #ifndef dAXMARK # define dAXMARK I32 ax = POPMARK; \ register SV ** const mark = PL_stack_base + ax++ #endif #ifndef XSprePUSH # define XSprePUSH (sp = PL_stack_base + ax - 1) #endif #if (PERL_BCDVERSION < 0x5005000) # undef XSRETURN # define XSRETURN(off) \ STMT_START { \ PL_stack_sp = PL_stack_base + ax + ((off) - 1); \ return; \ } STMT_END #endif #ifndef XSPROTO # define XSPROTO(name) void name(pTHX_ CV* cv) #endif #ifndef SVfARG # define SVfARG(p) ((void*)(p)) #endif #ifndef PERL_ABS # define PERL_ABS(x) ((x) < 0 ? -(x) : (x)) #endif #ifndef dVAR # define dVAR dNOOP #endif #ifndef SVf # define SVf "_" #endif #ifndef UTF8_MAXBYTES # define UTF8_MAXBYTES UTF8_MAXLEN #endif #ifndef CPERLscope # define CPERLscope(x) x #endif #ifndef PERL_HASH # define PERL_HASH(hash,str,len) \ STMT_START { \ const char *s_PeRlHaSh = str; \ I32 i_PeRlHaSh = len; \ U32 hash_PeRlHaSh = 0; \ while (i_PeRlHaSh--) \ hash_PeRlHaSh = hash_PeRlHaSh * 33 + *s_PeRlHaSh++; \ (hash) = hash_PeRlHaSh; \ } STMT_END #endif #ifndef PERLIO_FUNCS_DECL # ifdef PERLIO_FUNCS_CONST # define PERLIO_FUNCS_DECL(funcs) const PerlIO_funcs funcs # define PERLIO_FUNCS_CAST(funcs) (PerlIO_funcs*)(funcs) # else # define PERLIO_FUNCS_DECL(funcs) PerlIO_funcs funcs # define PERLIO_FUNCS_CAST(funcs) (funcs) # endif #endif /* provide these typedefs for older perls */ #if (PERL_BCDVERSION < 0x5009003) # ifdef ARGSproto typedef OP* (CPERLscope(*Perl_ppaddr_t))(ARGSproto); # else typedef OP* (CPERLscope(*Perl_ppaddr_t))(pTHX); # endif typedef OP* (CPERLscope(*Perl_check_t)) (pTHX_ OP*); #endif #ifndef isPSXSPC # define isPSXSPC(c) (isSPACE(c) || (c) == '\v') #endif #ifndef isBLANK # define isBLANK(c) ((c) == ' ' || (c) == '\t') #endif #ifdef EBCDIC #ifndef isALNUMC # define isALNUMC(c) isalnum(c) #endif #ifndef isASCII # define isASCII(c) isascii(c) #endif #ifndef isCNTRL # define isCNTRL(c) iscntrl(c) #endif #ifndef isGRAPH # define isGRAPH(c) isgraph(c) #endif #ifndef isPRINT # define isPRINT(c) isprint(c) #endif #ifndef isPUNCT # define isPUNCT(c) ispunct(c) #endif #ifndef isXDIGIT # define isXDIGIT(c) isxdigit(c) #endif #else # if (PERL_BCDVERSION < 0x5010000) /* Hint: isPRINT * The implementation in older perl versions includes all of the * isSPACE() characters, which is wrong. The version provided by * Devel::PPPort always overrides a present buggy version. */ # undef isPRINT # endif #ifndef isALNUMC # define isALNUMC(c) (isALPHA(c) || isDIGIT(c)) #endif #ifndef isASCII # define isASCII(c) ((c) <= 127) #endif #ifndef isCNTRL # define isCNTRL(c) ((c) < ' ' || (c) == 127) #endif #ifndef isGRAPH # define isGRAPH(c) (isALNUM(c) || isPUNCT(c)) #endif #ifndef isPRINT # define isPRINT(c) (((c) >= 32 && (c) < 127)) #endif #ifndef isPUNCT # define isPUNCT(c) (((c) >= 33 && (c) <= 47) || ((c) >= 58 && (c) <= 64) || ((c) >= 91 && (c) <= 96) || ((c) >= 123 && (c) <= 126)) #endif #ifndef isXDIGIT # define isXDIGIT(c) (isDIGIT(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) #endif #endif #ifndef PERL_SIGNALS_UNSAFE_FLAG #define PERL_SIGNALS_UNSAFE_FLAG 0x0001 #if (PERL_BCDVERSION < 0x5008000) # define D_PPP_PERL_SIGNALS_INIT PERL_SIGNALS_UNSAFE_FLAG #else # define D_PPP_PERL_SIGNALS_INIT 0 #endif #if defined(NEED_PL_signals) static U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT; #elif defined(NEED_PL_signals_GLOBAL) U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT; #else extern U32 DPPP_(my_PL_signals); #endif #define PL_signals DPPP_(my_PL_signals) #endif /* Hint: PL_ppaddr * Calling an op via PL_ppaddr requires passing a context argument * for threaded builds. Since the context argument is different for * 5.005 perls, you can use aTHXR (supplied by ppport.h), which will * automatically be defined as the correct argument. */ #if (PERL_BCDVERSION <= 0x5005005) /* Replace: 1 */ # define PL_ppaddr ppaddr # define PL_no_modify no_modify /* Replace: 0 */ #endif #if (PERL_BCDVERSION <= 0x5004005) /* Replace: 1 */ # define PL_DBsignal DBsignal # define PL_DBsingle DBsingle # define PL_DBsub DBsub # define PL_DBtrace DBtrace # define PL_Sv Sv # define PL_bufend bufend # define PL_bufptr bufptr # define PL_compiling compiling # define PL_copline copline # define PL_curcop curcop # define PL_curstash curstash # define PL_debstash debstash # define PL_defgv defgv # define PL_diehook diehook # define PL_dirty dirty # define PL_dowarn dowarn # define PL_errgv errgv # define PL_error_count error_count # define PL_expect expect # define PL_hexdigit hexdigit # define PL_hints hints # define PL_in_my in_my # define PL_laststatval laststatval # define PL_lex_state lex_state # define PL_lex_stuff lex_stuff # define PL_linestr linestr # define PL_na na # define PL_perl_destruct_level perl_destruct_level # define PL_perldb perldb # define PL_rsfp_filters rsfp_filters # define PL_rsfp rsfp # define PL_stack_base stack_base # define PL_stack_sp stack_sp # define PL_statcache statcache # define PL_stdingv stdingv # define PL_sv_arenaroot sv_arenaroot # define PL_sv_no sv_no # define PL_sv_undef sv_undef # define PL_sv_yes sv_yes # define PL_tainted tainted # define PL_tainting tainting # define PL_tokenbuf tokenbuf /* Replace: 0 */ #endif /* Warning: PL_parser * For perl versions earlier than 5.9.5, this is an always * non-NULL dummy. Also, it cannot be dereferenced. Don't * use it if you can avoid is and unless you absolutely know * what you're doing. * If you always check that PL_parser is non-NULL, you can * define DPPP_PL_parser_NO_DUMMY to avoid the creation of * a dummy parser structure. */ #if (PERL_BCDVERSION >= 0x5009005) # ifdef DPPP_PL_parser_NO_DUMMY # define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \ (croak("panic: PL_parser == NULL in %s:%d", \ __FILE__, __LINE__), (yy_parser *) NULL))->var) # else # ifdef DPPP_PL_parser_NO_DUMMY_WARNING # define D_PPP_parser_dummy_warning(var) # else # define D_PPP_parser_dummy_warning(var) \ warn("warning: dummy PL_" #var " used in %s:%d", __FILE__, __LINE__), # endif # define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \ (D_PPP_parser_dummy_warning(var) &DPPP_(dummy_PL_parser)))->var) #if defined(NEED_PL_parser) static yy_parser DPPP_(dummy_PL_parser); #elif defined(NEED_PL_parser_GLOBAL) yy_parser DPPP_(dummy_PL_parser); #else extern yy_parser DPPP_(dummy_PL_parser); #endif # endif /* PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf depends on PL_parser */ /* Warning: PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf * Do not use this variable unless you know exactly what you're * doint. It is internal to the perl parser and may change or even * be removed in the future. As of perl 5.9.5, you have to check * for (PL_parser != NULL) for this variable to have any effect. * An always non-NULL PL_parser dummy is provided for earlier * perl versions. * If PL_parser is NULL when you try to access this variable, a * dummy is being accessed instead and a warning is issued unless * you define DPPP_PL_parser_NO_DUMMY_WARNING. * If DPPP_PL_parser_NO_DUMMY is defined, the code trying to access * this variable will croak with a panic message. */ # define PL_expect D_PPP_my_PL_parser_var(expect) # define PL_copline D_PPP_my_PL_parser_var(copline) # define PL_rsfp D_PPP_my_PL_parser_var(rsfp) # define PL_rsfp_filters D_PPP_my_PL_parser_var(rsfp_filters) # define PL_linestr D_PPP_my_PL_parser_var(linestr) # define PL_bufptr D_PPP_my_PL_parser_var(bufptr) # define PL_bufend D_PPP_my_PL_parser_var(bufend) # define PL_lex_state D_PPP_my_PL_parser_var(lex_state) # define PL_lex_stuff D_PPP_my_PL_parser_var(lex_stuff) # define PL_tokenbuf D_PPP_my_PL_parser_var(tokenbuf) # define PL_in_my D_PPP_my_PL_parser_var(in_my) # define PL_in_my_stash D_PPP_my_PL_parser_var(in_my_stash) # define PL_error_count D_PPP_my_PL_parser_var(error_count) #else /* ensure that PL_parser != NULL and cannot be dereferenced */ # define PL_parser ((void *) 1) #endif #ifndef mPUSHs # define mPUSHs(s) PUSHs(sv_2mortal(s)) #endif #ifndef PUSHmortal # define PUSHmortal PUSHs(sv_newmortal()) #endif #ifndef mPUSHp # define mPUSHp(p,l) sv_setpvn(PUSHmortal, (p), (l)) #endif #ifndef mPUSHn # define mPUSHn(n) sv_setnv(PUSHmortal, (NV)(n)) #endif #ifndef mPUSHi # define mPUSHi(i) sv_setiv(PUSHmortal, (IV)(i)) #endif #ifndef mPUSHu # define mPUSHu(u) sv_setuv(PUSHmortal, (UV)(u)) #endif #ifndef mXPUSHs # define mXPUSHs(s) XPUSHs(sv_2mortal(s)) #endif #ifndef XPUSHmortal # define XPUSHmortal XPUSHs(sv_newmortal()) #endif #ifndef mXPUSHp # define mXPUSHp(p,l) STMT_START { EXTEND(sp,1); sv_setpvn(PUSHmortal, (p), (l)); } STMT_END #endif #ifndef mXPUSHn # define mXPUSHn(n) STMT_START { EXTEND(sp,1); sv_setnv(PUSHmortal, (NV)(n)); } STMT_END #endif #ifndef mXPUSHi # define mXPUSHi(i) STMT_START { EXTEND(sp,1); sv_setiv(PUSHmortal, (IV)(i)); } STMT_END #endif #ifndef mXPUSHu # define mXPUSHu(u) STMT_START { EXTEND(sp,1); sv_setuv(PUSHmortal, (UV)(u)); } STMT_END #endif /* Replace: 1 */ #ifndef call_sv # define call_sv perl_call_sv #endif #ifndef call_pv # define call_pv perl_call_pv #endif #ifndef call_argv # define call_argv perl_call_argv #endif #ifndef call_method # define call_method perl_call_method #endif #ifndef eval_sv # define eval_sv perl_eval_sv #endif /* Replace: 0 */ #ifndef PERL_LOADMOD_DENY # define PERL_LOADMOD_DENY 0x1 #endif #ifndef PERL_LOADMOD_NOIMPORT # define PERL_LOADMOD_NOIMPORT 0x2 #endif #ifndef PERL_LOADMOD_IMPORT_OPS # define PERL_LOADMOD_IMPORT_OPS 0x4 #endif #ifndef G_METHOD # define G_METHOD 64 # ifdef call_sv # undef call_sv # endif # if (PERL_BCDVERSION < 0x5006000) # define call_sv(sv, flags) ((flags) & G_METHOD ? perl_call_method((char *) SvPV_nolen_const(sv), \ (flags) & ~G_METHOD) : perl_call_sv(sv, flags)) # else # define call_sv(sv, flags) ((flags) & G_METHOD ? Perl_call_method(aTHX_ (char *) SvPV_nolen_const(sv), \ (flags) & ~G_METHOD) : Perl_call_sv(aTHX_ sv, flags)) # endif #endif /* Replace perl_eval_pv with eval_pv */ #ifndef eval_pv #if defined(NEED_eval_pv) static SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error); static #else extern SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error); #endif #ifdef eval_pv # undef eval_pv #endif #define eval_pv(a,b) DPPP_(my_eval_pv)(aTHX_ a,b) #define Perl_eval_pv DPPP_(my_eval_pv) #if defined(NEED_eval_pv) || defined(NEED_eval_pv_GLOBAL) SV* DPPP_(my_eval_pv)(char *p, I32 croak_on_error) { dSP; SV* sv = newSVpv(p, 0); PUSHMARK(sp); eval_sv(sv, G_SCALAR); SvREFCNT_dec(sv); SPAGAIN; sv = POPs; PUTBACK; if (croak_on_error && SvTRUE(GvSV(errgv))) croak(SvPVx(GvSV(errgv), na)); return sv; } #endif #endif #ifndef vload_module #if defined(NEED_vload_module) static void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args); static #else extern void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args); #endif #ifdef vload_module # undef vload_module #endif #define vload_module(a,b,c,d) DPPP_(my_vload_module)(aTHX_ a,b,c,d) #define Perl_vload_module DPPP_(my_vload_module) #if defined(NEED_vload_module) || defined(NEED_vload_module_GLOBAL) void DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args) { dTHR; dVAR; OP *veop, *imop; OP * const modname = newSVOP(OP_CONST, 0, name); /* 5.005 has a somewhat hacky force_normal that doesn't croak on SvREADONLY() if PL_compling is true. Current perls take care in ck_require() to correctly turn off SvREADONLY before calling force_normal_flags(). This seems a better fix than fudging PL_compling */ SvREADONLY_off(((SVOP*)modname)->op_sv); modname->op_private |= OPpCONST_BARE; if (ver) { veop = newSVOP(OP_CONST, 0, ver); } else veop = NULL; if (flags & PERL_LOADMOD_NOIMPORT) { imop = sawparens(newNULLLIST()); } else if (flags & PERL_LOADMOD_IMPORT_OPS) { imop = va_arg(*args, OP*); } else { SV *sv; imop = NULL; sv = va_arg(*args, SV*); while (sv) { imop = append_elem(OP_LIST, imop, newSVOP(OP_CONST, 0, sv)); sv = va_arg(*args, SV*); } } { const line_t ocopline = PL_copline; COP * const ocurcop = PL_curcop; const int oexpect = PL_expect; #if (PERL_BCDVERSION >= 0x5004000) utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(FALSE, 0), veop, modname, imop); #else utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(), modname, imop); #endif PL_expect = oexpect; PL_copline = ocopline; PL_curcop = ocurcop; } } #endif #endif #ifndef load_module #if defined(NEED_load_module) static void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...); static #else extern void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...); #endif #ifdef load_module # undef load_module #endif #define load_module DPPP_(my_load_module) #define Perl_load_module DPPP_(my_load_module) #if defined(NEED_load_module) || defined(NEED_load_module_GLOBAL) void DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...) { va_list args; va_start(args, ver); vload_module(flags, name, ver, &args); va_end(args); } #endif #endif #ifndef newRV_inc # define newRV_inc(sv) newRV(sv) /* Replace */ #endif #ifndef newRV_noinc #if defined(NEED_newRV_noinc) static SV * DPPP_(my_newRV_noinc)(SV *sv); static #else extern SV * DPPP_(my_newRV_noinc)(SV *sv); #endif #ifdef newRV_noinc # undef newRV_noinc #endif #define newRV_noinc(a) DPPP_(my_newRV_noinc)(aTHX_ a) #define Perl_newRV_noinc DPPP_(my_newRV_noinc) #if defined(NEED_newRV_noinc) || defined(NEED_newRV_noinc_GLOBAL) SV * DPPP_(my_newRV_noinc)(SV *sv) { SV *rv = (SV *)newRV(sv); SvREFCNT_dec(sv); return rv; } #endif #endif /* Hint: newCONSTSUB * Returns a CV* as of perl-5.7.1. This return value is not supported * by Devel::PPPort. */ /* newCONSTSUB from IO.xs is in the core starting with 5.004_63 */ #if (PERL_BCDVERSION < 0x5004063) && (PERL_BCDVERSION != 0x5004005) #if defined(NEED_newCONSTSUB) static void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv); static #else extern void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv); #endif #ifdef newCONSTSUB # undef newCONSTSUB #endif #define newCONSTSUB(a,b,c) DPPP_(my_newCONSTSUB)(aTHX_ a,b,c) #define Perl_newCONSTSUB DPPP_(my_newCONSTSUB) #if defined(NEED_newCONSTSUB) || defined(NEED_newCONSTSUB_GLOBAL) /* This is just a trick to avoid a dependency of newCONSTSUB on PL_parser */ /* (There's no PL_parser in perl < 5.005, so this is completely safe) */ #define D_PPP_PL_copline PL_copline void DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv) { U32 oldhints = PL_hints; HV *old_cop_stash = PL_curcop->cop_stash; HV *old_curstash = PL_curstash; line_t oldline = PL_curcop->cop_line; PL_curcop->cop_line = D_PPP_PL_copline; PL_hints &= ~HINT_BLOCK_SCOPE; if (stash) PL_curstash = PL_curcop->cop_stash = stash; newSUB( #if (PERL_BCDVERSION < 0x5003022) start_subparse(), #elif (PERL_BCDVERSION == 0x5003022) start_subparse(0), #else /* 5.003_23 onwards */ start_subparse(FALSE, 0), #endif newSVOP(OP_CONST, 0, newSVpv((char *) name, 0)), newSVOP(OP_CONST, 0, &PL_sv_no), /* SvPV(&PL_sv_no) == "" -- GMB */ newSTATEOP(0, Nullch, newSVOP(OP_CONST, 0, sv)) ); PL_hints = oldhints; PL_curcop->cop_stash = old_cop_stash; PL_curstash = old_curstash; PL_curcop->cop_line = oldline; } #endif #endif /* * Boilerplate macros for initializing and accessing interpreter-local * data from C. All statics in extensions should be reworked to use * this, if you want to make the extension thread-safe. See ext/re/re.xs * for an example of the use of these macros. * * Code that uses these macros is responsible for the following: * 1. #define MY_CXT_KEY to a unique string, e.g. "DynaLoader_guts" * 2. Declare a typedef named my_cxt_t that is a structure that contains * all the data that needs to be interpreter-local. * 3. Use the START_MY_CXT macro after the declaration of my_cxt_t. * 4. Use the MY_CXT_INIT macro such that it is called exactly once * (typically put in the BOOT: section). * 5. Use the members of the my_cxt_t structure everywhere as * MY_CXT.member. * 6. Use the dMY_CXT macro (a declaration) in all the functions that * access MY_CXT. */ #if defined(MULTIPLICITY) || defined(PERL_OBJECT) || \ defined(PERL_CAPI) || defined(PERL_IMPLICIT_CONTEXT) #ifndef START_MY_CXT /* This must appear in all extensions that define a my_cxt_t structure, * right after the definition (i.e. at file scope). The non-threads * case below uses it to declare the data as static. */ #define START_MY_CXT #if (PERL_BCDVERSION < 0x5004068) /* Fetches the SV that keeps the per-interpreter data. */ #define dMY_CXT_SV \ SV *my_cxt_sv = get_sv(MY_CXT_KEY, FALSE) #else /* >= perl5.004_68 */ #define dMY_CXT_SV \ SV *my_cxt_sv = *hv_fetch(PL_modglobal, MY_CXT_KEY, \ sizeof(MY_CXT_KEY)-1, TRUE) #endif /* < perl5.004_68 */ /* This declaration should be used within all functions that use the * interpreter-local data. */ #define dMY_CXT \ dMY_CXT_SV; \ my_cxt_t *my_cxtp = INT2PTR(my_cxt_t*,SvUV(my_cxt_sv)) /* Creates and zeroes the per-interpreter data. * (We allocate my_cxtp in a Perl SV so that it will be released when * the interpreter goes away.) */ #define MY_CXT_INIT \ dMY_CXT_SV; \ /* newSV() allocates one more than needed */ \ my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\ Zero(my_cxtp, 1, my_cxt_t); \ sv_setuv(my_cxt_sv, PTR2UV(my_cxtp)) /* This macro must be used to access members of the my_cxt_t structure. * e.g. MYCXT.some_data */ #define MY_CXT (*my_cxtp) /* Judicious use of these macros can reduce the number of times dMY_CXT * is used. Use is similar to pTHX, aTHX etc. */ #define pMY_CXT my_cxt_t *my_cxtp #define pMY_CXT_ pMY_CXT, #define _pMY_CXT ,pMY_CXT #define aMY_CXT my_cxtp #define aMY_CXT_ aMY_CXT, #define _aMY_CXT ,aMY_CXT #endif /* START_MY_CXT */ #ifndef MY_CXT_CLONE /* Clones the per-interpreter data. */ #define MY_CXT_CLONE \ dMY_CXT_SV; \ my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\ Copy(INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)), my_cxtp, 1, my_cxt_t);\ sv_setuv(my_cxt_sv, PTR2UV(my_cxtp)) #endif #else /* single interpreter */ #ifndef START_MY_CXT #define START_MY_CXT static my_cxt_t my_cxt; #define dMY_CXT_SV dNOOP #define dMY_CXT dNOOP #define MY_CXT_INIT NOOP #define MY_CXT my_cxt #define pMY_CXT void #define pMY_CXT_ #define _pMY_CXT #define aMY_CXT #define aMY_CXT_ #define _aMY_CXT #endif /* START_MY_CXT */ #ifndef MY_CXT_CLONE #define MY_CXT_CLONE NOOP #endif #endif #ifndef IVdf # if IVSIZE == LONGSIZE # define IVdf "ld" # define UVuf "lu" # define UVof "lo" # define UVxf "lx" # define UVXf "lX" # else # if IVSIZE == INTSIZE # define IVdf "d" # define UVuf "u" # define UVof "o" # define UVxf "x" # define UVXf "X" # endif # endif #endif #ifndef NVef # if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE) && \ defined(PERL_PRIfldbl) && (PERL_BCDVERSION != 0x5006000) /* Not very likely, but let's try anyway. */ # define NVef PERL_PRIeldbl # define NVff PERL_PRIfldbl # define NVgf PERL_PRIgldbl # else # define NVef "e" # define NVff "f" # define NVgf "g" # endif #endif #ifndef SvREFCNT_inc # ifdef PERL_USE_GCC_BRACE_GROUPS # define SvREFCNT_inc(sv) \ ({ \ SV * const _sv = (SV*)(sv); \ if (_sv) \ (SvREFCNT(_sv))++; \ _sv; \ }) # else # define SvREFCNT_inc(sv) \ ((PL_Sv=(SV*)(sv)) ? (++(SvREFCNT(PL_Sv)),PL_Sv) : NULL) # endif #endif #ifndef SvREFCNT_inc_simple # ifdef PERL_USE_GCC_BRACE_GROUPS # define SvREFCNT_inc_simple(sv) \ ({ \ if (sv) \ (SvREFCNT(sv))++; \ (SV *)(sv); \ }) # else # define SvREFCNT_inc_simple(sv) \ ((sv) ? (SvREFCNT(sv)++,(SV*)(sv)) : NULL) # endif #endif #ifndef SvREFCNT_inc_NN # ifdef PERL_USE_GCC_BRACE_GROUPS # define SvREFCNT_inc_NN(sv) \ ({ \ SV * const _sv = (SV*)(sv); \ SvREFCNT(_sv)++; \ _sv; \ }) # else # define SvREFCNT_inc_NN(sv) \ (PL_Sv=(SV*)(sv),++(SvREFCNT(PL_Sv)),PL_Sv) # endif #endif #ifndef SvREFCNT_inc_void # ifdef PERL_USE_GCC_BRACE_GROUPS # define SvREFCNT_inc_void(sv) \ ({ \ SV * const _sv = (SV*)(sv); \ if (_sv) \ (void)(SvREFCNT(_sv)++); \ }) # else # define SvREFCNT_inc_void(sv) \ (void)((PL_Sv=(SV*)(sv)) ? ++(SvREFCNT(PL_Sv)) : 0) # endif #endif #ifndef SvREFCNT_inc_simple_void # define SvREFCNT_inc_simple_void(sv) STMT_START { if (sv) SvREFCNT(sv)++; } STMT_END #endif #ifndef SvREFCNT_inc_simple_NN # define SvREFCNT_inc_simple_NN(sv) (++SvREFCNT(sv), (SV*)(sv)) #endif #ifndef SvREFCNT_inc_void_NN # define SvREFCNT_inc_void_NN(sv) (void)(++SvREFCNT((SV*)(sv))) #endif #ifndef SvREFCNT_inc_simple_void_NN # define SvREFCNT_inc_simple_void_NN(sv) (void)(++SvREFCNT((SV*)(sv))) #endif #ifndef newSV_type #if defined(NEED_newSV_type) static SV* DPPP_(my_newSV_type)(pTHX_ svtype const t); static #else extern SV* DPPP_(my_newSV_type)(pTHX_ svtype const t); #endif #ifdef newSV_type # undef newSV_type #endif #define newSV_type(a) DPPP_(my_newSV_type)(aTHX_ a) #define Perl_newSV_type DPPP_(my_newSV_type) #if defined(NEED_newSV_type) || defined(NEED_newSV_type_GLOBAL) SV* DPPP_(my_newSV_type)(pTHX_ svtype const t) { SV* const sv = newSV(0); sv_upgrade(sv, t); return sv; } #endif #endif #if (PERL_BCDVERSION < 0x5006000) # define D_PPP_CONSTPV_ARG(x) ((char *) (x)) #else # define D_PPP_CONSTPV_ARG(x) (x) #endif #ifndef newSVpvn # define newSVpvn(data,len) ((data) \ ? ((len) ? newSVpv((data), (len)) : newSVpv("", 0)) \ : newSV(0)) #endif #ifndef newSVpvn_utf8 # define newSVpvn_utf8(s, len, u) newSVpvn_flags((s), (len), (u) ? SVf_UTF8 : 0) #endif #ifndef SVf_UTF8 # define SVf_UTF8 0 #endif #ifndef newSVpvn_flags #if defined(NEED_newSVpvn_flags) static SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags); static #else extern SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags); #endif #ifdef newSVpvn_flags # undef newSVpvn_flags #endif #define newSVpvn_flags(a,b,c) DPPP_(my_newSVpvn_flags)(aTHX_ a,b,c) #define Perl_newSVpvn_flags DPPP_(my_newSVpvn_flags) #if defined(NEED_newSVpvn_flags) || defined(NEED_newSVpvn_flags_GLOBAL) SV * DPPP_(my_newSVpvn_flags)(pTHX_ const char *s, STRLEN len, U32 flags) { SV *sv = newSVpvn(D_PPP_CONSTPV_ARG(s), len); SvFLAGS(sv) |= (flags & SVf_UTF8); return (flags & SVs_TEMP) ? sv_2mortal(sv) : sv; } #endif #endif /* Backwards compatibility stuff... :-( */ #if !defined(NEED_sv_2pv_flags) && defined(NEED_sv_2pv_nolen) # define NEED_sv_2pv_flags #endif #if !defined(NEED_sv_2pv_flags_GLOBAL) && defined(NEED_sv_2pv_nolen_GLOBAL) # define NEED_sv_2pv_flags_GLOBAL #endif /* Hint: sv_2pv_nolen * Use the SvPV_nolen() or SvPV_nolen_const() macros instead of sv_2pv_nolen(). */ #ifndef sv_2pv_nolen # define sv_2pv_nolen(sv) SvPV_nolen(sv) #endif #ifdef SvPVbyte /* Hint: SvPVbyte * Does not work in perl-5.6.1, ppport.h implements a version * borrowed from perl-5.7.3. */ #if (PERL_BCDVERSION < 0x5007000) #if defined(NEED_sv_2pvbyte) static char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp); static #else extern char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp); #endif #ifdef sv_2pvbyte # undef sv_2pvbyte #endif #define sv_2pvbyte(a,b) DPPP_(my_sv_2pvbyte)(aTHX_ a,b) #define Perl_sv_2pvbyte DPPP_(my_sv_2pvbyte) #if defined(NEED_sv_2pvbyte) || defined(NEED_sv_2pvbyte_GLOBAL) char * DPPP_(my_sv_2pvbyte)(pTHX_ SV *sv, STRLEN *lp) { sv_utf8_downgrade(sv,0); return SvPV(sv,*lp); } #endif /* Hint: sv_2pvbyte * Use the SvPVbyte() macro instead of sv_2pvbyte(). */ #undef SvPVbyte #define SvPVbyte(sv, lp) \ ((SvFLAGS(sv) & (SVf_POK|SVf_UTF8)) == (SVf_POK) \ ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pvbyte(sv, &lp)) #endif #else # define SvPVbyte SvPV # define sv_2pvbyte sv_2pv #endif #ifndef sv_2pvbyte_nolen # define sv_2pvbyte_nolen(sv) sv_2pv_nolen(sv) #endif /* Hint: sv_pvn * Always use the SvPV() macro instead of sv_pvn(). */ /* Hint: sv_pvn_force * Always use the SvPV_force() macro instead of sv_pvn_force(). */ /* If these are undefined, they're not handled by the core anyway */ #ifndef SV_IMMEDIATE_UNREF # define SV_IMMEDIATE_UNREF 0 #endif #ifndef SV_GMAGIC # define SV_GMAGIC 0 #endif #ifndef SV_COW_DROP_PV # define SV_COW_DROP_PV 0 #endif #ifndef SV_UTF8_NO_ENCODING # define SV_UTF8_NO_ENCODING 0 #endif #ifndef SV_NOSTEAL # define SV_NOSTEAL 0 #endif #ifndef SV_CONST_RETURN # define SV_CONST_RETURN 0 #endif #ifndef SV_MUTABLE_RETURN # define SV_MUTABLE_RETURN 0 #endif #ifndef SV_SMAGIC # define SV_SMAGIC 0 #endif #ifndef SV_HAS_TRAILING_NUL # define SV_HAS_TRAILING_NUL 0 #endif #ifndef SV_COW_SHARED_HASH_KEYS # define SV_COW_SHARED_HASH_KEYS 0 #endif #if (PERL_BCDVERSION < 0x5007002) #if defined(NEED_sv_2pv_flags) static char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags); static #else extern char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags); #endif #ifdef sv_2pv_flags # undef sv_2pv_flags #endif #define sv_2pv_flags(a,b,c) DPPP_(my_sv_2pv_flags)(aTHX_ a,b,c) #define Perl_sv_2pv_flags DPPP_(my_sv_2pv_flags) #if defined(NEED_sv_2pv_flags) || defined(NEED_sv_2pv_flags_GLOBAL) char * DPPP_(my_sv_2pv_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags) { STRLEN n_a = (STRLEN) flags; return sv_2pv(sv, lp ? lp : &n_a); } #endif #if defined(NEED_sv_pvn_force_flags) static char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags); static #else extern char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags); #endif #ifdef sv_pvn_force_flags # undef sv_pvn_force_flags #endif #define sv_pvn_force_flags(a,b,c) DPPP_(my_sv_pvn_force_flags)(aTHX_ a,b,c) #define Perl_sv_pvn_force_flags DPPP_(my_sv_pvn_force_flags) #if defined(NEED_sv_pvn_force_flags) || defined(NEED_sv_pvn_force_flags_GLOBAL) char * DPPP_(my_sv_pvn_force_flags)(pTHX_ SV *sv, STRLEN *lp, I32 flags) { STRLEN n_a = (STRLEN) flags; return sv_pvn_force(sv, lp ? lp : &n_a); } #endif #endif #if (PERL_BCDVERSION < 0x5008008) || ( (PERL_BCDVERSION >= 0x5009000) && (PERL_BCDVERSION < 0x5009003) ) # define DPPP_SVPV_NOLEN_LP_ARG &PL_na #else # define DPPP_SVPV_NOLEN_LP_ARG 0 #endif #ifndef SvPV_const # define SvPV_const(sv, lp) SvPV_flags_const(sv, lp, SV_GMAGIC) #endif #ifndef SvPV_mutable # define SvPV_mutable(sv, lp) SvPV_flags_mutable(sv, lp, SV_GMAGIC) #endif #ifndef SvPV_flags # define SvPV_flags(sv, lp, flags) \ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pv_flags(sv, &lp, flags)) #endif #ifndef SvPV_flags_const # define SvPV_flags_const(sv, lp, flags) \ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ ? ((lp = SvCUR(sv)), SvPVX_const(sv)) : \ (const char*) sv_2pv_flags(sv, &lp, flags|SV_CONST_RETURN)) #endif #ifndef SvPV_flags_const_nolen # define SvPV_flags_const_nolen(sv, flags) \ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ ? SvPVX_const(sv) : \ (const char*) sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, flags|SV_CONST_RETURN)) #endif #ifndef SvPV_flags_mutable # define SvPV_flags_mutable(sv, lp, flags) \ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) : \ sv_2pv_flags(sv, &lp, flags|SV_MUTABLE_RETURN)) #endif #ifndef SvPV_force # define SvPV_force(sv, lp) SvPV_force_flags(sv, lp, SV_GMAGIC) #endif #ifndef SvPV_force_nolen # define SvPV_force_nolen(sv) SvPV_force_flags_nolen(sv, SV_GMAGIC) #endif #ifndef SvPV_force_mutable # define SvPV_force_mutable(sv, lp) SvPV_force_flags_mutable(sv, lp, SV_GMAGIC) #endif #ifndef SvPV_force_nomg # define SvPV_force_nomg(sv, lp) SvPV_force_flags(sv, lp, 0) #endif #ifndef SvPV_force_nomg_nolen # define SvPV_force_nomg_nolen(sv) SvPV_force_flags_nolen(sv, 0) #endif #ifndef SvPV_force_flags # define SvPV_force_flags(sv, lp, flags) \ ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \ ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_pvn_force_flags(sv, &lp, flags)) #endif #ifndef SvPV_force_flags_nolen # define SvPV_force_flags_nolen(sv, flags) \ ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \ ? SvPVX(sv) : sv_pvn_force_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, flags)) #endif #ifndef SvPV_force_flags_mutable # define SvPV_force_flags_mutable(sv, lp, flags) \ ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \ ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) \ : sv_pvn_force_flags(sv, &lp, flags|SV_MUTABLE_RETURN)) #endif #ifndef SvPV_nolen # define SvPV_nolen(sv) \ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ ? SvPVX(sv) : sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC)) #endif #ifndef SvPV_nolen_const # define SvPV_nolen_const(sv) \ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \ ? SvPVX_const(sv) : sv_2pv_flags(sv, DPPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC|SV_CONST_RETURN)) #endif #ifndef SvPV_nomg # define SvPV_nomg(sv, lp) SvPV_flags(sv, lp, 0) #endif #ifndef SvPV_nomg_const # define SvPV_nomg_const(sv, lp) SvPV_flags_const(sv, lp, 0) #endif #ifndef SvPV_nomg_const_nolen # define SvPV_nomg_const_nolen(sv) SvPV_flags_const_nolen(sv, 0) #endif #ifndef SvPV_renew # define SvPV_renew(sv,n) STMT_START { SvLEN_set(sv, n); \ SvPV_set((sv), (char *) saferealloc( \ (Malloc_t)SvPVX(sv), (MEM_SIZE)((n)))); \ } STMT_END #endif #ifndef SvMAGIC_set # define SvMAGIC_set(sv, val) \ STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \ (((XPVMG*) SvANY(sv))->xmg_magic = (val)); } STMT_END #endif #if (PERL_BCDVERSION < 0x5009003) #ifndef SvPVX_const # define SvPVX_const(sv) ((const char*) (0 + SvPVX(sv))) #endif #ifndef SvPVX_mutable # define SvPVX_mutable(sv) (0 + SvPVX(sv)) #endif #ifndef SvRV_set # define SvRV_set(sv, val) \ STMT_START { assert(SvTYPE(sv) >= SVt_RV); \ (((XRV*) SvANY(sv))->xrv_rv = (val)); } STMT_END #endif #else #ifndef SvPVX_const # define SvPVX_const(sv) ((const char*)((sv)->sv_u.svu_pv)) #endif #ifndef SvPVX_mutable # define SvPVX_mutable(sv) ((sv)->sv_u.svu_pv) #endif #ifndef SvRV_set # define SvRV_set(sv, val) \ STMT_START { assert(SvTYPE(sv) >= SVt_RV); \ ((sv)->sv_u.svu_rv = (val)); } STMT_END #endif #endif #ifndef SvSTASH_set # define SvSTASH_set(sv, val) \ STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \ (((XPVMG*) SvANY(sv))->xmg_stash = (val)); } STMT_END #endif #if (PERL_BCDVERSION < 0x5004000) #ifndef SvUV_set # define SvUV_set(sv, val) \ STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \ (((XPVIV*) SvANY(sv))->xiv_iv = (IV) (val)); } STMT_END #endif #else #ifndef SvUV_set # define SvUV_set(sv, val) \ STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \ (((XPVUV*) SvANY(sv))->xuv_uv = (val)); } STMT_END #endif #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(vnewSVpvf) #if defined(NEED_vnewSVpvf) static SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args); static #else extern SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args); #endif #ifdef vnewSVpvf # undef vnewSVpvf #endif #define vnewSVpvf(a,b) DPPP_(my_vnewSVpvf)(aTHX_ a,b) #define Perl_vnewSVpvf DPPP_(my_vnewSVpvf) #if defined(NEED_vnewSVpvf) || defined(NEED_vnewSVpvf_GLOBAL) SV * DPPP_(my_vnewSVpvf)(pTHX_ const char *pat, va_list *args) { register SV *sv = newSV(0); sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); return sv; } #endif #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf) # define sv_vcatpvf(sv, pat, args) sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)) #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf) # define sv_vsetpvf(sv, pat, args) sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)) #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg) #if defined(NEED_sv_catpvf_mg) static void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...); static #else extern void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...); #endif #define Perl_sv_catpvf_mg DPPP_(my_sv_catpvf_mg) #if defined(NEED_sv_catpvf_mg) || defined(NEED_sv_catpvf_mg_GLOBAL) void DPPP_(my_sv_catpvf_mg)(pTHX_ SV *sv, const char *pat, ...) { va_list args; va_start(args, pat); sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*)); SvSETMAGIC(sv); va_end(args); } #endif #endif #ifdef PERL_IMPLICIT_CONTEXT #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg_nocontext) #if defined(NEED_sv_catpvf_mg_nocontext) static void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...); static #else extern void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...); #endif #define sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext) #define Perl_sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext) #if defined(NEED_sv_catpvf_mg_nocontext) || defined(NEED_sv_catpvf_mg_nocontext_GLOBAL) void DPPP_(my_sv_catpvf_mg_nocontext)(SV *sv, const char *pat, ...) { dTHX; va_list args; va_start(args, pat); sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*)); SvSETMAGIC(sv); va_end(args); } #endif #endif #endif /* sv_catpvf_mg depends on sv_catpvf_mg_nocontext */ #ifndef sv_catpvf_mg # ifdef PERL_IMPLICIT_CONTEXT # define sv_catpvf_mg Perl_sv_catpvf_mg_nocontext # else # define sv_catpvf_mg Perl_sv_catpvf_mg # endif #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf_mg) # define sv_vcatpvf_mg(sv, pat, args) \ STMT_START { \ sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); \ SvSETMAGIC(sv); \ } STMT_END #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg) #if defined(NEED_sv_setpvf_mg) static void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...); static #else extern void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...); #endif #define Perl_sv_setpvf_mg DPPP_(my_sv_setpvf_mg) #if defined(NEED_sv_setpvf_mg) || defined(NEED_sv_setpvf_mg_GLOBAL) void DPPP_(my_sv_setpvf_mg)(pTHX_ SV *sv, const char *pat, ...) { va_list args; va_start(args, pat); sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*)); SvSETMAGIC(sv); va_end(args); } #endif #endif #ifdef PERL_IMPLICIT_CONTEXT #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg_nocontext) #if defined(NEED_sv_setpvf_mg_nocontext) static void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...); static #else extern void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...); #endif #define sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext) #define Perl_sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext) #if defined(NEED_sv_setpvf_mg_nocontext) || defined(NEED_sv_setpvf_mg_nocontext_GLOBAL) void DPPP_(my_sv_setpvf_mg_nocontext)(SV *sv, const char *pat, ...) { dTHX; va_list args; va_start(args, pat); sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*)); SvSETMAGIC(sv); va_end(args); } #endif #endif #endif /* sv_setpvf_mg depends on sv_setpvf_mg_nocontext */ #ifndef sv_setpvf_mg # ifdef PERL_IMPLICIT_CONTEXT # define sv_setpvf_mg Perl_sv_setpvf_mg_nocontext # else # define sv_setpvf_mg Perl_sv_setpvf_mg # endif #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf_mg) # define sv_vsetpvf_mg(sv, pat, args) \ STMT_START { \ sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); \ SvSETMAGIC(sv); \ } STMT_END #endif #ifndef newSVpvn_share #if defined(NEED_newSVpvn_share) static SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash); static #else extern SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash); #endif #ifdef newSVpvn_share # undef newSVpvn_share #endif #define newSVpvn_share(a,b,c) DPPP_(my_newSVpvn_share)(aTHX_ a,b,c) #define Perl_newSVpvn_share DPPP_(my_newSVpvn_share) #if defined(NEED_newSVpvn_share) || defined(NEED_newSVpvn_share_GLOBAL) SV * DPPP_(my_newSVpvn_share)(pTHX_ const char *src, I32 len, U32 hash) { SV *sv; if (len < 0) len = -len; if (!hash) PERL_HASH(hash, (char*) src, len); sv = newSVpvn((char *) src, len); sv_upgrade(sv, SVt_PVIV); SvIVX(sv) = hash; SvREADONLY_on(sv); SvPOK_on(sv); return sv; } #endif #endif #ifndef SvSHARED_HASH # define SvSHARED_HASH(sv) (0 + SvUVX(sv)) #endif #ifndef HvNAME_get # define HvNAME_get(hv) HvNAME(hv) #endif #ifndef HvNAMELEN_get # define HvNAMELEN_get(hv) (HvNAME_get(hv) ? (I32)strlen(HvNAME_get(hv)) : 0) #endif #ifndef GvSVn # define GvSVn(gv) GvSV(gv) #endif #ifndef isGV_with_GP # define isGV_with_GP(gv) isGV(gv) #endif #ifndef WARN_ALL # define WARN_ALL 0 #endif #ifndef WARN_CLOSURE # define WARN_CLOSURE 1 #endif #ifndef WARN_DEPRECATED # define WARN_DEPRECATED 2 #endif #ifndef WARN_EXITING # define WARN_EXITING 3 #endif #ifndef WARN_GLOB # define WARN_GLOB 4 #endif #ifndef WARN_IO # define WARN_IO 5 #endif #ifndef WARN_CLOSED # define WARN_CLOSED 6 #endif #ifndef WARN_EXEC # define WARN_EXEC 7 #endif #ifndef WARN_LAYER # define WARN_LAYER 8 #endif #ifndef WARN_NEWLINE # define WARN_NEWLINE 9 #endif #ifndef WARN_PIPE # define WARN_PIPE 10 #endif #ifndef WARN_UNOPENED # define WARN_UNOPENED 11 #endif #ifndef WARN_MISC # define WARN_MISC 12 #endif #ifndef WARN_NUMERIC # define WARN_NUMERIC 13 #endif #ifndef WARN_ONCE # define WARN_ONCE 14 #endif #ifndef WARN_OVERFLOW # define WARN_OVERFLOW 15 #endif #ifndef WARN_PACK # define WARN_PACK 16 #endif #ifndef WARN_PORTABLE # define WARN_PORTABLE 17 #endif #ifndef WARN_RECURSION # define WARN_RECURSION 18 #endif #ifndef WARN_REDEFINE # define WARN_REDEFINE 19 #endif #ifndef WARN_REGEXP # define WARN_REGEXP 20 #endif #ifndef WARN_SEVERE # define WARN_SEVERE 21 #endif #ifndef WARN_DEBUGGING # define WARN_DEBUGGING 22 #endif #ifndef WARN_INPLACE # define WARN_INPLACE 23 #endif #ifndef WARN_INTERNAL # define WARN_INTERNAL 24 #endif #ifndef WARN_MALLOC # define WARN_MALLOC 25 #endif #ifndef WARN_SIGNAL # define WARN_SIGNAL 26 #endif #ifndef WARN_SUBSTR # define WARN_SUBSTR 27 #endif #ifndef WARN_SYNTAX # define WARN_SYNTAX 28 #endif #ifndef WARN_AMBIGUOUS # define WARN_AMBIGUOUS 29 #endif #ifndef WARN_BAREWORD # define WARN_BAREWORD 30 #endif #ifndef WARN_DIGIT # define WARN_DIGIT 31 #endif #ifndef WARN_PARENTHESIS # define WARN_PARENTHESIS 32 #endif #ifndef WARN_PRECEDENCE # define WARN_PRECEDENCE 33 #endif #ifndef WARN_PRINTF # define WARN_PRINTF 34 #endif #ifndef WARN_PROTOTYPE # define WARN_PROTOTYPE 35 #endif #ifndef WARN_QW # define WARN_QW 36 #endif #ifndef WARN_RESERVED # define WARN_RESERVED 37 #endif #ifndef WARN_SEMICOLON # define WARN_SEMICOLON 38 #endif #ifndef WARN_TAINT # define WARN_TAINT 39 #endif #ifndef WARN_THREADS # define WARN_THREADS 40 #endif #ifndef WARN_UNINITIALIZED # define WARN_UNINITIALIZED 41 #endif #ifndef WARN_UNPACK # define WARN_UNPACK 42 #endif #ifndef WARN_UNTIE # define WARN_UNTIE 43 #endif #ifndef WARN_UTF8 # define WARN_UTF8 44 #endif #ifndef WARN_VOID # define WARN_VOID 45 #endif #ifndef WARN_ASSERTIONS # define WARN_ASSERTIONS 46 #endif #ifndef packWARN # define packWARN(a) (a) #endif #ifndef ckWARN # ifdef G_WARN_ON # define ckWARN(a) (PL_dowarn & G_WARN_ON) # else # define ckWARN(a) PL_dowarn # endif #endif #if (PERL_BCDVERSION >= 0x5004000) && !defined(warner) #if defined(NEED_warner) static void DPPP_(my_warner)(U32 err, const char *pat, ...); static #else extern void DPPP_(my_warner)(U32 err, const char *pat, ...); #endif #define Perl_warner DPPP_(my_warner) #if defined(NEED_warner) || defined(NEED_warner_GLOBAL) void DPPP_(my_warner)(U32 err, const char *pat, ...) { SV *sv; va_list args; PERL_UNUSED_ARG(err); va_start(args, pat); sv = vnewSVpvf(pat, &args); va_end(args); sv_2mortal(sv); warn("%s", SvPV_nolen(sv)); } #define warner Perl_warner #define Perl_warner_nocontext Perl_warner #endif #endif /* concatenating with "" ensures that only literal strings are accepted as argument * note that STR_WITH_LEN() can't be used as argument to macros or functions that * under some configurations might be macros */ #ifndef STR_WITH_LEN # define STR_WITH_LEN(s) (s ""), (sizeof(s)-1) #endif #ifndef newSVpvs # define newSVpvs(str) newSVpvn(str "", sizeof(str) - 1) #endif #ifndef newSVpvs_flags # define newSVpvs_flags(str, flags) newSVpvn_flags(str "", sizeof(str) - 1, flags) #endif #ifndef sv_catpvs # define sv_catpvs(sv, str) sv_catpvn(sv, str "", sizeof(str) - 1) #endif #ifndef sv_setpvs # define sv_setpvs(sv, str) sv_setpvn(sv, str "", sizeof(str) - 1) #endif #ifndef hv_fetchs # define hv_fetchs(hv, key, lval) hv_fetch(hv, key "", sizeof(key) - 1, lval) #endif #ifndef hv_stores # define hv_stores(hv, key, val) hv_store(hv, key "", sizeof(key) - 1, val, 0) #endif #ifndef gv_fetchpvn_flags # define gv_fetchpvn_flags(name, len, flags, svt) gv_fetchpv(name, flags, svt) #endif #ifndef gv_fetchpvs # define gv_fetchpvs(name, flags, svt) gv_fetchpvn_flags(name "", sizeof(name) - 1, flags, svt) #endif #ifndef gv_stashpvs # define gv_stashpvs(name, flags) gv_stashpvn(name "", sizeof(name) - 1, flags) #endif #ifndef SvGETMAGIC # define SvGETMAGIC(x) STMT_START { if (SvGMAGICAL(x)) mg_get(x); } STMT_END #endif #ifndef PERL_MAGIC_sv # define PERL_MAGIC_sv '\0' #endif #ifndef PERL_MAGIC_overload # define PERL_MAGIC_overload 'A' #endif #ifndef PERL_MAGIC_overload_elem # define PERL_MAGIC_overload_elem 'a' #endif #ifndef PERL_MAGIC_overload_table # define PERL_MAGIC_overload_table 'c' #endif #ifndef PERL_MAGIC_bm # define PERL_MAGIC_bm 'B' #endif #ifndef PERL_MAGIC_regdata # define PERL_MAGIC_regdata 'D' #endif #ifndef PERL_MAGIC_regdatum # define PERL_MAGIC_regdatum 'd' #endif #ifndef PERL_MAGIC_env # define PERL_MAGIC_env 'E' #endif #ifndef PERL_MAGIC_envelem # define PERL_MAGIC_envelem 'e' #endif #ifndef PERL_MAGIC_fm # define PERL_MAGIC_fm 'f' #endif #ifndef PERL_MAGIC_regex_global # define PERL_MAGIC_regex_global 'g' #endif #ifndef PERL_MAGIC_isa # define PERL_MAGIC_isa 'I' #endif #ifndef PERL_MAGIC_isaelem # define PERL_MAGIC_isaelem 'i' #endif #ifndef PERL_MAGIC_nkeys # define PERL_MAGIC_nkeys 'k' #endif #ifndef PERL_MAGIC_dbfile # define PERL_MAGIC_dbfile 'L' #endif #ifndef PERL_MAGIC_dbline # define PERL_MAGIC_dbline 'l' #endif #ifndef PERL_MAGIC_mutex # define PERL_MAGIC_mutex 'm' #endif #ifndef PERL_MAGIC_shared # define PERL_MAGIC_shared 'N' #endif #ifndef PERL_MAGIC_shared_scalar # define PERL_MAGIC_shared_scalar 'n' #endif #ifndef PERL_MAGIC_collxfrm # define PERL_MAGIC_collxfrm 'o' #endif #ifndef PERL_MAGIC_tied # define PERL_MAGIC_tied 'P' #endif #ifndef PERL_MAGIC_tiedelem # define PERL_MAGIC_tiedelem 'p' #endif #ifndef PERL_MAGIC_tiedscalar # define PERL_MAGIC_tiedscalar 'q' #endif #ifndef PERL_MAGIC_qr # define PERL_MAGIC_qr 'r' #endif #ifndef PERL_MAGIC_sig # define PERL_MAGIC_sig 'S' #endif #ifndef PERL_MAGIC_sigelem # define PERL_MAGIC_sigelem 's' #endif #ifndef PERL_MAGIC_taint # define PERL_MAGIC_taint 't' #endif #ifndef PERL_MAGIC_uvar # define PERL_MAGIC_uvar 'U' #endif #ifndef PERL_MAGIC_uvar_elem # define PERL_MAGIC_uvar_elem 'u' #endif #ifndef PERL_MAGIC_vstring # define PERL_MAGIC_vstring 'V' #endif #ifndef PERL_MAGIC_vec # define PERL_MAGIC_vec 'v' #endif #ifndef PERL_MAGIC_utf8 # define PERL_MAGIC_utf8 'w' #endif #ifndef PERL_MAGIC_substr # define PERL_MAGIC_substr 'x' #endif #ifndef PERL_MAGIC_defelem # define PERL_MAGIC_defelem 'y' #endif #ifndef PERL_MAGIC_glob # define PERL_MAGIC_glob '*' #endif #ifndef PERL_MAGIC_arylen # define PERL_MAGIC_arylen '#' #endif #ifndef PERL_MAGIC_pos # define PERL_MAGIC_pos '.' #endif #ifndef PERL_MAGIC_backref # define PERL_MAGIC_backref '<' #endif #ifndef PERL_MAGIC_ext # define PERL_MAGIC_ext '~' #endif /* That's the best we can do... */ #ifndef sv_catpvn_nomg # define sv_catpvn_nomg sv_catpvn #endif #ifndef sv_catsv_nomg # define sv_catsv_nomg sv_catsv #endif #ifndef sv_setsv_nomg # define sv_setsv_nomg sv_setsv #endif #ifndef sv_pvn_nomg # define sv_pvn_nomg sv_pvn #endif #ifndef SvIV_nomg # define SvIV_nomg SvIV #endif #ifndef SvUV_nomg # define SvUV_nomg SvUV #endif #ifndef sv_catpv_mg # define sv_catpv_mg(sv, ptr) \ STMT_START { \ SV *TeMpSv = sv; \ sv_catpv(TeMpSv,ptr); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_catpvn_mg # define sv_catpvn_mg(sv, ptr, len) \ STMT_START { \ SV *TeMpSv = sv; \ sv_catpvn(TeMpSv,ptr,len); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_catsv_mg # define sv_catsv_mg(dsv, ssv) \ STMT_START { \ SV *TeMpSv = dsv; \ sv_catsv(TeMpSv,ssv); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_setiv_mg # define sv_setiv_mg(sv, i) \ STMT_START { \ SV *TeMpSv = sv; \ sv_setiv(TeMpSv,i); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_setnv_mg # define sv_setnv_mg(sv, num) \ STMT_START { \ SV *TeMpSv = sv; \ sv_setnv(TeMpSv,num); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_setpv_mg # define sv_setpv_mg(sv, ptr) \ STMT_START { \ SV *TeMpSv = sv; \ sv_setpv(TeMpSv,ptr); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_setpvn_mg # define sv_setpvn_mg(sv, ptr, len) \ STMT_START { \ SV *TeMpSv = sv; \ sv_setpvn(TeMpSv,ptr,len); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_setsv_mg # define sv_setsv_mg(dsv, ssv) \ STMT_START { \ SV *TeMpSv = dsv; \ sv_setsv(TeMpSv,ssv); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_setuv_mg # define sv_setuv_mg(sv, i) \ STMT_START { \ SV *TeMpSv = sv; \ sv_setuv(TeMpSv,i); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef sv_usepvn_mg # define sv_usepvn_mg(sv, ptr, len) \ STMT_START { \ SV *TeMpSv = sv; \ sv_usepvn(TeMpSv,ptr,len); \ SvSETMAGIC(TeMpSv); \ } STMT_END #endif #ifndef SvVSTRING_mg # define SvVSTRING_mg(sv) (SvMAGICAL(sv) ? mg_find(sv, PERL_MAGIC_vstring) : NULL) #endif /* Hint: sv_magic_portable * This is a compatibility function that is only available with * Devel::PPPort. It is NOT in the perl core. * Its purpose is to mimic the 5.8.0 behaviour of sv_magic() when * it is being passed a name pointer with namlen == 0. In that * case, perl 5.8.0 and later store the pointer, not a copy of it. * The compatibility can be provided back to perl 5.004. With * earlier versions, the code will not compile. */ #if (PERL_BCDVERSION < 0x5004000) /* code that uses sv_magic_portable will not compile */ #elif (PERL_BCDVERSION < 0x5008000) # define sv_magic_portable(sv, obj, how, name, namlen) \ STMT_START { \ SV *SvMp_sv = (sv); \ char *SvMp_name = (char *) (name); \ I32 SvMp_namlen = (namlen); \ if (SvMp_name && SvMp_namlen == 0) \ { \ MAGIC *mg; \ sv_magic(SvMp_sv, obj, how, 0, 0); \ mg = SvMAGIC(SvMp_sv); \ mg->mg_len = -42; /* XXX: this is the tricky part */ \ mg->mg_ptr = SvMp_name; \ } \ else \ { \ sv_magic(SvMp_sv, obj, how, SvMp_name, SvMp_namlen); \ } \ } STMT_END #else # define sv_magic_portable(a, b, c, d, e) sv_magic(a, b, c, d, e) #endif #ifdef USE_ITHREADS #ifndef CopFILE # define CopFILE(c) ((c)->cop_file) #endif #ifndef CopFILEGV # define CopFILEGV(c) (CopFILE(c) ? gv_fetchfile(CopFILE(c)) : Nullgv) #endif #ifndef CopFILE_set # define CopFILE_set(c,pv) ((c)->cop_file = savepv(pv)) #endif #ifndef CopFILESV # define CopFILESV(c) (CopFILE(c) ? GvSV(gv_fetchfile(CopFILE(c))) : Nullsv) #endif #ifndef CopFILEAV # define CopFILEAV(c) (CopFILE(c) ? GvAV(gv_fetchfile(CopFILE(c))) : Nullav) #endif #ifndef CopSTASHPV # define CopSTASHPV(c) ((c)->cop_stashpv) #endif #ifndef CopSTASHPV_set # define CopSTASHPV_set(c,pv) ((c)->cop_stashpv = ((pv) ? savepv(pv) : Nullch)) #endif #ifndef CopSTASH # define CopSTASH(c) (CopSTASHPV(c) ? gv_stashpv(CopSTASHPV(c),GV_ADD) : Nullhv) #endif #ifndef CopSTASH_set # define CopSTASH_set(c,hv) CopSTASHPV_set(c, (hv) ? HvNAME(hv) : Nullch) #endif #ifndef CopSTASH_eq # define CopSTASH_eq(c,hv) ((hv) && (CopSTASHPV(c) == HvNAME(hv) \ || (CopSTASHPV(c) && HvNAME(hv) \ && strEQ(CopSTASHPV(c), HvNAME(hv))))) #endif #else #ifndef CopFILEGV # define CopFILEGV(c) ((c)->cop_filegv) #endif #ifndef CopFILEGV_set # define CopFILEGV_set(c,gv) ((c)->cop_filegv = (GV*)SvREFCNT_inc(gv)) #endif #ifndef CopFILE_set # define CopFILE_set(c,pv) CopFILEGV_set((c), gv_fetchfile(pv)) #endif #ifndef CopFILESV # define CopFILESV(c) (CopFILEGV(c) ? GvSV(CopFILEGV(c)) : Nullsv) #endif #ifndef CopFILEAV # define CopFILEAV(c) (CopFILEGV(c) ? GvAV(CopFILEGV(c)) : Nullav) #endif #ifndef CopFILE # define CopFILE(c) (CopFILESV(c) ? SvPVX(CopFILESV(c)) : Nullch) #endif #ifndef CopSTASH # define CopSTASH(c) ((c)->cop_stash) #endif #ifndef CopSTASH_set # define CopSTASH_set(c,hv) ((c)->cop_stash = (hv)) #endif #ifndef CopSTASHPV # define CopSTASHPV(c) (CopSTASH(c) ? HvNAME(CopSTASH(c)) : Nullch) #endif #ifndef CopSTASHPV_set # define CopSTASHPV_set(c,pv) CopSTASH_set((c), gv_stashpv(pv,GV_ADD)) #endif #ifndef CopSTASH_eq # define CopSTASH_eq(c,hv) (CopSTASH(c) == (hv)) #endif #endif /* USE_ITHREADS */ #ifndef IN_PERL_COMPILETIME # define IN_PERL_COMPILETIME (PL_curcop == &PL_compiling) #endif #ifndef IN_LOCALE_RUNTIME # define IN_LOCALE_RUNTIME (PL_curcop->op_private & HINT_LOCALE) #endif #ifndef IN_LOCALE_COMPILETIME # define IN_LOCALE_COMPILETIME (PL_hints & HINT_LOCALE) #endif #ifndef IN_LOCALE # define IN_LOCALE (IN_PERL_COMPILETIME ? IN_LOCALE_COMPILETIME : IN_LOCALE_RUNTIME) #endif #ifndef IS_NUMBER_IN_UV # define IS_NUMBER_IN_UV 0x01 #endif #ifndef IS_NUMBER_GREATER_THAN_UV_MAX # define IS_NUMBER_GREATER_THAN_UV_MAX 0x02 #endif #ifndef IS_NUMBER_NOT_INT # define IS_NUMBER_NOT_INT 0x04 #endif #ifndef IS_NUMBER_NEG # define IS_NUMBER_NEG 0x08 #endif #ifndef IS_NUMBER_INFINITY # define IS_NUMBER_INFINITY 0x10 #endif #ifndef IS_NUMBER_NAN # define IS_NUMBER_NAN 0x20 #endif #ifndef GROK_NUMERIC_RADIX # define GROK_NUMERIC_RADIX(sp, send) grok_numeric_radix(sp, send) #endif #ifndef PERL_SCAN_GREATER_THAN_UV_MAX # define PERL_SCAN_GREATER_THAN_UV_MAX 0x02 #endif #ifndef PERL_SCAN_SILENT_ILLDIGIT # define PERL_SCAN_SILENT_ILLDIGIT 0x04 #endif #ifndef PERL_SCAN_ALLOW_UNDERSCORES # define PERL_SCAN_ALLOW_UNDERSCORES 0x01 #endif #ifndef PERL_SCAN_DISALLOW_PREFIX # define PERL_SCAN_DISALLOW_PREFIX 0x02 #endif #ifndef grok_numeric_radix #if defined(NEED_grok_numeric_radix) static bool DPPP_(my_grok_numeric_radix)(pTHX_ const char ** sp, const char * send); static #else extern bool DPPP_(my_grok_numeric_radix)(pTHX_ const char ** sp, const char * send); #endif #ifdef grok_numeric_radix # undef grok_numeric_radix #endif #define grok_numeric_radix(a,b) DPPP_(my_grok_numeric_radix)(aTHX_ a,b) #define Perl_grok_numeric_radix DPPP_(my_grok_numeric_radix) #if defined(NEED_grok_numeric_radix) || defined(NEED_grok_numeric_radix_GLOBAL) bool DPPP_(my_grok_numeric_radix)(pTHX_ const char **sp, const char *send) { #ifdef USE_LOCALE_NUMERIC #ifdef PL_numeric_radix_sv if (PL_numeric_radix_sv && IN_LOCALE) { STRLEN len; char* radix = SvPV(PL_numeric_radix_sv, len); if (*sp + len <= send && memEQ(*sp, radix, len)) { *sp += len; return TRUE; } } #else /* older perls don't have PL_numeric_radix_sv so the radix * must manually be requested from locale.h */ #include dTHR; /* needed for older threaded perls */ struct lconv *lc = localeconv(); char *radix = lc->decimal_point; if (radix && IN_LOCALE) { STRLEN len = strlen(radix); if (*sp + len <= send && memEQ(*sp, radix, len)) { *sp += len; return TRUE; } } #endif #endif /* USE_LOCALE_NUMERIC */ /* always try "." if numeric radix didn't match because * we may have data from different locales mixed */ if (*sp < send && **sp == '.') { ++*sp; return TRUE; } return FALSE; } #endif #endif #ifndef grok_number #if defined(NEED_grok_number) static int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep); static #else extern int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep); #endif #ifdef grok_number # undef grok_number #endif #define grok_number(a,b,c) DPPP_(my_grok_number)(aTHX_ a,b,c) #define Perl_grok_number DPPP_(my_grok_number) #if defined(NEED_grok_number) || defined(NEED_grok_number_GLOBAL) int DPPP_(my_grok_number)(pTHX_ const char *pv, STRLEN len, UV *valuep) { const char *s = pv; const char *send = pv + len; const UV max_div_10 = UV_MAX / 10; const char max_mod_10 = UV_MAX % 10; int numtype = 0; int sawinf = 0; int sawnan = 0; while (s < send && isSPACE(*s)) s++; if (s == send) { return 0; } else if (*s == '-') { s++; numtype = IS_NUMBER_NEG; } else if (*s == '+') s++; if (s == send) return 0; /* next must be digit or the radix separator or beginning of infinity */ if (isDIGIT(*s)) { /* UVs are at least 32 bits, so the first 9 decimal digits cannot overflow. */ UV value = *s - '0'; /* This construction seems to be more optimiser friendly. (without it gcc does the isDIGIT test and the *s - '0' separately) With it gcc on arm is managing 6 instructions (6 cycles) per digit. In theory the optimiser could deduce how far to unroll the loop before checking for overflow. */ if (++s < send) { int digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { digit = *s - '0'; if (digit >= 0 && digit <= 9) { value = value * 10 + digit; if (++s < send) { /* Now got 9 digits, so need to check each time for overflow. */ digit = *s - '0'; while (digit >= 0 && digit <= 9 && (value < max_div_10 || (value == max_div_10 && digit <= max_mod_10))) { value = value * 10 + digit; if (++s < send) digit = *s - '0'; else break; } if (digit >= 0 && digit <= 9 && (s < send)) { /* value overflowed. skip the remaining digits, don't worry about setting *valuep. */ do { s++; } while (s < send && isDIGIT(*s)); numtype |= IS_NUMBER_GREATER_THAN_UV_MAX; goto skip_value; } } } } } } } } } } } } } } } } } } numtype |= IS_NUMBER_IN_UV; if (valuep) *valuep = value; skip_value: if (GROK_NUMERIC_RADIX(&s, send)) { numtype |= IS_NUMBER_NOT_INT; while (s < send && isDIGIT(*s)) /* optional digits after the radix */ s++; } } else if (GROK_NUMERIC_RADIX(&s, send)) { numtype |= IS_NUMBER_NOT_INT | IS_NUMBER_IN_UV; /* valuep assigned below */ /* no digits before the radix means we need digits after it */ if (s < send && isDIGIT(*s)) { do { s++; } while (s < send && isDIGIT(*s)); if (valuep) { /* integer approximation is valid - it's 0. */ *valuep = 0; } } else return 0; } else if (*s == 'I' || *s == 'i') { s++; if (s == send || (*s != 'N' && *s != 'n')) return 0; s++; if (s == send || (*s != 'F' && *s != 'f')) return 0; s++; if (s < send && (*s == 'I' || *s == 'i')) { s++; if (s == send || (*s != 'N' && *s != 'n')) return 0; s++; if (s == send || (*s != 'I' && *s != 'i')) return 0; s++; if (s == send || (*s != 'T' && *s != 't')) return 0; s++; if (s == send || (*s != 'Y' && *s != 'y')) return 0; s++; } sawinf = 1; } else if (*s == 'N' || *s == 'n') { /* XXX TODO: There are signaling NaNs and quiet NaNs. */ s++; if (s == send || (*s != 'A' && *s != 'a')) return 0; s++; if (s == send || (*s != 'N' && *s != 'n')) return 0; s++; sawnan = 1; } else return 0; if (sawinf) { numtype &= IS_NUMBER_NEG; /* Keep track of sign */ numtype |= IS_NUMBER_INFINITY | IS_NUMBER_NOT_INT; } else if (sawnan) { numtype &= IS_NUMBER_NEG; /* Keep track of sign */ numtype |= IS_NUMBER_NAN | IS_NUMBER_NOT_INT; } else if (s < send) { /* we can have an optional exponent part */ if (*s == 'e' || *s == 'E') { /* The only flag we keep is sign. Blow away any "it's UV" */ numtype &= IS_NUMBER_NEG; numtype |= IS_NUMBER_NOT_INT; s++; if (s < send && (*s == '-' || *s == '+')) s++; if (s < send && isDIGIT(*s)) { do { s++; } while (s < send && isDIGIT(*s)); } else return 0; } } while (s < send && isSPACE(*s)) s++; if (s >= send) return numtype; if (len == 10 && memEQ(pv, "0 but true", 10)) { if (valuep) *valuep = 0; return IS_NUMBER_IN_UV; } return 0; } #endif #endif /* * The grok_* routines have been modified to use warn() instead of * Perl_warner(). Also, 'hexdigit' was the former name of PL_hexdigit, * which is why the stack variable has been renamed to 'xdigit'. */ #ifndef grok_bin #if defined(NEED_grok_bin) static UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); static #else extern UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); #endif #ifdef grok_bin # undef grok_bin #endif #define grok_bin(a,b,c,d) DPPP_(my_grok_bin)(aTHX_ a,b,c,d) #define Perl_grok_bin DPPP_(my_grok_bin) #if defined(NEED_grok_bin) || defined(NEED_grok_bin_GLOBAL) UV DPPP_(my_grok_bin)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result) { const char *s = start; STRLEN len = *len_p; UV value = 0; NV value_nv = 0; const UV max_div_2 = UV_MAX / 2; bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES; bool overflowed = FALSE; if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) { /* strip off leading b or 0b. for compatibility silently suffer "b" and "0b" as valid binary numbers. */ if (len >= 1) { if (s[0] == 'b') { s++; len--; } else if (len >= 2 && s[0] == '0' && s[1] == 'b') { s+=2; len-=2; } } } for (; len-- && *s; s++) { char bit = *s; if (bit == '0' || bit == '1') { /* Write it in this wonky order with a goto to attempt to get the compiler to make the common case integer-only loop pretty tight. With gcc seems to be much straighter code than old scan_bin. */ redo: if (!overflowed) { if (value <= max_div_2) { value = (value << 1) | (bit - '0'); continue; } /* Bah. We're just overflowed. */ warn("Integer overflow in binary number"); overflowed = TRUE; value_nv = (NV) value; } value_nv *= 2.0; /* If an NV has not enough bits in its mantissa to * represent a UV this summing of small low-order numbers * is a waste of time (because the NV cannot preserve * the low-order bits anyway): we could just remember when * did we overflow and in the end just multiply value_nv by the * right amount. */ value_nv += (NV)(bit - '0'); continue; } if (bit == '_' && len && allow_underscores && (bit = s[1]) && (bit == '0' || bit == '1')) { --len; ++s; goto redo; } if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT)) warn("Illegal binary digit '%c' ignored", *s); break; } if ( ( overflowed && value_nv > 4294967295.0) #if UVSIZE > 4 || (!overflowed && value > 0xffffffff ) #endif ) { warn("Binary number > 0b11111111111111111111111111111111 non-portable"); } *len_p = s - start; if (!overflowed) { *flags = 0; return value; } *flags = PERL_SCAN_GREATER_THAN_UV_MAX; if (result) *result = value_nv; return UV_MAX; } #endif #endif #ifndef grok_hex #if defined(NEED_grok_hex) static UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); static #else extern UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); #endif #ifdef grok_hex # undef grok_hex #endif #define grok_hex(a,b,c,d) DPPP_(my_grok_hex)(aTHX_ a,b,c,d) #define Perl_grok_hex DPPP_(my_grok_hex) #if defined(NEED_grok_hex) || defined(NEED_grok_hex_GLOBAL) UV DPPP_(my_grok_hex)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result) { const char *s = start; STRLEN len = *len_p; UV value = 0; NV value_nv = 0; const UV max_div_16 = UV_MAX / 16; bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES; bool overflowed = FALSE; const char *xdigit; if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) { /* strip off leading x or 0x. for compatibility silently suffer "x" and "0x" as valid hex numbers. */ if (len >= 1) { if (s[0] == 'x') { s++; len--; } else if (len >= 2 && s[0] == '0' && s[1] == 'x') { s+=2; len-=2; } } } for (; len-- && *s; s++) { xdigit = strchr((char *) PL_hexdigit, *s); if (xdigit) { /* Write it in this wonky order with a goto to attempt to get the compiler to make the common case integer-only loop pretty tight. With gcc seems to be much straighter code than old scan_hex. */ redo: if (!overflowed) { if (value <= max_div_16) { value = (value << 4) | ((xdigit - PL_hexdigit) & 15); continue; } warn("Integer overflow in hexadecimal number"); overflowed = TRUE; value_nv = (NV) value; } value_nv *= 16.0; /* If an NV has not enough bits in its mantissa to * represent a UV this summing of small low-order numbers * is a waste of time (because the NV cannot preserve * the low-order bits anyway): we could just remember when * did we overflow and in the end just multiply value_nv by the * right amount of 16-tuples. */ value_nv += (NV)((xdigit - PL_hexdigit) & 15); continue; } if (*s == '_' && len && allow_underscores && s[1] && (xdigit = strchr((char *) PL_hexdigit, s[1]))) { --len; ++s; goto redo; } if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT)) warn("Illegal hexadecimal digit '%c' ignored", *s); break; } if ( ( overflowed && value_nv > 4294967295.0) #if UVSIZE > 4 || (!overflowed && value > 0xffffffff ) #endif ) { warn("Hexadecimal number > 0xffffffff non-portable"); } *len_p = s - start; if (!overflowed) { *flags = 0; return value; } *flags = PERL_SCAN_GREATER_THAN_UV_MAX; if (result) *result = value_nv; return UV_MAX; } #endif #endif #ifndef grok_oct #if defined(NEED_grok_oct) static UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); static #else extern UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result); #endif #ifdef grok_oct # undef grok_oct #endif #define grok_oct(a,b,c,d) DPPP_(my_grok_oct)(aTHX_ a,b,c,d) #define Perl_grok_oct DPPP_(my_grok_oct) #if defined(NEED_grok_oct) || defined(NEED_grok_oct_GLOBAL) UV DPPP_(my_grok_oct)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result) { const char *s = start; STRLEN len = *len_p; UV value = 0; NV value_nv = 0; const UV max_div_8 = UV_MAX / 8; bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES; bool overflowed = FALSE; for (; len-- && *s; s++) { /* gcc 2.95 optimiser not smart enough to figure that this subtraction out front allows slicker code. */ int digit = *s - '0'; if (digit >= 0 && digit <= 7) { /* Write it in this wonky order with a goto to attempt to get the compiler to make the common case integer-only loop pretty tight. */ redo: if (!overflowed) { if (value <= max_div_8) { value = (value << 3) | digit; continue; } /* Bah. We're just overflowed. */ warn("Integer overflow in octal number"); overflowed = TRUE; value_nv = (NV) value; } value_nv *= 8.0; /* If an NV has not enough bits in its mantissa to * represent a UV this summing of small low-order numbers * is a waste of time (because the NV cannot preserve * the low-order bits anyway): we could just remember when * did we overflow and in the end just multiply value_nv by the * right amount of 8-tuples. */ value_nv += (NV)digit; continue; } if (digit == ('_' - '0') && len && allow_underscores && (digit = s[1] - '0') && (digit >= 0 && digit <= 7)) { --len; ++s; goto redo; } /* Allow \octal to work the DWIM way (that is, stop scanning * as soon as non-octal characters are seen, complain only iff * someone seems to want to use the digits eight and nine). */ if (digit == 8 || digit == 9) { if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT)) warn("Illegal octal digit '%c' ignored", *s); } break; } if ( ( overflowed && value_nv > 4294967295.0) #if UVSIZE > 4 || (!overflowed && value > 0xffffffff ) #endif ) { warn("Octal number > 037777777777 non-portable"); } *len_p = s - start; if (!overflowed) { *flags = 0; return value; } *flags = PERL_SCAN_GREATER_THAN_UV_MAX; if (result) *result = value_nv; return UV_MAX; } #endif #endif #if !defined(my_snprintf) #if defined(NEED_my_snprintf) static int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...); static #else extern int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...); #endif #define my_snprintf DPPP_(my_my_snprintf) #define Perl_my_snprintf DPPP_(my_my_snprintf) #if defined(NEED_my_snprintf) || defined(NEED_my_snprintf_GLOBAL) int DPPP_(my_my_snprintf)(char *buffer, const Size_t len, const char *format, ...) { dTHX; int retval; va_list ap; va_start(ap, format); #ifdef HAS_VSNPRINTF retval = vsnprintf(buffer, len, format, ap); #else retval = vsprintf(buffer, format, ap); #endif va_end(ap); if (retval < 0 || (len > 0 && (Size_t)retval >= len)) Perl_croak(aTHX_ "panic: my_snprintf buffer overflow"); return retval; } #endif #endif #if !defined(my_sprintf) #if defined(NEED_my_sprintf) static int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...); static #else extern int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...); #endif #define my_sprintf DPPP_(my_my_sprintf) #define Perl_my_sprintf DPPP_(my_my_sprintf) #if defined(NEED_my_sprintf) || defined(NEED_my_sprintf_GLOBAL) int DPPP_(my_my_sprintf)(char *buffer, const char* pat, ...) { va_list args; va_start(args, pat); vsprintf(buffer, pat, args); va_end(args); return strlen(buffer); } #endif #endif #ifdef NO_XSLOCKS # ifdef dJMPENV # define dXCPT dJMPENV; int rEtV = 0 # define XCPT_TRY_START JMPENV_PUSH(rEtV); if (rEtV == 0) # define XCPT_TRY_END JMPENV_POP; # define XCPT_CATCH if (rEtV != 0) # define XCPT_RETHROW JMPENV_JUMP(rEtV) # else # define dXCPT Sigjmp_buf oldTOP; int rEtV = 0 # define XCPT_TRY_START Copy(top_env, oldTOP, 1, Sigjmp_buf); rEtV = Sigsetjmp(top_env, 1); if (rEtV == 0) # define XCPT_TRY_END Copy(oldTOP, top_env, 1, Sigjmp_buf); # define XCPT_CATCH if (rEtV != 0) # define XCPT_RETHROW Siglongjmp(top_env, rEtV) # endif #endif #if !defined(my_strlcat) #if defined(NEED_my_strlcat) static Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size); static #else extern Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size); #endif #define my_strlcat DPPP_(my_my_strlcat) #define Perl_my_strlcat DPPP_(my_my_strlcat) #if defined(NEED_my_strlcat) || defined(NEED_my_strlcat_GLOBAL) Size_t DPPP_(my_my_strlcat)(char *dst, const char *src, Size_t size) { Size_t used, length, copy; used = strlen(dst); length = strlen(src); if (size > 0 && used < size - 1) { copy = (length >= size - used) ? size - used - 1 : length; memcpy(dst + used, src, copy); dst[used + copy] = '\0'; } return used + length; } #endif #endif #if !defined(my_strlcpy) #if defined(NEED_my_strlcpy) static Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size); static #else extern Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size); #endif #define my_strlcpy DPPP_(my_my_strlcpy) #define Perl_my_strlcpy DPPP_(my_my_strlcpy) #if defined(NEED_my_strlcpy) || defined(NEED_my_strlcpy_GLOBAL) Size_t DPPP_(my_my_strlcpy)(char *dst, const char *src, Size_t size) { Size_t length, copy; length = strlen(src); if (size > 0) { copy = (length >= size) ? size - 1 : length; memcpy(dst, src, copy); dst[copy] = '\0'; } return length; } #endif #endif #ifndef PERL_PV_ESCAPE_QUOTE # define PERL_PV_ESCAPE_QUOTE 0x0001 #endif #ifndef PERL_PV_PRETTY_QUOTE # define PERL_PV_PRETTY_QUOTE PERL_PV_ESCAPE_QUOTE #endif #ifndef PERL_PV_PRETTY_ELLIPSES # define PERL_PV_PRETTY_ELLIPSES 0x0002 #endif #ifndef PERL_PV_PRETTY_LTGT # define PERL_PV_PRETTY_LTGT 0x0004 #endif #ifndef PERL_PV_ESCAPE_FIRSTCHAR # define PERL_PV_ESCAPE_FIRSTCHAR 0x0008 #endif #ifndef PERL_PV_ESCAPE_UNI # define PERL_PV_ESCAPE_UNI 0x0100 #endif #ifndef PERL_PV_ESCAPE_UNI_DETECT # define PERL_PV_ESCAPE_UNI_DETECT 0x0200 #endif #ifndef PERL_PV_ESCAPE_ALL # define PERL_PV_ESCAPE_ALL 0x1000 #endif #ifndef PERL_PV_ESCAPE_NOBACKSLASH # define PERL_PV_ESCAPE_NOBACKSLASH 0x2000 #endif #ifndef PERL_PV_ESCAPE_NOCLEAR # define PERL_PV_ESCAPE_NOCLEAR 0x4000 #endif #ifndef PERL_PV_ESCAPE_RE # define PERL_PV_ESCAPE_RE 0x8000 #endif #ifndef PERL_PV_PRETTY_NOCLEAR # define PERL_PV_PRETTY_NOCLEAR PERL_PV_ESCAPE_NOCLEAR #endif #ifndef PERL_PV_PRETTY_DUMP # define PERL_PV_PRETTY_DUMP PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_QUOTE #endif #ifndef PERL_PV_PRETTY_REGPROP # define PERL_PV_PRETTY_REGPROP PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_LTGT|PERL_PV_ESCAPE_RE #endif /* Hint: pv_escape * Note that unicode functionality is only backported to * those perl versions that support it. For older perl * versions, the implementation will fall back to bytes. */ #ifndef pv_escape #if defined(NEED_pv_escape) static char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags); static #else extern char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags); #endif #ifdef pv_escape # undef pv_escape #endif #define pv_escape(a,b,c,d,e,f) DPPP_(my_pv_escape)(aTHX_ a,b,c,d,e,f) #define Perl_pv_escape DPPP_(my_pv_escape) #if defined(NEED_pv_escape) || defined(NEED_pv_escape_GLOBAL) char * DPPP_(my_pv_escape)(pTHX_ SV *dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags) { const char esc = flags & PERL_PV_ESCAPE_RE ? '%' : '\\'; const char dq = flags & PERL_PV_ESCAPE_QUOTE ? '"' : esc; char octbuf[32] = "%123456789ABCDF"; STRLEN wrote = 0; STRLEN chsize = 0; STRLEN readsize = 1; #if defined(is_utf8_string) && defined(utf8_to_uvchr) bool isuni = flags & PERL_PV_ESCAPE_UNI ? 1 : 0; #endif const char *pv = str; const char * const end = pv + count; octbuf[0] = esc; if (!(flags & PERL_PV_ESCAPE_NOCLEAR)) sv_setpvs(dsv, ""); #if defined(is_utf8_string) && defined(utf8_to_uvchr) if ((flags & PERL_PV_ESCAPE_UNI_DETECT) && is_utf8_string((U8*)pv, count)) isuni = 1; #endif for (; pv < end && (!max || wrote < max) ; pv += readsize) { const UV u = #if defined(is_utf8_string) && defined(utf8_to_uvchr) isuni ? utf8_to_uvchr((U8*)pv, &readsize) : #endif (U8)*pv; const U8 c = (U8)u & 0xFF; if (u > 255 || (flags & PERL_PV_ESCAPE_ALL)) { if (flags & PERL_PV_ESCAPE_FIRSTCHAR) chsize = my_snprintf(octbuf, sizeof octbuf, "%"UVxf, u); else chsize = my_snprintf(octbuf, sizeof octbuf, "%cx{%"UVxf"}", esc, u); } else if (flags & PERL_PV_ESCAPE_NOBACKSLASH) { chsize = 1; } else { if (c == dq || c == esc || !isPRINT(c)) { chsize = 2; switch (c) { case '\\' : /* fallthrough */ case '%' : if (c == esc) octbuf[1] = esc; else chsize = 1; break; case '\v' : octbuf[1] = 'v'; break; case '\t' : octbuf[1] = 't'; break; case '\r' : octbuf[1] = 'r'; break; case '\n' : octbuf[1] = 'n'; break; case '\f' : octbuf[1] = 'f'; break; case '"' : if (dq == '"') octbuf[1] = '"'; else chsize = 1; break; default: chsize = my_snprintf(octbuf, sizeof octbuf, pv < end && isDIGIT((U8)*(pv+readsize)) ? "%c%03o" : "%c%o", esc, c); } } else { chsize = 1; } } if (max && wrote + chsize > max) { break; } else if (chsize > 1) { sv_catpvn(dsv, octbuf, chsize); wrote += chsize; } else { char tmp[2]; my_snprintf(tmp, sizeof tmp, "%c", c); sv_catpvn(dsv, tmp, 1); wrote++; } if (flags & PERL_PV_ESCAPE_FIRSTCHAR) break; } if (escaped != NULL) *escaped= pv - str; return SvPVX(dsv); } #endif #endif #ifndef pv_pretty #if defined(NEED_pv_pretty) static char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags); static #else extern char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags); #endif #ifdef pv_pretty # undef pv_pretty #endif #define pv_pretty(a,b,c,d,e,f,g) DPPP_(my_pv_pretty)(aTHX_ a,b,c,d,e,f,g) #define Perl_pv_pretty DPPP_(my_pv_pretty) #if defined(NEED_pv_pretty) || defined(NEED_pv_pretty_GLOBAL) char * DPPP_(my_pv_pretty)(pTHX_ SV *dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags) { const U8 dq = (flags & PERL_PV_PRETTY_QUOTE) ? '"' : '%'; STRLEN escaped; if (!(flags & PERL_PV_PRETTY_NOCLEAR)) sv_setpvs(dsv, ""); if (dq == '"') sv_catpvs(dsv, "\""); else if (flags & PERL_PV_PRETTY_LTGT) sv_catpvs(dsv, "<"); if (start_color != NULL) sv_catpv(dsv, D_PPP_CONSTPV_ARG(start_color)); pv_escape(dsv, str, count, max, &escaped, flags | PERL_PV_ESCAPE_NOCLEAR); if (end_color != NULL) sv_catpv(dsv, D_PPP_CONSTPV_ARG(end_color)); if (dq == '"') sv_catpvs(dsv, "\""); else if (flags & PERL_PV_PRETTY_LTGT) sv_catpvs(dsv, ">"); if ((flags & PERL_PV_PRETTY_ELLIPSES) && escaped < count) sv_catpvs(dsv, "..."); return SvPVX(dsv); } #endif #endif #ifndef pv_display #if defined(NEED_pv_display) static char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim); static #else extern char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim); #endif #ifdef pv_display # undef pv_display #endif #define pv_display(a,b,c,d,e) DPPP_(my_pv_display)(aTHX_ a,b,c,d,e) #define Perl_pv_display DPPP_(my_pv_display) #if defined(NEED_pv_display) || defined(NEED_pv_display_GLOBAL) char * DPPP_(my_pv_display)(pTHX_ SV *dsv, const char *pv, STRLEN cur, STRLEN len, STRLEN pvlim) { pv_pretty(dsv, pv, cur, pvlim, NULL, NULL, PERL_PV_PRETTY_DUMP); if (len > cur && pv[cur] == '\0') sv_catpvs(dsv, "\\0"); return SvPVX(dsv); } #endif #endif #endif /* _P_P_PORTABILITY_H_ */ /* End of File ppport.h */ Slic3r-version_1.39.1/xs/src/slic3r/000077500000000000000000000000001324354444700171435ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/slic3r/GUI/000077500000000000000000000000001324354444700175675ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/src/slic3r/GUI/3DScene.cpp000066400000000000000000001511341324354444700215240ustar00rootroot00000000000000#include #include "3DScene.hpp" #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/ExtrusionEntity.hpp" #include "../../libslic3r/ExtrusionEntityCollection.hpp" #include "../../libslic3r/Geometry.hpp" #include "../../libslic3r/Print.hpp" #include "../../libslic3r/Slicing.hpp" #include #include #include #include #include #include #include #include namespace Slic3r { void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh) { assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0); assert(quad_indices.empty() && triangle_indices_size == 0); assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size()); this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count()); for (int i = 0; i < mesh.stl.stats.number_of_facets; ++ i) { const stl_facet &facet = mesh.stl.facet_start[i]; for (int j = 0; j < 3; ++ j) this->push_geometry(facet.vertex[j].x, facet.vertex[j].y, facet.vertex[j].z, facet.normal.x, facet.normal.y, facet.normal.z); } } void GLIndexedVertexArray::finalize_geometry(bool use_VBOs) { assert(this->vertices_and_normals_interleaved_VBO_id == 0); assert(this->triangle_indices_VBO_id == 0); assert(this->quad_indices_VBO_id == 0); this->setup_sizes(); if (use_VBOs) { if (! empty()) { glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id); glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id); glBufferData(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved.size() * 4, this->vertices_and_normals_interleaved.data(), GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); this->vertices_and_normals_interleaved.clear(); } if (! this->triangle_indices.empty()) { glGenBuffers(1, &this->triangle_indices_VBO_id); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id); glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices.size() * 4, this->triangle_indices.data(), GL_STATIC_DRAW); this->triangle_indices.clear(); } if (! this->quad_indices.empty()) { glGenBuffers(1, &this->quad_indices_VBO_id); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id); glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices.size() * 4, this->quad_indices.data(), GL_STATIC_DRAW); this->quad_indices.clear(); } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } this->shrink_to_fit(); } void GLIndexedVertexArray::release_geometry() { if (this->vertices_and_normals_interleaved_VBO_id) glDeleteBuffers(1, &this->vertices_and_normals_interleaved_VBO_id); if (this->triangle_indices_VBO_id) glDeleteBuffers(1, &this->triangle_indices_VBO_id); if (this->quad_indices_VBO_id) glDeleteBuffers(1, &this->quad_indices_VBO_id); this->clear(); this->shrink_to_fit(); } void GLIndexedVertexArray::render() const { if (this->vertices_and_normals_interleaved_VBO_id) { glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id); glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float))); glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr); } else { glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data() + 3); glNormalPointer(GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data()); } glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); if (this->indexed()) { if (this->vertices_and_normals_interleaved_VBO_id) { // Render using the Vertex Buffer Objects. if (this->triangle_indices_size > 0) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id); glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_size), GL_UNSIGNED_INT, nullptr); } if (this->quad_indices_size > 0) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id); glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size), GL_UNSIGNED_INT, nullptr); } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } else { // Render in an immediate mode. if (! this->triangle_indices.empty()) glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_size), GL_UNSIGNED_INT, this->triangle_indices.data()); if (! this->quad_indices.empty()) glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size), GL_UNSIGNED_INT, this->quad_indices.data()); } } else glDrawArrays(GL_TRIANGLES, 0, GLsizei(this->vertices_and_normals_interleaved_size / 6)); if (this->vertices_and_normals_interleaved_VBO_id) glBindBuffer(GL_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); } void GLIndexedVertexArray::render( const std::pair &tverts_range, const std::pair &qverts_range) const { assert(this->indexed()); if (! this->indexed()) return; if (this->vertices_and_normals_interleaved_VBO_id) { // Render using the Vertex Buffer Objects. glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id); glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float))); glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); if (this->triangle_indices_size > 0) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id); glDrawElements(GL_TRIANGLES, GLsizei(std::min(this->triangle_indices_size, tverts_range.second - tverts_range.first)), GL_UNSIGNED_INT, (const void*)(tverts_range.first * 4)); } if (this->quad_indices_size > 0) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id); glDrawElements(GL_QUADS, GLsizei(std::min(this->quad_indices_size, qverts_range.second - qverts_range.first)), GL_UNSIGNED_INT, (const void*)(qverts_range.first * 4)); } glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } else { // Render in an immediate mode. glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data() + 3); glNormalPointer(GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data()); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); if (! this->triangle_indices.empty()) glDrawElements(GL_TRIANGLES, GLsizei(std::min(this->triangle_indices_size, tverts_range.second - tverts_range.first)), GL_UNSIGNED_INT, (const void*)(this->triangle_indices.data() + tverts_range.first)); if (! this->quad_indices.empty()) glDrawElements(GL_QUADS, GLsizei(std::min(this->quad_indices_size, qverts_range.second - qverts_range.first)), GL_UNSIGNED_INT, (const void*)(this->quad_indices.data() + qverts_range.first)); } glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); } void GLVolume::set_range(double min_z, double max_z) { this->qverts_range.first = 0; this->qverts_range.second = this->indexed_vertex_array.quad_indices_size; this->tverts_range.first = 0; this->tverts_range.second = this->indexed_vertex_array.triangle_indices_size; if (! this->print_zs.empty()) { // The Z layer range is specified. // First test whether the Z span of this object is not out of (min_z, max_z) completely. if (this->print_zs.front() > max_z || this->print_zs.back() < min_z) { this->qverts_range.second = 0; this->tverts_range.second = 0; } else { // Then find the lowest layer to be displayed. size_t i = 0; for (; i < this->print_zs.size() && this->print_zs[i] < min_z; ++ i); if (i == this->print_zs.size()) { // This shall not happen. this->qverts_range.second = 0; this->tverts_range.second = 0; } else { // Remember start of the layer. this->qverts_range.first = this->offsets[i * 2]; this->tverts_range.first = this->offsets[i * 2 + 1]; // Some layers are above $min_z. Which? for (; i < this->print_zs.size() && this->print_zs[i] <= max_z; ++ i); if (i < this->print_zs.size()) { this->qverts_range.second = this->offsets[i * 2]; this->tverts_range.second = this->offsets[i * 2 + 1]; } } } } } void GLVolume::render() const { glCullFace(GL_BACK); glPushMatrix(); glTranslated(this->origin.x, this->origin.y, this->origin.z); if (this->indexed_vertex_array.indexed()) this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); else this->indexed_vertex_array.render(); glPopMatrix(); } void GLVolume::generate_layer_height_texture(PrintObject *print_object, bool force) { GLTexture *tex = this->layer_height_texture.get(); if (tex == nullptr) // No layer_height_texture is assigned to this GLVolume, therefore the layer height texture cannot be filled. return; // Always try to update the layer height profile. bool update = print_object->update_layer_height_profile(print_object->model_object()->layer_height_profile) || force; // Update if the layer height profile was changed, or when the texture is not valid. if (! update && ! tex->data.empty() && tex->cells > 0) // Texture is valid, don't update. return; if (tex->data.empty()) { tex->width = 1024; tex->height = 1024; tex->levels = 2; tex->data.assign(tex->width * tex->height * 5, 0); } SlicingParameters slicing_params = print_object->slicing_parameters(); bool level_of_detail_2nd_level = true; tex->cells = Slic3r::generate_layer_height_texture( slicing_params, Slic3r::generate_object_layers(slicing_params, print_object->model_object()->layer_height_profile), tex->data.data(), tex->height, tex->width, level_of_detail_2nd_level); } // 512x512 bitmaps are supported everywhere, but that may not be sufficent for super large print volumes. #define LAYER_HEIGHT_TEXTURE_WIDTH 1024 #define LAYER_HEIGHT_TEXTURE_HEIGHT 1024 std::vector GLVolumeCollection::load_object( const ModelObject *model_object, int obj_idx, const std::vector &instance_idxs, const std::string &color_by, const std::string &select_by, const std::string &drag_by, bool use_VBOs) { static float colors[4][4] = { { 1.0f, 1.0f, 0.0f, 1.f }, { 1.0f, 0.5f, 0.5f, 1.f }, { 0.5f, 1.0f, 0.5f, 1.f }, { 0.5f, 0.5f, 1.0f, 1.f } }; // Object will have a single common layer height texture for all volumes. std::shared_ptr layer_height_texture = std::make_shared(); std::vector volumes_idx; for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++ volume_idx) { const ModelVolume *model_volume = model_object->volumes[volume_idx]; for (int instance_idx : instance_idxs) { const ModelInstance *instance = model_object->instances[instance_idx]; TriangleMesh mesh = model_volume->mesh; instance->transform_mesh(&mesh); volumes_idx.push_back(int(this->volumes.size())); float color[4]; memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); color[3] = model_volume->modifier ? 0.5f : 1.f; this->volumes.emplace_back(new GLVolume(color)); GLVolume &v = *this->volumes.back(); v.indexed_vertex_array.load_mesh_flat_shading(mesh); // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). v.bounding_box = v.indexed_vertex_array.bounding_box(); v.indexed_vertex_array.finalize_geometry(use_VBOs); v.composite_id = obj_idx * 1000000 + volume_idx * 1000 + instance_idx; if (select_by == "object") v.select_group_id = obj_idx * 1000000; else if (select_by == "volume") v.select_group_id = obj_idx * 1000000 + volume_idx * 1000; else if (select_by == "instance") v.select_group_id = v.composite_id; if (drag_by == "object") v.drag_group_id = obj_idx * 1000; else if (drag_by == "instance") v.drag_group_id = obj_idx * 1000 + instance_idx; if (! model_volume->modifier) v.layer_height_texture = layer_height_texture; } } return volumes_idx; } int GLVolumeCollection::load_wipe_tower_preview( int obj_idx, float pos_x, float pos_y, float width, float depth, float height, bool use_VBOs) { float color[4] = { 1.0f, 1.0f, 0.0f, 0.5f }; this->volumes.emplace_back(new GLVolume(color)); GLVolume &v = *this->volumes.back(); auto mesh = make_cube(width, depth, height); v.indexed_vertex_array.load_mesh_flat_shading(mesh); v.origin = Pointf3(pos_x, pos_y, 0.); // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). v.bounding_box = v.indexed_vertex_array.bounding_box(); v.indexed_vertex_array.finalize_geometry(use_VBOs); v.composite_id = obj_idx * 1000000; v.select_group_id = obj_idx * 1000000; v.drag_group_id = obj_idx * 1000; return int(this->volumes.size() - 1); } void GLVolumeCollection::render_VBOs() const { // glEnable(GL_BLEND); // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glCullFace(GL_BACK); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); GLint current_program_id; glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id); GLint color_id = (current_program_id > 0) ? glGetUniformLocation(current_program_id, "uniform_color") : -1; for (GLVolume *volume : this->volumes) { if (! volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id) continue; GLsizei n_triangles = GLsizei(std::min(volume->indexed_vertex_array.triangle_indices_size, volume->tverts_range.second - volume->tverts_range.first)); GLsizei n_quads = GLsizei(std::min(volume->indexed_vertex_array.quad_indices_size, volume->qverts_range.second - volume->qverts_range.first)); if (n_triangles + n_quads == 0) continue; if (color_id >= 0) glUniform4fv(color_id, 1, (const GLfloat*)volume->color); else glColor4f(volume->color[0], volume->color[1], volume->color[2], volume->color[3]); glBindBuffer(GL_ARRAY_BUFFER, volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id); glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float))); glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr); if (n_triangles > 0) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.triangle_indices_VBO_id); glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, (const void*)(volume->tverts_range.first * 4)); } if (n_quads > 0) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.quad_indices_VBO_id); glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, (const void*)(volume->qverts_range.first * 4)); } } glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); // glDisable(GL_BLEND); } void GLVolumeCollection::render_legacy() const { glCullFace(GL_BACK); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); for (GLVolume *volume : this->volumes) { assert(! volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id); GLsizei n_triangles = GLsizei(std::min(volume->indexed_vertex_array.triangle_indices_size, volume->tverts_range.second - volume->tverts_range.first)); GLsizei n_quads = GLsizei(std::min(volume->indexed_vertex_array.quad_indices_size, volume->qverts_range.second - volume->qverts_range.first)); if (n_triangles + n_quads == 0) continue; glColor4f(volume->color[0], volume->color[1], volume->color[2], volume->color[3]); glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), volume->indexed_vertex_array.vertices_and_normals_interleaved.data() + 3); glNormalPointer(GL_FLOAT, 6 * sizeof(float), volume->indexed_vertex_array.vertices_and_normals_interleaved.data()); bool has_offset = volume->origin.x != 0 || volume->origin.y != 0 || volume->origin.z != 0; if (has_offset) { glPushMatrix(); glTranslated(volume->origin.x, volume->origin.y, volume->origin.z); } if (n_triangles > 0) glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, volume->indexed_vertex_array.triangle_indices.data() + volume->tverts_range.first); if (n_quads > 0) glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, volume->indexed_vertex_array.quad_indices.data() + volume->qverts_range.first); if (has_offset) glPushMatrix(); } glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); } // caller is responsible for supplying NO lines with zero length static void thick_lines_to_indexed_vertex_array( const Lines &lines, const std::vector &widths, const std::vector &heights, bool closed, double top_z, GLIndexedVertexArray &volume) { assert(! lines.empty()); if (lines.empty()) return; #define LEFT 0 #define RIGHT 1 #define TOP 2 #define BOTTOM 3 Line prev_line; // right, left, top, bottom int idx_prev[4] = { -1, -1, -1, -1 }; double bottom_z_prev = 0.; Pointf b1_prev; Pointf b2_prev; Vectorf v_prev; int idx_initial[4] = { -1, -1, -1, -1 }; double width_initial = 0.; // loop once more in case of closed loops size_t lines_end = closed ? (lines.size() + 1) : lines.size(); for (size_t ii = 0; ii < lines_end; ++ ii) { size_t i = (ii == lines.size()) ? 0 : ii; const Line &line = lines[i]; double len = unscale(line.length()); double bottom_z = top_z - heights[i]; double middle_z = (top_z + bottom_z) / 2.; double width = widths[i]; Vectorf v = Vectorf::new_unscale(line.vector()); v.scale(1. / len); Pointf a = Pointf::new_unscale(line.a); Pointf b = Pointf::new_unscale(line.b); Pointf a1 = a; Pointf a2 = a; Pointf b1 = b; Pointf b2 = b; { double dist = width / 2.; // scaled a1.translate(+dist*v.y, -dist*v.x); a2.translate(-dist*v.y, +dist*v.x); b1.translate(+dist*v.y, -dist*v.x); b2.translate(-dist*v.y, +dist*v.x); } // calculate new XY normals Vector n = line.normal(); Vectorf3 xy_right_normal = Vectorf3::new_unscale(n.x, n.y, 0); xy_right_normal.scale(1.f / len); int idx_a[4]; int idx_b[4]; int idx_last = int(volume.vertices_and_normals_interleaved.size() / 6); bool bottom_z_different = bottom_z_prev != bottom_z; bottom_z_prev = bottom_z; // Share top / bottom vertices if possible. if (ii == 0) { idx_a[TOP] = idx_last ++; volume.push_geometry(a.x, a.y, top_z , 0., 0., 1.); } else { idx_a[TOP] = idx_prev[TOP]; } if (ii == 0 || bottom_z_different) { // Start of the 1st line segment or a change of the layer thickness while maintaining the print_z. idx_a[BOTTOM] = idx_last ++; volume.push_geometry(a.x, a.y, bottom_z, 0., 0., -1.); idx_a[LEFT ] = idx_last ++; volume.push_geometry(a2.x, a2.y, middle_z, -xy_right_normal.x, -xy_right_normal.y, -xy_right_normal.z); idx_a[RIGHT] = idx_last ++; volume.push_geometry(a1.x, a1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z); } else { idx_a[BOTTOM] = idx_prev[BOTTOM]; } if (ii == 0) { // Start of the 1st line segment. width_initial = width; memcpy(idx_initial, idx_a, sizeof(int) * 4); } else { // Continuing a previous segment. // Share left / right vertices if possible. double v_dot = dot(v_prev, v); bool sharp = v_dot < 0.707; // sin(45 degrees) if (sharp) { // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. idx_a[RIGHT] = idx_last ++; volume.push_geometry(a1.x, a1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z); idx_a[LEFT ] = idx_last ++; volume.push_geometry(a2.x, a2.y, middle_z, -xy_right_normal.x, -xy_right_normal.y, -xy_right_normal.z); } if (v_dot > 0.9) { // The two successive segments are nearly collinear. idx_a[LEFT ] = idx_prev[LEFT]; idx_a[RIGHT] = idx_prev[RIGHT]; } else if (! sharp) { // Create a sharp corner with an overshot and average the left / right normals. // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc. Pointf intersection; Geometry::ray_ray_intersection(b1_prev, v_prev, a1, v, intersection); a1 = intersection; a2 = 2. * a - intersection; assert(length(a1.vector_to(a)) < width); assert(length(a2.vector_to(a)) < width); float *n_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6; float *p_left_prev = n_left_prev + 3; float *n_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6; float *p_right_prev = n_right_prev + 3; p_left_prev [0] = float(a2.x); p_left_prev [1] = float(a2.y); p_right_prev[0] = float(a1.x); p_right_prev[1] = float(a1.y); xy_right_normal.x += n_right_prev[0]; xy_right_normal.y += n_right_prev[1]; xy_right_normal.scale(1. / length(xy_right_normal)); n_left_prev [0] = float(-xy_right_normal.x); n_left_prev [1] = float(-xy_right_normal.y); n_right_prev[0] = float( xy_right_normal.x); n_right_prev[1] = float( xy_right_normal.y); idx_a[LEFT ] = idx_prev[LEFT ]; idx_a[RIGHT] = idx_prev[RIGHT]; } else if (cross(v_prev, v) > 0.) { // Right turn. Fill in the right turn wedge. volume.push_triangle(idx_prev[RIGHT], idx_a [RIGHT], idx_prev[TOP] ); volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a [RIGHT] ); } else { // Left turn. Fill in the left turn wedge. volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a [LEFT] ); volume.push_triangle(idx_prev[LEFT], idx_a [LEFT], idx_prev[BOTTOM]); } if (ii == lines.size()) { if (! sharp) { // Closing a loop with smooth transition. Unify the closing left / right vertices. memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[LEFT ] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6, sizeof(float) * 6); memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[RIGHT] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6, sizeof(float) * 6); volume.vertices_and_normals_interleaved.erase(volume.vertices_and_normals_interleaved.end() - 12, volume.vertices_and_normals_interleaved.end()); // Replace the left / right vertex indices to point to the start of the loop. for (size_t u = volume.quad_indices.size() - 16; u < volume.quad_indices.size(); ++ u) { if (volume.quad_indices[u] == idx_prev[LEFT]) volume.quad_indices[u] = idx_initial[LEFT]; else if (volume.quad_indices[u] == idx_prev[RIGHT]) volume.quad_indices[u] = idx_initial[RIGHT]; } } // This is the last iteration, only required to solve the transition. break; } } // Only new allocate top / bottom vertices, if not closing a loop. if (closed && ii + 1 == lines.size()) { idx_b[TOP] = idx_initial[TOP]; } else { idx_b[TOP] = idx_last ++; volume.push_geometry(b.x, b.y, top_z , 0., 0., 1.); } if (closed && ii + 1 == lines.size() && width == width_initial) { idx_b[BOTTOM] = idx_initial[BOTTOM]; } else { idx_b[BOTTOM] = idx_last ++; volume.push_geometry(b.x, b.y, bottom_z, 0., 0., -1.); } // Generate new vertices for the end of this line segment. idx_b[LEFT ] = idx_last ++; volume.push_geometry(b2.x, b2.y, middle_z, -xy_right_normal.x, -xy_right_normal.y, -xy_right_normal.z); idx_b[RIGHT ] = idx_last ++; volume.push_geometry(b1.x, b1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z); prev_line = line; memcpy(idx_prev, idx_b, 4 * sizeof(int)); bottom_z_prev = bottom_z; b1_prev = b1; b2_prev = b2; v_prev = v; if (! closed) { // Terminate open paths with caps. if (i == 0) volume.push_quad(idx_a[BOTTOM], idx_a[RIGHT], idx_a[TOP], idx_a[LEFT]); // We don't use 'else' because both cases are true if we have only one line. if (i + 1 == lines.size()) volume.push_quad(idx_b[BOTTOM], idx_b[LEFT], idx_b[TOP], idx_b[RIGHT]); } // Add quads for a straight hollow tube-like segment. // bottom-right face volume.push_quad(idx_a[BOTTOM], idx_b[BOTTOM], idx_b[RIGHT], idx_a[RIGHT]); // top-right face volume.push_quad(idx_a[RIGHT], idx_b[RIGHT], idx_b[TOP], idx_a[TOP]); // top-left face volume.push_quad(idx_a[TOP], idx_b[TOP], idx_b[LEFT], idx_a[LEFT]); // bottom-left face volume.push_quad(idx_a[LEFT], idx_b[LEFT], idx_b[BOTTOM], idx_a[BOTTOM]); } #undef LEFT #undef RIGHT #undef TOP #undef BOTTOM } static void thick_lines_to_verts( const Lines &lines, const std::vector &widths, const std::vector &heights, bool closed, double top_z, GLVolume &volume) { thick_lines_to_indexed_vertex_array(lines, widths, heights, closed, top_z, volume.indexed_vertex_array); } // Fill in the qverts and tverts with quads and triangles for the extrusion_path. static inline void extrusionentity_to_verts(const ExtrusionPath &extrusion_path, float print_z, const Point ©, GLVolume &volume) { Polyline polyline = extrusion_path.polyline; polyline.remove_duplicate_points(); polyline.translate(copy); Lines lines = polyline.lines(); std::vector widths(lines.size(), extrusion_path.width); std::vector heights(lines.size(), extrusion_path.height); thick_lines_to_verts(lines, widths, heights, false, print_z, volume); } // Fill in the qverts and tverts with quads and triangles for the extrusion_loop. static inline void extrusionentity_to_verts(const ExtrusionLoop &extrusion_loop, float print_z, const Point ©, GLVolume &volume) { Lines lines; std::vector widths; std::vector heights; for (const ExtrusionPath &extrusion_path : extrusion_loop.paths) { Polyline polyline = extrusion_path.polyline; polyline.remove_duplicate_points(); polyline.translate(copy); Lines lines_this = polyline.lines(); append(lines, lines_this); widths.insert(widths.end(), lines_this.size(), extrusion_path.width); heights.insert(heights.end(), lines_this.size(), extrusion_path.height); } thick_lines_to_verts(lines, widths, heights, true, print_z, volume); } // Fill in the qverts and tverts with quads and triangles for the extrusion_multi_path. static inline void extrusionentity_to_verts(const ExtrusionMultiPath &extrusion_multi_path, float print_z, const Point ©, GLVolume &volume) { Lines lines; std::vector widths; std::vector heights; for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths) { Polyline polyline = extrusion_path.polyline; polyline.remove_duplicate_points(); polyline.translate(copy); Lines lines_this = polyline.lines(); append(lines, lines_this); widths.insert(widths.end(), lines_this.size(), extrusion_path.width); heights.insert(heights.end(), lines_this.size(), extrusion_path.height); } thick_lines_to_verts(lines, widths, heights, false, print_z, volume); } static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point ©, GLVolume &volume); static inline void extrusionentity_to_verts(const ExtrusionEntityCollection &extrusion_entity_collection, float print_z, const Point ©, GLVolume &volume) { for (const ExtrusionEntity *extrusion_entity : extrusion_entity_collection.entities) extrusionentity_to_verts(extrusion_entity, print_z, copy, volume); } static void extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, float print_z, const Point ©, GLVolume &volume) { if (extrusion_entity != nullptr) { auto *extrusion_path = dynamic_cast(extrusion_entity); if (extrusion_path != nullptr) extrusionentity_to_verts(*extrusion_path, print_z, copy, volume); else { auto *extrusion_loop = dynamic_cast(extrusion_entity); if (extrusion_loop != nullptr) extrusionentity_to_verts(*extrusion_loop, print_z, copy, volume); else { auto *extrusion_multi_path = dynamic_cast(extrusion_entity); if (extrusion_multi_path != nullptr) extrusionentity_to_verts(*extrusion_multi_path, print_z, copy, volume); else { auto *extrusion_entity_collection = dynamic_cast(extrusion_entity); if (extrusion_entity_collection != nullptr) extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, volume); else { CONFESS("Unexpected extrusion_entity type in to_verts()"); } } } } } } void _3DScene::_glew_init() { glewInit(); } static inline int hex_digit_to_int(const char c) { return (c >= '0' && c <= '9') ? int(c - '0') : (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; } static inline std::vector parse_colors(const std::vector &scolors) { std::vector output(scolors.size() * 4, 1.f); for (size_t i = 0; i < scolors.size(); ++ i) { const std::string &scolor = scolors[i]; const char *c = scolor.data() + 1; if (scolor.size() == 7 && scolor.front() == '#') { for (size_t j = 0; j < 3; ++j) { int digit1 = hex_digit_to_int(*c ++); int digit2 = hex_digit_to_int(*c ++); if (digit1 == -1 || digit2 == -1) break; output[i * 4 + j] = float(digit1 * 16 + digit2) / 255.f; } } } return output; } // Create 3D thick extrusion lines for a skirt and brim. // Adds a new Slic3r::GUI::3DScene::Volume to volumes. void _3DScene::_load_print_toolpaths( const Print *print, GLVolumeCollection *volumes, const std::vector &tool_colors, bool use_VBOs) { if (! print->has_skirt() && print->config.brim_width.value == 0) return; const float color[] = { 0.5f, 1.0f, 0.5f, 1.f }; // greenish // number of skirt layers size_t total_layer_count = 0; for (const PrintObject *print_object : print->objects) total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); size_t skirt_height = print->has_infinite_skirt() ? total_layer_count : std::min(print->config.skirt_height.value, total_layer_count); if (skirt_height == 0 && print->config.brim_width.value > 0) skirt_height = 1; // get first skirt_height layers (maybe this should be moved to a PrintObject method?) const PrintObject *object0 = print->objects.front(); std::vector print_zs; print_zs.reserve(skirt_height * 2); for (size_t i = 0; i < std::min(skirt_height, object0->layers.size()); ++ i) print_zs.push_back(float(object0->layers[i]->print_z)); //FIXME why there are support layers? for (size_t i = 0; i < std::min(skirt_height, object0->support_layers.size()); ++ i) print_zs.push_back(float(object0->support_layers[i]->print_z)); sort_remove_duplicates(print_zs); if (print_zs.size() > skirt_height) print_zs.erase(print_zs.begin() + skirt_height, print_zs.end()); volumes->volumes.emplace_back(new GLVolume(color)); GLVolume &volume = *volumes->volumes.back(); for (size_t i = 0; i < skirt_height; ++ i) { volume.print_zs.push_back(print_zs[i]); volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size()); volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size()); if (i == 0) extrusionentity_to_verts(print->brim, print_zs[i], Point(0, 0), volume); extrusionentity_to_verts(print->skirt, print_zs[i], Point(0, 0), volume); } volume.bounding_box = volume.indexed_vertex_array.bounding_box(); volume.indexed_vertex_array.finalize_geometry(use_VBOs); } // Create 3D thick extrusion lines for object forming extrusions. // Adds a new Slic3r::GUI::3DScene::Volume to $self->volumes, // one for perimeters, one for infill and one for supports. void _3DScene::_load_print_object_toolpaths( const PrintObject *print_object, GLVolumeCollection *volumes, const std::vector &tool_colors_str, bool use_VBOs) { std::vector tool_colors = parse_colors(tool_colors_str); struct Ctxt { const Points *shifted_copies; std::vector layers; bool has_perimeters; bool has_infill; bool has_support; const std::vector* tool_colors; // Number of vertices (each vertex is 6x4=24 bytes long) static const size_t alloc_size_max () { return 131072; } // 3.15MB // static const size_t alloc_size_max () { return 65536; } // 1.57MB // static const size_t alloc_size_max () { return 32768; } // 786kB static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } static const float* color_perimeters () { static float color[4] = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow static const float* color_infill () { static float color[4] = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish static const float* color_support () { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish // For cloring by a tool, return a parsed color. bool color_by_tool() const { return tool_colors != nullptr; } size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } int volume_idx(int extruder, int feature) const { return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(extruder - 1, 0)) : feature; } } ctxt; ctxt.shifted_copies = &print_object->_shifted_copies; // order layers by print_z ctxt.layers.reserve(print_object->layers.size() + print_object->support_layers.size()); for (const Layer *layer : print_object->layers) ctxt.layers.push_back(layer); for (const Layer *layer : print_object->support_layers) ctxt.layers.push_back(layer); std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); // Maximum size of an allocation block: 32MB / sizeof(float) ctxt.has_perimeters = print_object->state.is_done(posPerimeters); ctxt.has_infill = print_object->state.is_done(posInfill); ctxt.has_support = print_object->state.is_done(posSupportMaterial); ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start"; //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); tbb::spin_mutex new_volume_mutex; auto new_volume = [volumes, &new_volume_mutex](const float *color) -> GLVolume* { auto *volume = new GLVolume(color); new_volume_mutex.lock(); volumes->volumes.emplace_back(volume); new_volume_mutex.unlock(); return volume; }; const size_t volumes_cnt_initial = volumes->volumes.size(); std::vector volumes_per_thread(ctxt.layers.size()); tbb::parallel_for( tbb::blocked_range(0, ctxt.layers.size(), grain_size), [&ctxt, &new_volume](const tbb::blocked_range& range) { std::vector vols; if (ctxt.color_by_tool()) { for (size_t i = 0; i < ctxt.number_tools(); ++ i) vols.emplace_back(new_volume(ctxt.color_tool(i))); } else vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; for (GLVolume *vol : vols) vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { const Layer *layer = ctxt.layers[idx_layer]; for (size_t i = 0; i < vols.size(); ++ i) { GLVolume &vol = *vols[i]; if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) { vol.print_zs.push_back(layer->print_z); vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); } } for (const Point ©: *ctxt.shifted_copies) { for (const LayerRegion *layerm : layer->regions) { if (ctxt.has_perimeters) extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, *vols[ctxt.volume_idx(layerm->region()->config.perimeter_extruder.value, 0)]); if (ctxt.has_infill) { for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); if (! fill->entities.empty()) extrusionentity_to_verts(*fill, float(layer->print_z), copy, *vols[ctxt.volume_idx( is_solid_infill(fill->entities.front()->role()) ? layerm->region()->config.solid_infill_extruder : layerm->region()->config.infill_extruder, 1)]); } } } if (ctxt.has_support) { const SupportLayer *support_layer = dynamic_cast(layer); if (support_layer) { for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, *vols[ctxt.volume_idx( (extrusion_entity->role() == erSupportMaterial) ? support_layer->object()->config.support_material_extruder : support_layer->object()->config.support_material_interface_extruder, 2)]); } } } for (size_t i = 0; i < vols.size(); ++ i) { GLVolume &vol = *vols[i]; if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { // Store the vertex arrays and restart their containers, vols[i] = new_volume(vol.color); GLVolume &vol_new = *vols[i]; // Assign the large pre-allocated buffers to the new GLVolume. vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); // Copy the content back to the old GLVolume. vol.indexed_vertex_array = vol_new.indexed_vertex_array; // Finalize a bounding box of the old GLVolume. vol.bounding_box = vol.indexed_vertex_array.bounding_box(); // Clear the buffers, but keep them pre-allocated. vol_new.indexed_vertex_array.clear(); // Just make sure that clear did not clear the reserved memory. vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); } } } for (GLVolume *vol : vols) { vol->bounding_box = vol->indexed_vertex_array.bounding_box(); vol->indexed_vertex_array.shrink_to_fit(); } }); BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results"; // Remove empty volumes from the newly added volumes. volumes->volumes.erase( std::remove_if(volumes->volumes.begin() + volumes_cnt_initial, volumes->volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), volumes->volumes.end()); for (size_t i = volumes_cnt_initial; i < volumes->volumes.size(); ++ i) volumes->volumes[i]->indexed_vertex_array.finalize_geometry(use_VBOs); BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; } void _3DScene::_load_wipe_tower_toolpaths( const Print *print, GLVolumeCollection *volumes, const std::vector &tool_colors_str, bool use_VBOs) { if (print->m_wipe_tower_tool_changes.empty()) return; std::vector tool_colors = parse_colors(tool_colors_str); struct Ctxt { const Print *print; const std::vector *tool_colors; // Number of vertices (each vertex is 6x4=24 bytes long) static const size_t alloc_size_max () { return 131072; } // 3.15MB static const size_t alloc_size_reserve() { return alloc_size_max() * 2; } static const float* color_support () { static float color[4] = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish // For cloring by a tool, return a parsed color. bool color_by_tool() const { return tool_colors != nullptr; } size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } int volume_idx(int tool, int feature) const { return this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(tool, 0)) : feature; } const std::vector& tool_change(size_t idx) { return priming.empty() ? ((idx == print->m_wipe_tower_tool_changes.size()) ? final : print->m_wipe_tower_tool_changes[idx]) : ((idx == 0) ? priming : (idx == print->m_wipe_tower_tool_changes.size() + 1) ? final : print->m_wipe_tower_tool_changes[idx - 1]); } std::vector priming; std::vector final; } ctxt; ctxt.print = print; ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; if (print->m_wipe_tower_priming) ctxt.priming.emplace_back(*print->m_wipe_tower_priming.get()); if (print->m_wipe_tower_final_purge) ctxt.final.emplace_back(*print->m_wipe_tower_final_purge.get()); BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; //FIXME Improve the heuristics for a grain size. size_t n_items = print->m_wipe_tower_tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); size_t grain_size = std::max(n_items / 128, size_t(1)); tbb::spin_mutex new_volume_mutex; auto new_volume = [volumes, &new_volume_mutex](const float *color) -> GLVolume* { auto *volume = new GLVolume(color); new_volume_mutex.lock(); volumes->volumes.emplace_back(volume); new_volume_mutex.unlock(); return volume; }; const size_t volumes_cnt_initial = volumes->volumes.size(); std::vector volumes_per_thread(n_items); tbb::parallel_for( tbb::blocked_range(0, n_items, grain_size), [&ctxt, &new_volume](const tbb::blocked_range& range) { // Bounding box of this slab of a wipe tower. std::vector vols; if (ctxt.color_by_tool()) { for (size_t i = 0; i < ctxt.number_tools(); ++ i) vols.emplace_back(new_volume(ctxt.color_tool(i))); } else vols = { new_volume(ctxt.color_support()) }; for (GLVolume *volume : vols) volume->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { const std::vector &layer = ctxt.tool_change(idx_layer); for (size_t i = 0; i < vols.size(); ++ i) { GLVolume &vol = *vols[i]; if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) { vol.print_zs.push_back(layer.front().print_z); vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); } } for (const WipeTower::ToolChangeResult &extrusions : layer) { for (size_t i = 1; i < extrusions.extrusions.size();) { const WipeTower::Extrusion &e = extrusions.extrusions[i]; if (e.width == 0.) { ++ i; continue; } size_t j = i + 1; if (ctxt.color_by_tool()) for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++ j) ; else for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++ j) ; size_t n_lines = j - i; Lines lines; std::vector widths; std::vector heights; lines.reserve(n_lines); widths.reserve(n_lines); heights.assign(n_lines, extrusions.layer_height); for (; i < j; ++ i) { const WipeTower::Extrusion &e = extrusions.extrusions[i]; assert(e.width > 0.f); const WipeTower::Extrusion &e_prev = *(&e - 1); lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y)); widths.emplace_back(e.width); } thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z, *vols[ctxt.volume_idx(e.tool, 0)]); } } } for (size_t i = 0; i < vols.size(); ++ i) { GLVolume &vol = *vols[i]; if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { // Store the vertex arrays and restart their containers, vols[i] = new_volume(vol.color); GLVolume &vol_new = *vols[i]; // Assign the large pre-allocated buffers to the new GLVolume. vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); // Copy the content back to the old GLVolume. vol.indexed_vertex_array = vol_new.indexed_vertex_array; // Finalize a bounding box of the old GLVolume. vol.bounding_box = vol.indexed_vertex_array.bounding_box(); // Clear the buffers, but keep them pre-allocated. vol_new.indexed_vertex_array.clear(); // Just make sure that clear did not clear the reserved memory. vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); } } for (GLVolume *vol : vols) { vol->bounding_box = vol->indexed_vertex_array.bounding_box(); vol->indexed_vertex_array.shrink_to_fit(); } }); BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; // Remove empty volumes from the newly added volumes. volumes->volumes.erase( std::remove_if(volumes->volumes.begin() + volumes_cnt_initial, volumes->volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), volumes->volumes.end()); for (size_t i = volumes_cnt_initial; i < volumes->volumes.size(); ++ i) volumes->volumes[i]->indexed_vertex_array.finalize_geometry(use_VBOs); BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; } } Slic3r-version_1.39.1/xs/src/slic3r/GUI/3DScene.hpp000066400000000000000000000372301324354444700215310ustar00rootroot00000000000000#ifndef slic3r_3DScene_hpp_ #define slic3r_3DScene_hpp_ #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/Point.hpp" #include "../../libslic3r/Line.hpp" #include "../../libslic3r/TriangleMesh.hpp" #include "../../libslic3r/Utils.hpp" namespace Slic3r { class Print; class PrintObject; class Model; class ModelObject; // A container for interleaved arrays of 3D vertices and normals, // possibly indexed by triangles and / or quads. class GLIndexedVertexArray { public: GLIndexedVertexArray() : vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) { this->setup_sizes(); } GLIndexedVertexArray(const GLIndexedVertexArray &rhs) : vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved), triangle_indices(rhs.triangle_indices), quad_indices(rhs.quad_indices), vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) { this->setup_sizes(); } GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), triangle_indices(std::move(rhs.triangle_indices)), quad_indices(std::move(rhs.quad_indices)), vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) { this->setup_sizes(); } GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs) { assert(vertices_and_normals_interleaved_VBO_id == 0); assert(triangle_indices_VBO_id == 0); assert(triangle_indices_VBO_id == 0); this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; this->triangle_indices = rhs.triangle_indices; this->quad_indices = rhs.quad_indices; this->setup_sizes(); return *this; } GLIndexedVertexArray& operator=(GLIndexedVertexArray &&rhs) { assert(vertices_and_normals_interleaved_VBO_id == 0); assert(triangle_indices_VBO_id == 0); assert(triangle_indices_VBO_id == 0); this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); this->triangle_indices = std::move(rhs.triangle_indices); this->quad_indices = std::move(rhs.quad_indices); this->setup_sizes(); return *this; } // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x) std::vector vertices_and_normals_interleaved; std::vector triangle_indices; std::vector quad_indices; // When the geometry data is loaded into the graphics card as Vertex Buffer Objects, // the above mentioned std::vectors are cleared and the following variables keep their original length. size_t vertices_and_normals_interleaved_size; size_t triangle_indices_size; size_t quad_indices_size; // IDs of the Vertex Array Objects, into which the geometry has been loaded. // Zero if the VBOs are not used. unsigned int vertices_and_normals_interleaved_VBO_id; unsigned int triangle_indices_VBO_id; unsigned int quad_indices_VBO_id; void load_mesh_flat_shading(const TriangleMesh &mesh); inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; } inline void reserve(size_t sz) { this->vertices_and_normals_interleaved.reserve(sz * 6); this->triangle_indices.reserve(sz * 3); this->quad_indices.reserve(sz * 4); } inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) { if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity()) this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6)); this->vertices_and_normals_interleaved.push_back(nx); this->vertices_and_normals_interleaved.push_back(ny); this->vertices_and_normals_interleaved.push_back(nz); this->vertices_and_normals_interleaved.push_back(x); this->vertices_and_normals_interleaved.push_back(y); this->vertices_and_normals_interleaved.push_back(z); }; inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz)); } inline void push_triangle(int idx1, int idx2, int idx3) { if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity()) this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3)); this->triangle_indices.push_back(idx1); this->triangle_indices.push_back(idx2); this->triangle_indices.push_back(idx3); }; inline void push_quad(int idx1, int idx2, int idx3, int idx4) { if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity()) this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4)); this->quad_indices.push_back(idx1); this->quad_indices.push_back(idx2); this->quad_indices.push_back(idx3); this->quad_indices.push_back(idx4); }; // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. void finalize_geometry(bool use_VBOs); // Release the geometry data, release OpenGL VBOs. void release_geometry(); // Render either using an immediate mode, or the VBOs. void render() const; void render(const std::pair &tverts_range, const std::pair &qverts_range) const; // Is there any geometry data stored? bool empty() const { return vertices_and_normals_interleaved_size == 0; } // Is this object indexed, or is it just a set of triangles? bool indexed() const { return ! this->empty() && this->triangle_indices_size + this->quad_indices_size > 0; } void clear() { this->vertices_and_normals_interleaved.clear(); this->triangle_indices.clear(); this->quad_indices.clear(); this->setup_sizes(); } // Shrink the internal storage to tighly fit the data stored. void shrink_to_fit() { if (! this->has_VBOs()) this->setup_sizes(); this->vertices_and_normals_interleaved.shrink_to_fit(); this->triangle_indices.shrink_to_fit(); this->quad_indices.shrink_to_fit(); } BoundingBoxf3 bounding_box() const { BoundingBoxf3 bbox; if (! this->vertices_and_normals_interleaved.empty()) { bbox.defined = true; bbox.min.x = bbox.max.x = this->vertices_and_normals_interleaved[3]; bbox.min.y = bbox.max.y = this->vertices_and_normals_interleaved[4]; bbox.min.z = bbox.max.z = this->vertices_and_normals_interleaved[5]; for (size_t i = 9; i < this->vertices_and_normals_interleaved.size(); i += 6) { const float *verts = this->vertices_and_normals_interleaved.data() + i; bbox.min.x = std::min(bbox.min.x, verts[0]); bbox.min.y = std::min(bbox.min.y, verts[1]); bbox.min.z = std::min(bbox.min.z, verts[2]); bbox.max.x = std::max(bbox.max.x, verts[0]); bbox.max.y = std::max(bbox.max.y, verts[1]); bbox.max.z = std::max(bbox.max.z, verts[2]); } } return bbox; } private: inline void setup_sizes() { vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); triangle_indices_size = this->triangle_indices.size(); quad_indices_size = this->quad_indices.size(); } }; class GLTexture { public: GLTexture() : width(0), height(0), levels(0), cells(0) {} // Texture data std::vector data; // Width of the texture, top level. size_t width; // Height of the texture, top level. size_t height; // For how many levels of detail is the data allocated? size_t levels; // Number of texture cells allocated for the height texture. size_t cells; }; class GLVolume { public: GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f) : composite_id(-1), select_group_id(-1), drag_group_id(-1), selected(false), hover(false), tverts_range(0, size_t(-1)), qverts_range(0, size_t(-1)) { color[0] = r; color[1] = g; color[2] = b; color[3] = a; } GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} std::vector load_object( const ModelObject *model_object, const std::vector &instance_idxs, const std::string &color_by, const std::string &select_by, const std::string &drag_by); int load_wipe_tower_preview( int obj_idx, float pos_x, float pos_y, float width, float depth, float height, bool use_VBOs); // Bounding box of this volume, in unscaled coordinates. BoundingBoxf3 bounding_box; // Offset of the volume to be rendered. Pointf3 origin; // Color of the triangles / quads held by this volume. float color[4]; // An ID containing the object ID, volume ID and instance ID. int composite_id; // An ID for group selection. It may be the same for all meshes of all object instances, or for just a single object instance. int select_group_id; // An ID for group dragging. It may be the same for all meshes of all object instances, or for just a single object instance. int drag_group_id; // Is this object selected? bool selected; // Boolean: Is mouse over this object? bool hover; // Interleaved triangles & normals with indexed triangles & quads. GLIndexedVertexArray indexed_vertex_array; // Ranges of triangle and quad indices to be rendered. std::pair tverts_range; std::pair qverts_range; // If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts // of the extrusions per layer. std::vector print_zs; // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer. std::vector offsets; int object_idx() const { return this->composite_id / 1000000; } int volume_idx() const { return (this->composite_id / 1000) % 1000; } int instance_idx() const { return this->composite_id % 1000; } BoundingBoxf3 transformed_bounding_box() const { BoundingBoxf3 bb = this->bounding_box; bb.translate(this->origin); return bb; } bool empty() const { return this->indexed_vertex_array.empty(); } bool indexed() const { return this->indexed_vertex_array.indexed(); } void set_range(coordf_t low, coordf_t high); void render() const; void finalize_geometry(bool use_VBOs) { this->indexed_vertex_array.finalize_geometry(use_VBOs); } void release_geometry() { this->indexed_vertex_array.release_geometry(); } /************************************************ Layer height texture ****************************************************/ std::shared_ptr layer_height_texture; bool has_layer_height_texture() const { return this->layer_height_texture.get() != nullptr; } size_t layer_height_texture_width() const { return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->width; } size_t layer_height_texture_height() const { return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->height; } size_t layer_height_texture_cells() const { return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->cells; } void* layer_height_texture_data_ptr_level0() { return (layer_height_texture.get() == nullptr) ? 0 : (void*)layer_height_texture->data.data(); } void* layer_height_texture_data_ptr_level1() { return (layer_height_texture.get() == nullptr) ? 0 : (void*)(layer_height_texture->data.data() + layer_height_texture->width * layer_height_texture->height * 4); } double layer_height_texture_z_to_row_id() const { return (this->layer_height_texture.get() == nullptr) ? 0. : double(this->layer_height_texture->cells - 1) / (double(this->layer_height_texture->width) * bounding_box.max.z); } void generate_layer_height_texture(PrintObject *print_object, bool force); }; class GLVolumeCollection { public: std::vector volumes; GLVolumeCollection() {}; ~GLVolumeCollection() { clear(); }; std::vector load_object( const ModelObject *model_object, int obj_idx, const std::vector &instance_idxs, const std::string &color_by, const std::string &select_by, const std::string &drag_by, bool use_VBOs); int load_wipe_tower_preview( int obj_idx, float pos_x, float pos_y, float width, float depth, float height, bool use_VBOs); // Render the volumes by OpenGL. void render_VBOs() const; void render_legacy() const; // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. void finalize_geometry(bool use_VBOs) { for (auto *v : volumes) v->finalize_geometry(use_VBOs); } // Release the geometry data assigned to the volumes. // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them. void release_geometry() { for (auto *v : volumes) v->release_geometry(); } // Clear the geometry void clear() { for (auto *v : volumes) delete v; volumes.clear(); } bool empty() const { return volumes.empty(); } void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); } private: GLVolumeCollection(const GLVolumeCollection &other); GLVolumeCollection& operator=(const GLVolumeCollection &); }; class _3DScene { public: static void _glew_init(); static void _load_print_toolpaths( const Print *print, GLVolumeCollection *volumes, const std::vector &tool_colors, bool use_VBOs); static void _load_print_object_toolpaths( const PrintObject *print_object, GLVolumeCollection *volumes, const std::vector &tool_colors, bool use_VBOs); static void _load_wipe_tower_toolpaths( const Print *print, GLVolumeCollection *volumes, const std::vector &tool_colors_str, bool use_VBOs); }; } #endif Slic3r-version_1.39.1/xs/src/slic3r/GUI/AppConfig.cpp000066400000000000000000000121231324354444700221400ustar00rootroot00000000000000#include #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/Utils.hpp" #include "AppConfig.hpp" #include #include #include #include #include #include #include #include #include #include namespace Slic3r { void AppConfig::reset() { m_storage.clear(); set_defaults(); }; // Override missing or keys with their defaults. void AppConfig::set_defaults() { // Reset the empty fields to defaults. if (get("autocenter").empty()) set("autocenter", "0"); // Disable background processing by default as it is not stable. if (get("background_processing").empty()) set("background_processing", "0"); // If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden. // By default, Prusa has the controller hidden. if (get("no_controller").empty()) set("no_controller", "1"); // If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available. if (get("no_defaults").empty()) set("no_defaults", "1"); if (get("show_incompatible_presets").empty()) set("show_incompatible_presets", "0"); // Version check is enabled by default in the config, but it is not implemented yet. if (get("version_check").empty()) set("version_check", "1"); // Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers. // https://github.com/prusa3d/Slic3r/issues/233 if (get("use_legacy_opengl").empty()) set("use_legacy_opengl", "0"); } void AppConfig::load() { // 1) Read the complete config file into a boost::property_tree. namespace pt = boost::property_tree; pt::ptree tree; boost::nowide::ifstream ifs(AppConfig::config_path()); pt::read_ini(ifs, tree); // 2) Parse the property_tree, extract the sections and key / value pairs. for (const auto §ion : tree) { if (section.second.empty()) { // This may be a top level (no section) entry, or an empty section. std::string data = section.second.data(); if (! data.empty()) // If there is a non-empty data, then it must be a top-level (without a section) config entry. m_storage[""][section.first] = data; } else { // This must be a section name. Read the entries of a section. std::map &storage = m_storage[section.first]; for (auto &kvp : section.second) storage[kvp.first] = kvp.second.data(); } } // Override missing or keys with their defaults. this->set_defaults(); m_dirty = false; } void AppConfig::save() { boost::nowide::ofstream c; c.open(AppConfig::config_path(), std::ios::out | std::ios::trunc); c << "# " << Slic3r::header_slic3r_generated() << std::endl; // Make sure the "no" category is written first. for (const std::pair &kvp : m_storage[""]) c << kvp.first << " = " << kvp.second << std::endl; // Write the other categories. for (const auto category : m_storage) { if (category.first.empty()) continue; c << std::endl << "[" << category.first << "]" << std::endl; for (const std::pair &kvp : category.second) c << kvp.first << " = " << kvp.second << std::endl; } c.close(); m_dirty = false; } std::string AppConfig::get_last_dir() const { const auto it = m_storage.find("recent"); if (it != m_storage.end()) { { const auto it2 = it->second.find("skein_directory"); if (it2 != it->second.end() && ! it2->second.empty()) return it2->second; } { const auto it2 = it->second.find("config_directory"); if (it2 != it->second.end() && ! it2->second.empty()) return it2->second; } } return std::string(); } void AppConfig::update_config_dir(const std::string &dir) { this->set("recent", "config_directory", dir); } void AppConfig::update_skein_dir(const std::string &dir) { this->set("recent", "skein_directory", dir); } std::string AppConfig::get_last_output_dir(const std::string &alt) const { const auto it = m_storage.find(""); if (it != m_storage.end()) { const auto it2 = it->second.find("last_output_path"); const auto it3 = it->second.find("remember_output_path"); if (it2 != it->second.end() && it3 != it->second.end() && ! it2->second.empty() && it3->second == "1") return it2->second; } return alt; } void AppConfig::update_last_output_dir(const std::string &dir) { this->set("", "last_output_path", dir); } std::string AppConfig::config_path() { return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string(); } bool AppConfig::exists() { return boost::filesystem::exists(AppConfig::config_path()); } }; // namespace Slic3r Slic3r-version_1.39.1/xs/src/slic3r/GUI/AppConfig.hpp000066400000000000000000000053661324354444700221600ustar00rootroot00000000000000#ifndef slic3r_AppConfig_hpp_ #define slic3r_AppConfig_hpp_ #include #include namespace Slic3r { class AppConfig { public: AppConfig() : m_dirty(false) { this->reset(); } // Clear and reset to defaults. void reset(); // Override missing or keys with their defaults. void set_defaults(); // Load the slic3r.ini from a user profile directory (or a datadir, if configured). void load(); // Store the slic3r.ini into a user profile directory (or a datadir, if configured). void save(); // Does this config need to be saved? bool dirty() const { return m_dirty; } // Const accessor, it will return false if a section or a key does not exist. bool get(const std::string §ion, const std::string &key, std::string &value) const { value.clear(); auto it = m_storage.find(section); if (it == m_storage.end()) return false; auto it2 = it->second.find(key); if (it2 == it->second.end()) return false; value = it2->second; return true; } std::string get(const std::string §ion, const std::string &key) const { std::string value; this->get(section, key, value); return value; } std::string get(const std::string &key) const { std::string value; this->get("", key, value); return value; } void set(const std::string §ion, const std::string &key, const std::string &value) { std::string &old = m_storage[section][key]; if (old != value) { old = value; m_dirty = true; } } void set(const std::string &key, const std::string &value) { this->set("", key, value); } bool has(const std::string §ion, const std::string &key) const { auto it = m_storage.find(section); if (it == m_storage.end()) return false; auto it2 = it->second.find(key); return it2 != it->second.end() && ! it2->second.empty(); } bool has(const std::string &key) const { return this->has("", key); } void clear_section(const std::string §ion) { m_storage[section].clear(); } // return recent/skein_directory or recent/config_directory or empty string. std::string get_last_dir() const; void update_config_dir(const std::string &dir); void update_skein_dir(const std::string &dir); std::string get_last_output_dir(const std::string &alt) const; void update_last_output_dir(const std::string &dir); // Get the default config path from Slic3r::data_dir(). static std::string config_path(); // Does the config file exist? static bool exists(); private: // Map of section, name -> value std::map> m_storage; // Has any value been modified since the config.ini has been last saved or loaded? bool m_dirty; }; }; // namespace Slic3r #endif /* slic3r_AppConfig_hpp_ */ Slic3r-version_1.39.1/xs/src/slic3r/GUI/GLShader.cpp000066400000000000000000000151131324354444700217250ustar00rootroot00000000000000#include #include "GLShader.hpp" #include #include #include namespace Slic3r { GLShader::~GLShader() { assert(fragment_program_id == 0); assert(vertex_program_id == 0); assert(shader_program_id == 0); } // A safe wrapper around glGetString to report a "N/A" string in case glGetString returns nullptr. inline std::string gl_get_string_safe(GLenum param) { const char *value = (const char*)glGetString(param); return std::string(value ? value : "N/A"); } bool GLShader::load(const char *fragment_shader, const char *vertex_shader) { std::string gl_version = gl_get_string_safe(GL_VERSION); int major = atoi(gl_version.c_str()); //int minor = atoi(gl_version.c_str() + gl_version.find('.') + 1); if (major < 2) { // Cannot create a shader object on OpenGL 1.x. // Form an error message. std::string gl_vendor = gl_get_string_safe(GL_VENDOR); std::string gl_renderer = gl_get_string_safe(GL_RENDERER); std::string glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION); last_error = "Your computer does not support OpenGL shaders.\n"; #ifdef _WIN32 if (gl_vendor == "Microsoft Corporation" && gl_renderer == "GDI Generic") { last_error = "Windows is using a software OpenGL renderer.\n" "You are either connected over remote desktop,\n" "or a hardware acceleration is not available.\n"; } #endif last_error += "GL version: " + gl_version + "\n"; last_error += "vendor: " + gl_vendor + "\n"; last_error += "renderer: " + gl_renderer + "\n"; last_error += "GLSL version: " + glsl_version + "\n"; return false; } if (fragment_shader != nullptr) { this->fragment_program_id = glCreateShader(GL_FRAGMENT_SHADER); if (this->fragment_program_id == 0) { last_error = "glCreateShader(GL_FRAGMENT_SHADER) failed."; return false; } GLint len = (GLint)strlen(fragment_shader); glShaderSource(this->fragment_program_id, 1, &fragment_shader, &len); glCompileShader(this->fragment_program_id); GLint params; glGetShaderiv(this->fragment_program_id, GL_COMPILE_STATUS, ¶ms); if (params == GL_FALSE) { // Compilation failed. Get the log. glGetShaderiv(this->fragment_program_id, GL_INFO_LOG_LENGTH, ¶ms); std::vector msg(params); glGetShaderInfoLog(this->fragment_program_id, params, ¶ms, msg.data()); this->last_error = std::string("Fragment shader compilation failed:\n") + msg.data(); this->release(); return false; } } if (vertex_shader != nullptr) { this->vertex_program_id = glCreateShader(GL_VERTEX_SHADER); if (this->vertex_program_id == 0) { last_error = "glCreateShader(GL_VERTEX_SHADER) failed."; this->release(); return false; } GLint len = (GLint)strlen(vertex_shader); glShaderSource(this->vertex_program_id, 1, &vertex_shader, &len); glCompileShader(this->vertex_program_id); GLint params; glGetShaderiv(this->vertex_program_id, GL_COMPILE_STATUS, ¶ms); if (params == GL_FALSE) { // Compilation failed. Get the log. glGetShaderiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, ¶ms); std::vector msg(params); glGetShaderInfoLog(this->vertex_program_id, params, ¶ms, msg.data()); this->last_error = std::string("Vertex shader compilation failed:\n") + msg.data(); this->release(); return false; } } // Link shaders this->shader_program_id = glCreateProgram(); if (this->shader_program_id == 0) { last_error = "glCreateProgram() failed."; this->release(); return false; } if (this->fragment_program_id) glAttachShader(this->shader_program_id, this->fragment_program_id); if (this->vertex_program_id) glAttachShader(this->shader_program_id, this->vertex_program_id); glLinkProgram(this->shader_program_id); GLint params; glGetProgramiv(this->shader_program_id, GL_LINK_STATUS, ¶ms); if (params == GL_FALSE) { // Linking failed. Get the log. glGetProgramiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, ¶ms); std::vector msg(params); glGetProgramInfoLog(this->vertex_program_id, params, ¶ms, msg.data()); this->last_error = std::string("Shader linking failed:\n") + msg.data(); this->release(); return false; } last_error.clear(); return true; } void GLShader::release() { if (this->shader_program_id) { if (this->vertex_program_id) glDetachShader(this->shader_program_id, this->vertex_program_id); if (this->fragment_program_id) glDetachShader(this->shader_program_id, this->fragment_program_id); glDeleteProgram(this->shader_program_id); this->shader_program_id = 0; } if (this->vertex_program_id) { glDeleteShader(this->vertex_program_id); this->vertex_program_id = 0; } if (this->fragment_program_id) { glDeleteShader(this->fragment_program_id); this->fragment_program_id = 0; } } void GLShader::enable() const { glUseProgram(this->shader_program_id); } void GLShader::disable() const { glUseProgram(0); } // Return shader vertex attribute ID int GLShader::get_attrib_location(const char *name) const { return this->shader_program_id ? glGetAttribLocation(this->shader_program_id, name) : -1; } // Return shader uniform variable ID int GLShader::get_uniform_location(const char *name) const { return this->shader_program_id ? glGetUniformLocation(this->shader_program_id, name) : -1; } bool GLShader::set_uniform(const char *name, float value) const { int id = this->get_uniform_location(name); if (id >= 0) { glUniform1fARB(id, value); return true; } return false; } /* # Set shader vector sub SetVector { my($self,$var,@values) = @_; my $id = $self->Map($var); return 'Unable to map $var' if (!defined($id)); my $count = scalar(@values); eval('glUniform'.$count.'fARB($id,@values)'); return ''; } # Set shader 4x4 matrix sub SetMatrix { my($self,$var,$oga) = @_; my $id = $self->Map($var); return 'Unable to map $var' if (!defined($id)); glUniformMatrix4fvARB_c($id,1,0,$oga->ptr()); return ''; } */ } // namespace Slic3r Slic3r-version_1.39.1/xs/src/slic3r/GUI/GLShader.hpp000066400000000000000000000014701324354444700217330ustar00rootroot00000000000000#ifndef slic3r_GLShader_hpp_ #define slic3r_GLShader_hpp_ #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/Point.hpp" namespace Slic3r { class GLShader { public: GLShader() : fragment_program_id(0), vertex_program_id(0), shader_program_id(0) {} ~GLShader(); bool load(const char *fragment_shader, const char *vertex_shader); void release(); int get_attrib_location(const char *name) const; int get_uniform_location(const char *name) const; bool set_uniform(const char *name, float value) const; void enable() const; void disable() const; unsigned int fragment_program_id; unsigned int vertex_program_id; unsigned int shader_program_id; std::string last_error; }; } #endif /* slic3r_GLShader_hpp_ */ Slic3r-version_1.39.1/xs/src/slic3r/GUI/GUI.cpp000066400000000000000000000136011324354444700207200ustar00rootroot00000000000000#include "GUI.hpp" #include #include #include #if __APPLE__ #import #elif _WIN32 #include #include "boost/nowide/convert.hpp" #pragma comment(lib, "user32.lib") #endif #include #include #include #include #include #include #include namespace Slic3r { namespace GUI { #if __APPLE__ IOPMAssertionID assertionID; #endif void disable_screensaver() { #if __APPLE__ CFStringRef reasonForActivity = CFSTR("Slic3r"); IOReturn success = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn, reasonForActivity, &assertionID); // ignore result: success == kIOReturnSuccess #elif _WIN32 SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS); #endif } void enable_screensaver() { #if __APPLE__ IOReturn success = IOPMAssertionRelease(assertionID); #elif _WIN32 SetThreadExecutionState(ES_CONTINUOUS); #endif } std::vector scan_serial_ports() { std::vector out; #ifdef _WIN32 // 1) Open the registry key SERIALCOM. HKEY hKey; LONG lRes = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_READ, &hKey); assert(lRes == ERROR_SUCCESS); if (lRes == ERROR_SUCCESS) { // 2) Get number of values of SERIALCOM key. DWORD cValues; // number of values for key { TCHAR achKey[255]; // buffer for subkey name DWORD cbName; // size of name string TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name DWORD cchClassName = MAX_PATH; // size of class string DWORD cSubKeys=0; // number of subkeys DWORD cbMaxSubKey; // longest subkey size DWORD cchMaxClass; // longest class string DWORD cchMaxValue; // longest value name DWORD cbMaxValueData; // longest value data DWORD cbSecurityDescriptor; // size of security descriptor FILETIME ftLastWriteTime; // last write time // Get the class name and the value count. lRes = RegQueryInfoKey( hKey, // key handle achClass, // buffer for class name &cchClassName, // size of class string NULL, // reserved &cSubKeys, // number of subkeys &cbMaxSubKey, // longest subkey size &cchMaxClass, // longest class string &cValues, // number of values for this key &cchMaxValue, // longest value name &cbMaxValueData, // longest value data &cbSecurityDescriptor, // security descriptor &ftLastWriteTime); // last write time assert(lRes == ERROR_SUCCESS); } // 3) Read the SERIALCOM values. { DWORD dwIndex = 0; for (int i = 0; i < cValues; ++ i, ++ dwIndex) { wchar_t valueName[2048]; DWORD valNameLen = 2048; DWORD dataType; wchar_t data[2048]; DWORD dataSize = 4096; lRes = ::RegEnumValueW(hKey, dwIndex, valueName, &valNameLen, nullptr, &dataType, (BYTE*)&data, &dataSize); if (lRes == ERROR_SUCCESS && dataType == REG_SZ && valueName[0] != 0) out.emplace_back(boost::nowide::narrow(data)); } } ::RegCloseKey(hKey); } #else // UNIX and OS X std::initializer_list prefixes { "ttyUSB" , "ttyACM", "tty.", "cu.", "rfcomm" }; for (auto &dir_entry : boost::filesystem::directory_iterator(boost::filesystem::path("/dev"))) { std::string name = dir_entry.path().filename().string(); for (const char *prefix : prefixes) { if (boost::starts_with(name, prefix)) { out.emplace_back(dir_entry.path().string()); break; } } } #endif out.erase(std::remove_if(out.begin(), out.end(), [](const std::string &key){ return boost::starts_with(key, "Bluetooth") || boost::starts_with(key, "FireFly"); }), out.end()); return out; } bool debugged() { #ifdef _WIN32 return IsDebuggerPresent(); #else return false; #endif /* _WIN32 */ } void break_to_debugger() { #ifdef _WIN32 if (IsDebuggerPresent()) DebugBreak(); #endif /* _WIN32 */ } // Passing the wxWidgets GUI classes instantiated by the Perl part to C++. wxApp *g_wxApp = nullptr; wxFrame *g_wxMainFrame = nullptr; wxNotebook *g_wxTabPanel = nullptr; void set_wxapp(wxApp *app) { g_wxApp = app; } void set_main_frame(wxFrame *main_frame) { g_wxMainFrame = main_frame; } void set_tab_panel(wxNotebook *tab_panel) { g_wxTabPanel = tab_panel; } void add_debug_menu(wxMenuBar *menu) { #if 0 auto debug_menu = new wxMenu(); debug_menu->Append(wxWindow::NewControlId(1), "Some debug"); menu->Append(debug_menu, _T("&Debug")); #endif } void create_preset_tab(const char *name) { auto *panel = new wxPanel(g_wxTabPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); // Vertical sizer to hold the choice menu and the rest of the page. auto *sizer = new wxBoxSizer(wxVERTICAL); sizer->SetSizeHints(panel); panel->SetSizer(sizer); auto *button = new wxButton(panel, wxID_ANY, "Hello World", wxDefaultPosition, wxDefaultSize, 0); sizer->Add(button, 0, 0, 0); g_wxTabPanel->AddPage(panel, name); } } } Slic3r-version_1.39.1/xs/src/slic3r/GUI/GUI.hpp000066400000000000000000000013051324354444700207230ustar00rootroot00000000000000#ifndef slic3r_GUI_hpp_ #define slic3r_GUI_hpp_ #include #include class wxApp; class wxFrame; class wxMenuBar; class wxNotebook; namespace Slic3r { namespace GUI { void disable_screensaver(); void enable_screensaver(); std::vector scan_serial_ports(); bool debugged(); void break_to_debugger(); // Passing the wxWidgets GUI classes instantiated by the Perl part to C++. void set_wxapp(wxApp *app); void set_main_frame(wxFrame *main_frame); void set_tab_panel(wxNotebook *tab_panel); void add_debug_menu(wxMenuBar *menu); // Create a new preset tab (print, filament or printer), // add it at the end of the tab panel. void create_preset_tab(const char *name); } } #endif Slic3r-version_1.39.1/xs/src/slic3r/GUI/Preset.cpp000066400000000000000000000650071324354444700215450ustar00rootroot00000000000000//#undef NDEBUG #include #include "Preset.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/Utils.hpp" #include "../../libslic3r/PlaceholderParser.hpp" namespace Slic3r { ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree) { size_t app_config = 0; size_t bundle = 0; size_t config = 0; for (const boost::property_tree::ptree::value_type &v : tree) { if (v.second.empty()) { if (v.first == "background_processing" || v.first == "last_output_path" || v.first == "no_controller" || v.first == "no_defaults") ++ app_config; else if (v.first == "nozzle_diameter" || v.first == "filament_diameter") ++ config; } else if (boost::algorithm::starts_with(v.first, "print:") || boost::algorithm::starts_with(v.first, "filament:") || boost::algorithm::starts_with(v.first, "printer:") || v.first == "settings") ++ bundle; else if (v.first == "presets") { ++ app_config; ++ bundle; } else if (v.first == "recent") { for (auto &kvp : v.second) if (kvp.first == "config_directory" || kvp.first == "skein_directory") ++ app_config; } } return (app_config > bundle && app_config > config) ? CONFIG_FILE_TYPE_APP_CONFIG : (bundle > config) ? CONFIG_FILE_TYPE_CONFIG_BUNDLE : CONFIG_FILE_TYPE_CONFIG; } // Suffix to be added to a modified preset name in the combo box. static std::string g_suffix_modified = " (modified)"; const std::string& Preset::suffix_modified() { return g_suffix_modified; } // Remove an optional "(modified)" suffix from a name. // This converts a UI name to a unique preset identifier. std::string Preset::remove_suffix_modified(const std::string &name) { return boost::algorithm::ends_with(name, g_suffix_modified) ? name.substr(0, name.size() - g_suffix_modified.size()) : name; } void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extruders) { const auto &defaults = FullPrintConfig::defaults(); for (const std::string &key : Preset::nozzle_options()) { auto *opt = config.option(key, false); assert(opt != nullptr); assert(opt->is_vector()); if (opt != nullptr && opt->is_vector()) static_cast(opt)->resize(num_extruders, defaults.option(key)); } } // Update new extruder fields at the printer profile. void Preset::normalize(DynamicPrintConfig &config) { auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter")); if (nozzle_diameter != nullptr) // Loaded the Printer settings. Verify, that all extruder dependent values have enough values. set_num_extruders(config, (unsigned int)nozzle_diameter->values.size()); if (config.option("filament_diameter") != nullptr) { // This config contains single or multiple filament presets. // Ensure that the filament preset vector options contain the correct number of values. size_t n = (nozzle_diameter == nullptr) ? 1 : nozzle_diameter->values.size(); const auto &defaults = FullPrintConfig::defaults(); for (const std::string &key : Preset::filament_options()) { if (key == "compatible_printers") continue; auto *opt = config.option(key, false); assert(opt != nullptr); assert(opt->is_vector()); if (opt != nullptr && opt->is_vector()) static_cast(opt)->resize(n, defaults.option(key)); } } } // Load a config file, return a C++ class Slic3r::DynamicPrintConfig with $keys initialized from the config file. // In case of a "default" config item, return the default values. DynamicPrintConfig& Preset::load(const std::vector &keys) { // Set the configuration from the defaults. Slic3r::FullPrintConfig defaults; this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); if (! this->is_default) { // Load the preset file, apply preset values on top of defaults. try { this->config.load_from_ini(this->file); Preset::normalize(this->config); } catch (const std::ifstream::failure &err) { throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + this->file + "\n\tReason: " + err.what()); } catch (const std::runtime_error &err) { throw std::runtime_error(std::string("Failed loading the preset file: ") + this->file + "\n\tReason: " + err.what()); } } this->loaded = true; return this->config; } void Preset::save() { this->config.save(this->file); } // Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty. std::string Preset::label() const { return this->name + (this->is_dirty ? g_suffix_modified : ""); } bool Preset::is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const { auto *condition = dynamic_cast(this->config.option("compatible_printers_condition")); auto *compatible_printers = dynamic_cast(this->config.option("compatible_printers")); bool has_compatible_printers = compatible_printers != nullptr && ! compatible_printers->values.empty(); if (! has_compatible_printers && condition != nullptr && ! condition->value.empty()) { try { return PlaceholderParser::evaluate_boolean_expression(condition->value, active_printer.config, extra_config); } catch (const std::runtime_error &err) { //FIXME in case of an error, return "compatible with everything". printf("Preset::is_compatible_with_printer - parsing error of compatible_printers_condition %s:\n%s\n", active_printer.name.c_str(), err.what()); return true; } } return this->is_default || active_printer.name.empty() || ! has_compatible_printers || std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.name) != compatible_printers->values.end(); } bool Preset::is_compatible_with_printer(const Preset &active_printer) const { DynamicPrintConfig config; config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); config.set_key_value("num_extruders", new ConfigOptionInt( (int)static_cast(active_printer.config.option("nozzle_diameter"))->values.size())); return this->is_compatible_with_printer(active_printer, &config); } bool Preset::update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) { return this->is_compatible = is_compatible_with_printer(active_printer, extra_config); } const std::vector& Preset::print_options() { static std::vector s_opts { "layer_height", "first_layer_height", "perimeters", "spiral_vase", "top_solid_layers", "bottom_solid_layers", "extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs", "seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "external_fill_pattern", "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "max_print_speed", "max_volumetric_speed", "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "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_width", "support_material", "support_material_threshold", "support_material_enforce_layers", "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_interface_spacing", "support_material_interface_contact_loops", "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", "extrusion_width", "first_layer_extrusion_width", "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_per_color_wipe", "compatible_printers", "compatible_printers_condition" }; return s_opts; } const std::vector& Preset::filament_options() { static std::vector s_opts { "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "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", "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", "compatible_printers", "compatible_printers_condition" }; return s_opts; } const std::vector& Preset::printer_options() { static std::vector s_opts; if (s_opts.empty()) { s_opts = { "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "octoprint_host", "octoprint_apikey", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "between_objects_gcode", "printer_notes" }; s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end()); } return s_opts; } const std::vector& Preset::nozzle_options() { // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings static std::vector s_opts { "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour" }; return s_opts; } PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys) : m_type(type), m_edited_preset(type, "", false), m_idx_selected(0), m_bitmap_main_frame(new wxBitmap) { // Insert just the default preset. m_presets.emplace_back(Preset(type, "- default -", true)); m_presets.front().load(keys); m_edited_preset.config.apply(m_presets.front().config); } PresetCollection::~PresetCollection() { delete m_bitmap_main_frame; m_bitmap_main_frame = nullptr; } void PresetCollection::reset(bool delete_files) { if (m_presets.size() > 1) { if (delete_files) { // Erase the preset files. for (Preset &preset : m_presets) if (! preset.is_default && ! preset.is_external) boost::nowide::remove(preset.file.c_str()); } // Don't use m_presets.resize() here as it requires a default constructor for Preset. m_presets.erase(m_presets.begin() + 1, m_presets.end()); this->select_preset(0); } } // Load all presets found in dir_path. // Throws an exception on error. void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) { boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred(); m_dir_path = dir.string(); m_presets.erase(m_presets.begin()+1, m_presets.end()); t_config_option_keys keys = this->default_preset().config.keys(); std::string errors_cummulative; for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) { std::string name = dir_entry.path().filename().string(); // Remove the .ini suffix. name.erase(name.size() - 4); try { Preset preset(m_type, name, false); preset.file = dir_entry.path().string(); preset.load(keys); m_presets.emplace_back(preset); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); errors_cummulative += "\n"; } } std::sort(m_presets.begin() + 1, m_presets.end()); this->select_preset(first_visible_idx()); if (! errors_cummulative.empty()) throw std::runtime_error(errors_cummulative); } // Load a preset from an already parsed config file, insert it into the sorted sequence of presets // and select it, losing previous modifications. Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select) { DynamicPrintConfig cfg(this->default_preset().config); cfg.apply_only(config, cfg.keys(), true); return this->load_preset(path, name, std::move(cfg)); } Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select) { auto it = this->find_preset_internal(name); if (it == m_presets.end() || it->name != name) { // The preset was not found. Create a new preset. it = m_presets.emplace(it, Preset(m_type, name, false)); } Preset &preset = *it; preset.file = path; preset.config = std::move(config); preset.loaded = true; preset.is_dirty = false; if (select) this->select_preset_by_name(name, true); return preset; } void PresetCollection::save_current_preset(const std::string &new_name) { // 1) Find the preset with a new_name or create a new one, // initialize it with the edited config. auto it = this->find_preset_internal(new_name); if (it != m_presets.end() && it->name == new_name) { // Preset with the same name found. Preset &preset = *it; if (preset.is_default) // Cannot overwrite the default preset. return; // Overwriting an existing preset. preset.config = std::move(m_edited_preset.config); } else { // Creating a new preset. Preset &preset = *m_presets.insert(it, m_edited_preset); preset.name = new_name; preset.file = this->path_from_name(new_name); } // 2) Activate the saved preset. this->select_preset_by_name(new_name, true); // 2) Store the active preset to disk. this->get_selected_preset().save(); } void PresetCollection::delete_current_preset() { const Preset &selected = this->get_selected_preset(); if (selected.is_default) return; if (! selected.is_external) { // Erase the preset file. boost::nowide::remove(selected.file.c_str()); } // Remove the preset from the list. m_presets.erase(m_presets.begin() + m_idx_selected); // Find the next visible preset. size_t new_selected_idx = m_idx_selected; if (new_selected_idx < m_presets.size()) for (; new_selected_idx < m_presets.size() && ! m_presets[new_selected_idx].is_visible; ++ new_selected_idx) ; if (new_selected_idx == m_presets.size()) for (--new_selected_idx; new_selected_idx > 0 && !m_presets[new_selected_idx].is_visible; --new_selected_idx); this->select_preset(new_selected_idx); } bool PresetCollection::load_bitmap_default(const std::string &file_name) { return m_bitmap_main_frame->LoadFile(wxString::FromUTF8(Slic3r::var(file_name).c_str()), wxBITMAP_TYPE_PNG); } // Return a preset by its name. If the preset is active, a temporary copy is returned. // If a preset is not found by its name, null is returned. Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found) { Preset key(m_type, name, false); auto it = this->find_preset_internal(name); // Ensure that a temporary copy is returned if the preset found is currently selected. return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin()) : first_visible_if_not_found ? &this->first_visible() : nullptr; } // Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible. size_t PresetCollection::first_visible_idx() const { size_t idx = m_default_suppressed ? 1 : 0; for (; idx < this->m_presets.size(); ++ idx) if (m_presets[idx].is_visible) break; if (idx == this->m_presets.size()) idx = 0; return idx; } // Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible. size_t PresetCollection::first_compatible_idx() const { size_t idx = m_default_suppressed ? 1 : 0; for (; idx < this->m_presets.size(); ++ idx) if (m_presets[idx].is_compatible) break; if (idx == this->m_presets.size()) idx = 0; return idx; } void PresetCollection::set_default_suppressed(bool default_suppressed) { if (m_default_suppressed != default_suppressed) { m_default_suppressed = default_suppressed; m_presets.front().is_visible = ! default_suppressed || (m_presets.size() > 1 && m_idx_selected > 0); } } void PresetCollection::update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible) { DynamicPrintConfig config; config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); config.set_key_value("num_extruders", new ConfigOptionInt( (int)static_cast(active_printer.config.option("nozzle_diameter"))->values.size())); for (size_t idx_preset = 1; idx_preset < m_presets.size(); ++ idx_preset) { bool selected = idx_preset == m_idx_selected; Preset &preset_selected = m_presets[idx_preset]; Preset &preset_edited = selected ? m_edited_preset : preset_selected; if (! preset_edited.update_compatible_with_printer(active_printer, &config) && selected && select_other_if_incompatible) m_idx_selected = (size_t)-1; if (selected) preset_selected.is_compatible = preset_edited.is_compatible; } if (m_idx_selected == (size_t)-1) // Find some other compatible preset, or the "-- default --" preset. this->select_preset(first_compatible_idx()); } // Save the preset under a new name. If the name is different from the old one, // a new preset is stored into the list of presets. // All presets are marked as not modified and the new preset is activated. //void PresetCollection::save_current_preset(const std::string &new_name); // Delete the current preset, activate the first visible preset. //void PresetCollection::delete_current_preset(); // Update the wxChoice UI component from this list of presets. // Hide the void PresetCollection::update_platter_ui(wxBitmapComboBox *ui) { if (ui == nullptr) return; // Otherwise fill in the list from scratch. ui->Freeze(); ui->Clear(); for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected)) continue; const wxBitmap *bmp = (i == 0 || preset.is_compatible) ? m_bitmap_main_frame : m_bitmap_incompatible; ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); if (i == m_idx_selected) ui->SetSelection(ui->GetCount() - 1); } ui->Thaw(); } void PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible) { if (ui == nullptr) return; ui->Freeze(); ui->Clear(); for (size_t i = this->m_presets.front().is_visible ? 0 : 1; i < this->m_presets.size(); ++ i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)) continue; const wxBitmap *bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible; ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); if (i == m_idx_selected) ui->SetSelection(ui->GetCount() - 1); } ui->Thaw(); } // Update a dirty floag of the current preset, update the labels of the UI component accordingly. // Return true if the dirty flag changed. bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) { wxWindowUpdateLocker noUpdates(ui); // 1) Update the dirty flag of the current preset. bool was_dirty = this->get_selected_preset().is_dirty; bool is_dirty = current_is_dirty(); this->get_selected_preset().is_dirty = is_dirty; this->get_edited_preset().is_dirty = is_dirty; // 2) Update the labels. for (unsigned int ui_id = 0; ui_id < ui->GetCount(); ++ ui_id) { std::string old_label = ui->GetString(ui_id).utf8_str().data(); std::string preset_name = Preset::remove_suffix_modified(old_label); const Preset *preset = this->find_preset(preset_name, false); assert(preset != nullptr); if (preset != nullptr) { std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name; if (old_label != new_label) ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str())); } } #ifdef __APPLE__ // wxWidgets on OSX do not upload the text of the combo box line automatically. // Force it to update by re-selecting. ui->SetSelection(ui->GetSelection()); #endif /* __APPLE __ */ return was_dirty != is_dirty; } std::vector PresetCollection::current_dirty_options() const { std::vector changed = this->get_selected_preset().config.diff(this->get_edited_preset().config); // The "compatible_printers" option key is handled differently from the others: // It is not mandatory. If the key is missing, it means it is compatible with any printer. // If the key exists and it is empty, it means it is compatible with no printer. std::initializer_list optional_keys { "compatible_printers", "compatible_printers_condition" }; for (auto &opt_key : optional_keys) { if (this->get_selected_preset().config.has(opt_key) != this->get_edited_preset().config.has(opt_key)) changed.emplace_back(opt_key); } return changed; } // Select a new preset. This resets all the edits done to the currently selected preset. // If the preset with index idx does not exist, a first visible preset is selected. Preset& PresetCollection::select_preset(size_t idx) { for (Preset &preset : m_presets) preset.is_dirty = false; if (idx >= m_presets.size()) idx = first_visible_idx(); m_idx_selected = idx; m_edited_preset = m_presets[idx]; m_presets.front().is_visible = ! m_default_suppressed || m_idx_selected == 0; return m_presets[idx]; } bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force) { std::string name = Preset::remove_suffix_modified(name_w_suffix); // 1) Try to find the preset by its name. auto it = this->find_preset_internal(name); size_t idx = 0; if (it != m_presets.end() && it->name == name) // Preset found by its name. idx = it - m_presets.begin(); else { // Find the first visible preset. for (size_t i = m_default_suppressed ? 1 : 0; i < m_presets.size(); ++ i) if (m_presets[i].is_visible) { idx = i; break; } // If the first visible preset was not found, return the 0th element, which is the default preset. } // 2) Select the new preset. if (m_idx_selected != idx || force) { this->select_preset(idx); return true; } return false; } std::string PresetCollection::name() const { switch (this->type()) { case Preset::TYPE_PRINT: return "print"; case Preset::TYPE_FILAMENT: return "filament"; case Preset::TYPE_PRINTER: return "printer"; default: return "invalid"; } } // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string PresetCollection::path_from_name(const std::string &new_name) const { std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini"); return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/slic3r/GUI/Preset.hpp000066400000000000000000000321571324354444700215520ustar00rootroot00000000000000#ifndef slic3r_Preset_hpp_ #define slic3r_Preset_hpp_ #include #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/PrintConfig.hpp" class wxBitmap; class wxChoice; class wxBitmapComboBox; class wxItemContainer; namespace Slic3r { enum ConfigFileType { CONFIG_FILE_TYPE_UNKNOWN, CONFIG_FILE_TYPE_APP_CONFIG, CONFIG_FILE_TYPE_CONFIG, CONFIG_FILE_TYPE_CONFIG_BUNDLE, }; extern ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree); class Preset { public: enum Type { TYPE_INVALID, TYPE_PRINT, TYPE_FILAMENT, TYPE_PRINTER, }; Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {} Type type = TYPE_INVALID; // The preset represents a "default" set of properties, // pulled from the default values of the PrintConfig (see PrintConfigDef for their definitions). bool is_default; // External preset points to a configuration, which has been loaded but not imported // into the Slic3r default configuration location. bool is_external = false; // Preset is visible, if it is compatible with the active Printer. // Also the "default" preset is only visible, if it is the only preset in the list. bool is_visible = true; // Has this preset been modified? bool is_dirty = false; // Is this preset compatible with the currently active printer? bool is_compatible = true; // Name of the preset, usually derived form the file name. std::string name; // File name of the preset. This could be a Print / Filament / Printer preset, // or a Configuration file bundling the Print + Filament + Printer presets (in that case is_external will be true), // or it could be a G-code (again, is_external will be true). std::string file; // Has this profile been loaded? bool loaded = false; // Configuration data, loaded from a file, or set from the defaults. DynamicPrintConfig config; // Load this profile for the following keys only. // Throws std::runtime_error in case the file cannot be read. DynamicPrintConfig& load(const std::vector &keys); void save(); // Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty. std::string label() const; // Set the is_dirty flag if the provided config is different from the active one. void set_dirty(const DynamicPrintConfig &config) { this->is_dirty = ! this->config.diff(config).empty(); } void set_dirty(bool dirty = true) { this->is_dirty = dirty; } void reset_dirty() { this->is_dirty = false; } bool is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const; bool is_compatible_with_printer(const Preset &active_printer) const; // Mark this preset as compatible if it is compatible with active_printer. bool update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config); // Resize the extruder specific fields, initialize them with the content of the 1st extruder. void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); } // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. bool operator<(const Preset &other) const { return this->name < other.name; } static const std::vector& print_options(); static const std::vector& filament_options(); // Printer options contain the nozzle options. static const std::vector& printer_options(); // Nozzle options of the printer options. static const std::vector& nozzle_options(); protected: friend class PresetCollection; friend class PresetBundle; static void normalize(DynamicPrintConfig &config); // Resize the extruder specific vectors () static void set_num_extruders(DynamicPrintConfig &config, unsigned int n); static const std::string& suffix_modified(); static std::string remove_suffix_modified(const std::string &name); }; // Collections of presets of the same type (one of the Print, Filament or Printer type). class PresetCollection { public: // Initialize the PresetCollection with the "- default -" preset. PresetCollection(Preset::Type type, const std::vector &keys); ~PresetCollection(); void reset(bool delete_files); Preset::Type type() const { return m_type; } std::string name() const; const std::deque& operator()() const { return m_presets; } // Load ini files of the particular type from the provided directory path. void load_presets(const std::string &dir_path, const std::string &subdir); // Load a preset from an already parsed config file, insert it into the sorted sequence of presets // and select it, losing previous modifications. Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true); Preset& load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select = true); // Save the preset under a new name. If the name is different from the old one, // a new preset is stored into the list of presets. // All presets are marked as not modified and the new preset is activated. void save_current_preset(const std::string &new_name); // Delete the current preset, activate the first visible preset. void delete_current_preset(); // Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame. bool load_bitmap_default(const std::string &file_name); // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items. void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; } void set_bitmap_incompatible(const wxBitmap *bmp) { m_bitmap_incompatible = bmp; } // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); bool is_default_suppressed() const { return m_default_suppressed; } // Select a preset. If an invalid index is provided, the first visible preset is selected. Preset& select_preset(size_t idx); // Return the selected preset, without the user modifications applied. Preset& get_selected_preset() { return m_presets[m_idx_selected]; } const Preset& get_selected_preset() const { return m_presets[m_idx_selected]; } int get_selected_idx() const { return m_idx_selected; } // Return the selected preset including the user modifications. Preset& get_edited_preset() { return m_edited_preset; } const Preset& get_edited_preset() const { return m_edited_preset; } // Return a preset possibly with modifications. const Preset& default_preset() const { return m_presets.front(); } // Return a preset by an index. If the preset is active, a temporary copy is returned. Preset& preset(size_t idx) { return (int(idx) == m_idx_selected) ? m_edited_preset : m_presets[idx]; } const Preset& preset(size_t idx) const { return const_cast(this)->preset(idx); } void discard_current_changes() { m_presets[m_idx_selected].reset_dirty(); m_edited_preset = m_presets[m_idx_selected]; } // Return a preset by its name. If the preset is active, a temporary copy is returned. // If a preset is not found by its name, null is returned. Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false); const Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false) const { return const_cast(this)->find_preset(name, first_visible_if_not_found); } size_t first_visible_idx() const; size_t first_compatible_idx() const; // Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible. // Return the first visible preset. Certainly at least the '- default -' preset shall be visible. Preset& first_visible() { return this->preset(this->first_visible_idx()); } const Preset& first_visible() const { return this->preset(this->first_visible_idx()); } Preset& first_compatible() { return this->preset(this->first_compatible_idx()); } const Preset& first_compatible() const { return this->preset(this->first_compatible_idx()); } // Return number of presets including the "- default -" preset. size_t size() const { return this->m_presets.size(); } // For Print / Filament presets, disable those, which are not compatible with the printer. void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible); size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); } // Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ. bool current_is_dirty() const { return ! this->current_dirty_options().empty(); } // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. std::vector current_dirty_options() const; // Update the choice UI from the list of presets. // If show_incompatible, all presets are shown, otherwise only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. void update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible); // Update the choice UI from the list of presets. // Only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. void update_platter_ui(wxBitmapComboBox *ui); // Update a dirty floag of the current preset, update the labels of the UI component accordingly. // Return true if the dirty flag changed. bool update_dirty_ui(wxBitmapComboBox *ui); // Select a profile by its name. Return true if the selection changed. // Without force, the selection is only updated if the index changes. // With force, the changes are reverted if the new index is the same as the old index. bool select_preset_by_name(const std::string &name, bool force); // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string &new_name) const; private: PresetCollection(); PresetCollection(const PresetCollection &other); PresetCollection& operator=(const PresetCollection &other); // Find a preset in the sorted list of presets. // The "-- default -- " preset is always the first, so it needs // to be handled differently. std::deque::iterator find_preset_internal(const std::string &name) { Preset key(m_type, name); auto it = std::lower_bound(m_presets.begin() + 1, m_presets.end(), key); return ((it == m_presets.end() || it->name != name) && m_presets.front().name == name) ? m_presets.begin() : it; } std::deque::const_iterator find_preset_internal(const std::string &name) const { return const_cast(this)->find_preset_internal(name); } // Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER. Preset::Type m_type; // List of presets, starting with the "- default -" preset. // Use deque to force the container to allocate an object per each entry, // so that the addresses of the presets don't change during resizing of the container. std::deque m_presets; // Initially this preset contains a copy of the selected preset. Later on, this copy may be modified by the user. Preset m_edited_preset; // Selected preset. int m_idx_selected; // Is the "- default -" preset suppressed? bool m_default_suppressed = true; // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Platter. // These bitmaps are not owned by PresetCollection, but by a PresetBundle. const wxBitmap *m_bitmap_compatible = nullptr; const wxBitmap *m_bitmap_incompatible = nullptr; // Marks placed at the wxBitmapComboBox of a MainFrame. // These bitmaps are owned by PresetCollection. wxBitmap *m_bitmap_main_frame; // Path to the directory to store the config files into. std::string m_dir_path; }; } // namespace Slic3r #endif /* slic3r_Preset_hpp_ */ Slic3r-version_1.39.1/xs/src/slic3r/GUI/PresetBundle.cpp000066400000000000000000001046461324354444700227020ustar00rootroot00000000000000//#undef NDEBUG #include #include "PresetBundle.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/PlaceholderParser.hpp" #include "../../libslic3r/Utils.hpp" // Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir. // This breaks compatibility with the upstream Slic3r if the --datadir is used to switch between the two versions. // #define SLIC3R_PROFILE_USE_PRESETS_SUBDIR namespace Slic3r { PresetBundle::PresetBundle() : prints(Preset::TYPE_PRINT, Preset::print_options()), filaments(Preset::TYPE_FILAMENT, Preset::filament_options()), printers(Preset::TYPE_PRINTER, Preset::printer_options()), m_bitmapCompatible(new wxBitmap), m_bitmapIncompatible(new wxBitmap) { if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) wxImage::AddHandler(new wxPNGHandler); // Create the ID config keys, as they are not part of the Static print config classes. this->prints.preset(0).config.opt_string("print_settings_id", true); this->filaments.preset(0).config.opt_string("filament_settings_id", true); this->printers.preset(0).config.opt_string("print_settings_id", true); // Create the "compatible printers" keys, as they are not part of the Static print config classes. this->filaments.preset(0).config.optptr("compatible_printers", true); this->filaments.preset(0).config.optptr("compatible_printers_condition", true); this->prints.preset(0).config.optptr("compatible_printers", true); this->prints.preset(0).config.optptr("compatible_printers_condition", true); this->prints .load_bitmap_default("cog.png"); this->filaments.load_bitmap_default("spool.png"); this->printers .load_bitmap_default("printer_empty.png"); this->load_compatible_bitmaps(); } PresetBundle::~PresetBundle() { assert(m_bitmapCompatible != nullptr); assert(m_bitmapIncompatible != nullptr); delete m_bitmapCompatible; m_bitmapCompatible = nullptr; delete m_bitmapIncompatible; m_bitmapIncompatible = nullptr; for (std::pair &bitmap : m_mapColorToBitmap) delete bitmap.second; } void PresetBundle::reset(bool delete_files) { // Clear the existing presets, delete their respective files. this->prints .reset(delete_files); this->filaments.reset(delete_files); this->printers .reset(delete_files); this->filament_presets.clear(); this->filament_presets.emplace_back(this->filaments.get_selected_preset().name); } void PresetBundle::setup_directories() { boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); std::initializer_list paths = { data_dir, #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. data_dir / "presets", data_dir / "presets" / "print", data_dir / "presets" / "filament", data_dir / "presets" / "printer" #else // Store the print/filament/printer presets at the same location as the upstream Slic3r. data_dir / "print", data_dir / "filament", data_dir / "printer" #endif }; for (const boost::filesystem::path &path : paths) { boost::filesystem::path subdir = path; subdir.make_preferred(); if (! boost::filesystem::is_directory(subdir) && ! boost::filesystem::create_directory(subdir)) throw std::runtime_error(std::string("Slic3r was unable to create its data directory at ") + subdir.string()); } } void PresetBundle::load_presets() { std::string errors_cummulative; const std::string dir_path = data_dir() #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. + "/presets" #else // Store the print/filament/printer presets at the same location as the upstream Slic3r. #endif ; try { this->prints.load_presets(dir_path, "print"); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { this->filaments.load_presets(dir_path, "filament"); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { this->printers.load_presets(dir_path, "printer"); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } this->update_multi_material_filament_presets(); this->update_compatible_with_printer(false); if (! errors_cummulative.empty()) throw std::runtime_error(errors_cummulative); } static inline std::string remove_ini_suffix(const std::string &name) { std::string out = name; if (boost::iends_with(out, ".ini")) out.erase(out.end() - 4, out.end()); return out; } // Load selections (current print, current filaments, current printer) from config.ini // This is done just once on application start up. void PresetBundle::load_selections(const AppConfig &config) { prints.select_preset_by_name(remove_ini_suffix(config.get("presets", "print")), true); filaments.select_preset_by_name(remove_ini_suffix(config.get("presets", "filament")), true); printers.select_preset_by_name(remove_ini_suffix(config.get("presets", "printer")), true); auto *nozzle_diameter = dynamic_cast(printers.get_selected_preset().config.option("nozzle_diameter")); size_t num_extruders = nozzle_diameter->values.size(); this->set_filament_preset(0, filaments.get_selected_preset().name); for (unsigned int i = 1; i < (unsigned int)num_extruders; ++ i) { char name[64]; sprintf(name, "filament_%d", i); if (! config.has("presets", name)) break; this->set_filament_preset(i, remove_ini_suffix(config.get("presets", name))); } // Update visibility of presets based on their compatibility with the active printer. // Always try to select a compatible print and filament preset to the current printer preset, // as the application may have been closed with an active "external" preset, which does not // exist. this->update_compatible_with_printer(true); } // Export selections (current print, current filaments, current printer) into config.ini void PresetBundle::export_selections(AppConfig &config) { assert(filament_presets.size() >= 1); assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front()); config.clear_section("presets"); config.set("presets", "print", prints.get_selected_preset().name); config.set("presets", "filament", filament_presets.front()); for (int i = 1; i < filament_presets.size(); ++i) { char name[64]; sprintf(name, "filament_%d", i); config.set("presets", name, filament_presets[i]); } config.set("presets", "printer", printers.get_selected_preset().name); } void PresetBundle::export_selections(PlaceholderParser &pp) { assert(filament_presets.size() >= 1); assert(filament_presets.size() > 1 || filaments.get_selected_preset().name == filament_presets.front()); pp.set("print_preset", prints.get_selected_preset().name); pp.set("filament_preset", filament_presets); pp.set("printer_preset", printers.get_selected_preset().name); } bool PresetBundle::load_compatible_bitmaps() { const std::string path_bitmap_compatible = "flag-green-icon.png"; const std::string path_bitmap_incompatible = "flag-red-icon.png"; bool loaded_compatible = m_bitmapCompatible ->LoadFile( wxString::FromUTF8(Slic3r::var(path_bitmap_compatible).c_str()), wxBITMAP_TYPE_PNG); bool loaded_incompatible = m_bitmapIncompatible->LoadFile( wxString::FromUTF8(Slic3r::var(path_bitmap_incompatible).c_str()), wxBITMAP_TYPE_PNG); if (loaded_compatible) { prints .set_bitmap_compatible(m_bitmapCompatible); filaments.set_bitmap_compatible(m_bitmapCompatible); // printers .set_bitmap_compatible(m_bitmapCompatible); } if (loaded_incompatible) { prints .set_bitmap_incompatible(m_bitmapIncompatible); filaments.set_bitmap_incompatible(m_bitmapIncompatible); // printers .set_bitmap_incompatible(m_bitmapIncompatible); } return loaded_compatible && loaded_incompatible; } DynamicPrintConfig PresetBundle::full_config() const { DynamicPrintConfig out; out.apply(FullPrintConfig()); out.apply(this->prints.get_edited_preset().config); out.apply(this->printers.get_edited_preset().config); auto *nozzle_diameter = dynamic_cast(out.option("nozzle_diameter")); size_t num_extruders = nozzle_diameter->values.size(); if (num_extruders <= 1) { out.apply(this->filaments.get_edited_preset().config); } else { // Retrieve filament presets and build a single config object for them. // First collect the filament configurations based on the user selection of this->filament_presets. std::vector filament_configs; for (const std::string &filament_preset_name : this->filament_presets) filament_configs.emplace_back(&this->filaments.find_preset(filament_preset_name, true)->config); while (filament_configs.size() < num_extruders) filament_configs.emplace_back(&this->filaments.first_visible().config); // Option values to set a ConfigOptionVector from. std::vector filament_opts(num_extruders, nullptr); // loop through options and apply them to the resulting config. for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) { if (key == "compatible_printers") continue; // Get a destination option. ConfigOption *opt_dst = out.option(key, false); if (opt_dst->is_scalar()) { // Get an option, do not create if it does not exist. const ConfigOption *opt_src = filament_configs.front()->option(key); if (opt_src != nullptr) opt_dst->set(opt_src); } else { // Setting a vector value from all filament_configs. for (size_t i = 0; i < filament_opts.size(); ++ i) filament_opts[i] = filament_configs[i]->option(key); static_cast(opt_dst)->set(filament_opts); } } } out.erase("compatible_printers"); static const char *keys[] = { "perimeter", "infill", "solid_infill", "support_material", "support_material_interface" }; for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++ i) { std::string key = std::string(keys[i]) + "_extruder"; auto *opt = dynamic_cast(out.option(key, false)); assert(opt != nullptr); opt->value = boost::algorithm::clamp(opt->value, 0, int(num_extruders)); } return out; } // Load an external config file containing the print, filament and printer presets. // Instead of a config file, a G-code may be loaded containing the full set of parameters. // In the future the configuration will likely be read from an AMF file as well. // If the file is loaded successfully, its print / filament / printer profiles will be activated. void PresetBundle::load_config_file(const std::string &path) { if (boost::iends_with(path, ".gcode") || boost::iends_with(path, ".g")) { DynamicPrintConfig config; config.apply(FullPrintConfig::defaults()); config.load_from_gcode(path); Preset::normalize(config); load_config_file_config(path, true, std::move(config)); return; } // 1) Try to load the config file into a boost property tree. boost::property_tree::ptree tree; try { boost::nowide::ifstream ifs(path); boost::property_tree::read_ini(ifs, tree); } catch (const std::ifstream::failure &err) { throw std::runtime_error(std::string("The config file cannot be loaded: ") + path + "\n\tReason: " + err.what()); } catch (const std::runtime_error &err) { throw std::runtime_error(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what()); } // 2) Continue based on the type of the configuration file. ConfigFileType config_file_type = guess_config_file_type(tree); switch (config_file_type) { case CONFIG_FILE_TYPE_UNKNOWN: throw std::runtime_error(std::string("Unknown configuration file type: ") + path); case CONFIG_FILE_TYPE_APP_CONFIG: throw std::runtime_error(std::string("Invalid configuration file: ") + path + ". This is an application config file."); case CONFIG_FILE_TYPE_CONFIG: { // Initialize a config from full defaults. DynamicPrintConfig config; config.apply(FullPrintConfig::defaults()); config.load(tree); Preset::normalize(config); load_config_file_config(path, true, std::move(config)); break; } case CONFIG_FILE_TYPE_CONFIG_BUNDLE: load_config_file_config_bundle(path, tree); break; } } // Load a config file from a boost property_tree. This is a private method called from load_config_file. void PresetBundle::load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config) { // The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway, // but some of the alpha versions of Slic3r did. { ConfigOption *opt_compatible = config.optptr("compatible_printers"); if (opt_compatible != nullptr) { assert(opt_compatible->type() == coStrings); if (opt_compatible->type() == coStrings) static_cast(opt_compatible)->values.clear(); } } // 1) Create a name from the file name. // Keep the suffix (.ini, .gcode, .amf, .3mf etc) to differentiate it from the normal profiles. std::string name = is_external ? boost::filesystem::path(name_or_path).filename().string() : name_or_path; // 2) If the loading succeeded, split and load the config into print / filament / printer settings. // First load the print and printer presets. for (size_t i_group = 0; i_group < 2; ++ i_group) { PresetCollection &presets = (i_group == 0) ? this->prints : this->printers; Preset &preset = presets.load_preset(is_external ? name_or_path : presets.path_from_name(name), name, config); if (is_external) preset.is_external = true; else preset.save(); } // 3) Now load the filaments. If there are multiple filament presets, split them and load them. auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter")); auto *filament_diameter = dynamic_cast(config.option("filament_diameter")); size_t num_extruders = std::min(nozzle_diameter->values.size(), filament_diameter->values.size()); if (num_extruders <= 1) { Preset &preset = this->filaments.load_preset( is_external ? name_or_path : this->filaments.path_from_name(name), name, config); if (is_external) preset.is_external = true; else preset.save(); this->filament_presets.clear(); this->filament_presets.emplace_back(name); } else { // Split the filament presets, load each of them separately. std::vector configs(num_extruders, this->filaments.default_preset().config); // loop through options and scatter them into configs. for (const t_config_option_key &key : this->filaments.default_preset().config.keys()) { const ConfigOption *other_opt = config.option(key); if (other_opt == nullptr) continue; if (other_opt->is_scalar()) { for (size_t i = 0; i < configs.size(); ++ i) configs[i].option(key, false)->set(other_opt); } else if (key != "compatible_printers") { for (size_t i = 0; i < configs.size(); ++ i) static_cast(configs[i].option(key, false))->set_at(other_opt, 0, i); } } // Load the configs into this->filaments and make them active. this->filament_presets.clear(); for (size_t i = 0; i < configs.size(); ++ i) { char suffix[64]; if (i == 0) suffix[0] = 0; else sprintf(suffix, " (%d)", i); std::string new_name = name + suffix; // Load all filament presets, but only select the first one in the preset dialog. Preset &preset = this->filaments.load_preset( is_external ? name_or_path : this->filaments.path_from_name(new_name), new_name, std::move(configs[i]), i == 0); if (is_external) preset.is_external = true; else preset.save(); this->filament_presets.emplace_back(new_name); } } this->update_compatible_with_printer(false); } // Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file. void PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree) { // 1) Load the config bundle into a temp data. PresetBundle tmp_bundle; // Load the config bundle, don't save the loaded presets to user profile directory. tmp_bundle.load_configbundle(path, 0); std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string(); // 2) Extract active configs from the config bundle, copy them and activate them in this bundle. auto load_one = [this, &path, &bundle_name](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string { Preset *preset_src = collection_src.find_preset(preset_name_src, false); Preset *preset_dst = collection_dst.find_preset(preset_name_src, false); assert(preset_src != nullptr); std::string preset_name_dst; if (preset_dst != nullptr && preset_dst->is_default) { // No need to copy a default preset, it always exists in collection_dst. if (activate) collection_dst.select_preset(0); return preset_name_src; } else if (preset_dst != nullptr && preset_src->config == preset_dst->config) { // Don't save as the config exists in the current bundle and its content is the same. return preset_name_src; } else { // Generate a new unique name. preset_name_dst = preset_name_src + bundle_name; Preset *preset_dup = nullptr; for (size_t i = 1; (preset_dup = collection_dst.find_preset(preset_name_dst, false)) != nullptr; ++ i) { if (preset_src->config == preset_dup->config) // The preset has been already copied into collection_dst. return preset_name_dst; // Try to generate another name. char buf[64]; sprintf(buf, " (%d)", i); preset_name_dst = preset_name_src + buf + bundle_name; } } assert(! preset_name_dst.empty()); // Save preset_src->config into collection_dst under preset_name_dst. // The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway, // but some of the alpha versions of Slic3r did. ConfigOption *opt_compatible = preset_src->config.optptr("compatible_printers"); if (opt_compatible != nullptr) { assert(opt_compatible->type() == coStrings); if (opt_compatible->type() == coStrings) static_cast(opt_compatible)->values.clear(); } collection_dst.load_preset(path, preset_name_dst, std::move(preset_src->config), activate).is_external = true; return preset_name_dst; }; load_one(this->prints, tmp_bundle.prints, tmp_bundle.prints .get_selected_preset().name, true); load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments.get_selected_preset().name, true); load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset().name, true); this->update_multi_material_filament_presets(); for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i) this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false); this->update_compatible_with_printer(false); } // Load a config bundle file, into presets and store the loaded presets into separate files // of the local configuration directory. size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags) { if (flags & LOAD_CFGBNDLE_RESET_USER_PROFILE) this->reset(flags & LOAD_CFGBNDLE_SAVE); // 1) Read the complete config file into a boost::property_tree. namespace pt = boost::property_tree; pt::ptree tree; boost::nowide::ifstream ifs(path); pt::read_ini(ifs, tree); // 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files. std::vector loaded_prints; std::vector loaded_filaments; std::vector loaded_printers; std::string active_print; std::vector active_filaments; std::string active_printer; size_t presets_loaded = 0; for (const auto §ion : tree) { PresetCollection *presets = nullptr; std::vector *loaded = nullptr; std::string preset_name; if (boost::starts_with(section.first, "print:")) { presets = &prints; loaded = &loaded_prints; preset_name = section.first.substr(6); } else if (boost::starts_with(section.first, "filament:")) { presets = &filaments; loaded = &loaded_filaments; preset_name = section.first.substr(9); } else if (boost::starts_with(section.first, "printer:")) { presets = &printers; loaded = &loaded_printers; preset_name = section.first.substr(8); } else if (section.first == "presets") { // Load the names of the active presets. for (auto &kvp : section.second) { if (kvp.first == "print") { active_print = kvp.second.data(); } else if (boost::starts_with(kvp.first, "filament")) { int idx = 0; if (kvp.first == "filament" || sscanf(kvp.first.c_str(), "filament_%d", &idx) == 1) { if (int(active_filaments.size()) <= idx) active_filaments.resize(idx + 1, std::string()); active_filaments[idx] = kvp.second.data(); } } else if (kvp.first == "printer") { active_printer = kvp.second.data(); } } } else if (section.first == "settings") { // Load the settings. for (auto &kvp : section.second) { if (kvp.first == "autocenter") { } } } else // Ignore an unknown section. continue; if (presets != nullptr) { // Load the print, filament or printer preset. DynamicPrintConfig config(presets->default_preset().config); for (auto &kvp : section.second) config.set_deserialize(kvp.first, kvp.second.data()); Preset::normalize(config); // Decide a full path to this .ini file. auto file_name = boost::algorithm::iends_with(preset_name, ".ini") ? preset_name : preset_name + ".ini"; auto file_path = (boost::filesystem::path(data_dir()) #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. / "presets" #else // Store the print/filament/printer presets at the same location as the upstream Slic3r. #endif / presets->name() / file_name).make_preferred(); // Load the preset into the list of presets, save it to disk. Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false); if (flags & LOAD_CFGBNDLE_SAVE) loaded.save(); ++ presets_loaded; } } // 3) Activate the presets. if (! active_print.empty()) prints.select_preset_by_name(active_print, true); if (! active_printer.empty()) printers.select_preset_by_name(active_printer, true); // Activate the first filament preset. if (! active_filaments.empty() && ! active_filaments.front().empty()) filaments.select_preset_by_name(active_filaments.front(), true); this->update_multi_material_filament_presets(); for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i) this->filament_presets[i] = filaments.find_preset(active_filaments[i], true)->name; this->update_compatible_with_printer(false); return presets_loaded; } void PresetBundle::update_multi_material_filament_presets() { // Verify and select the filament presets. auto *nozzle_diameter = static_cast(printers.get_edited_preset().config.option("nozzle_diameter")); size_t num_extruders = nozzle_diameter->values.size(); // Verify validity of the current filament presets. for (size_t i = 0; i < std::min(this->filament_presets.size(), num_extruders); ++ i) this->filament_presets[i] = this->filaments.find_preset(this->filament_presets[i], true)->name; // Append the rest of filament presets. // if (this->filament_presets.size() < num_extruders) this->filament_presets.resize(num_extruders, this->filament_presets.empty() ? this->filaments.first_visible().name : this->filament_presets.back()); } void PresetBundle::update_compatible_with_printer(bool select_other_if_incompatible) { this->prints.update_compatible_with_printer(this->printers.get_edited_preset(), select_other_if_incompatible); this->filaments.update_compatible_with_printer(this->printers.get_edited_preset(), select_other_if_incompatible); if (select_other_if_incompatible) { // Verify validity of the current filament presets. for (std::string &filament_name : this->filament_presets) { Preset *preset = this->filaments.find_preset(filament_name, false); if (preset == nullptr || ! preset->is_compatible) filament_name = this->filaments.first_compatible().name; } } } void PresetBundle::export_configbundle(const std::string &path) //, const DynamicPrintConfig &settings { boost::nowide::ofstream c; c.open(path, std::ios::out | std::ios::trunc); // Put a comment at the first line including the time stamp and Slic3r version. c << "# " << Slic3r::header_slic3r_generated() << std::endl; // Export the print, filament and printer profiles. for (size_t i_group = 0; i_group < 3; ++ i_group) { const PresetCollection &presets = (i_group == 0) ? this->prints : (i_group == 1) ? this->filaments : this->printers; for (const Preset &preset : presets()) { if (preset.is_default || preset.is_external) // Only export the common presets, not external files or the default preset. continue; c << std::endl << "[" << presets.name() << ":" << preset.name << "]" << std::endl; for (const std::string &opt_key : preset.config.keys()) c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl; } } // Export the names of the active presets. c << std::endl << "[presets]" << std::endl; c << "print = " << this->prints.get_selected_preset().name << std::endl; c << "printer = " << this->printers.get_selected_preset().name << std::endl; for (size_t i = 0; i < this->filament_presets.size(); ++ i) { char suffix[64]; if (i > 0) sprintf(suffix, "_%d", i); else suffix[0] = 0; c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl; } #if 0 // Export the following setting values from the provided setting repository. static const char *settings_keys[] = { "autocenter" }; c << "[settings]" << std::endl; for (size_t i = 0; i < sizeof(settings_keys) / sizeof(settings_keys[0]); ++ i) c << settings_keys[i] << " = " << settings.serialize(settings_keys[i]) << std::endl; #endif c.close(); } // Set the filament preset name. As the name could come from the UI selection box, // an optional "(modified)" suffix will be removed from the filament name. void PresetBundle::set_filament_preset(size_t idx, const std::string &name) { if (idx >= filament_presets.size()) filament_presets.resize(idx + 1, filaments.default_preset().name); filament_presets[idx] = Preset::remove_suffix_modified(name); } static inline int hex_digit_to_int(const char c) { return (c >= '0' && c <= '9') ? int(c - '0') : (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; } static inline bool parse_color(const std::string &scolor, unsigned char *rgb_out) { rgb_out[0] = rgb_out[1] = rgb_out[2] = 0; if (scolor.size() != 7 || scolor.front() != '#') return false; const char *c = scolor.data() + 1; for (size_t i = 0; i < 3; ++ i) { int digit1 = hex_digit_to_int(*c ++); int digit2 = hex_digit_to_int(*c ++); if (digit1 == -1 || digit2 == -1) return false; rgb_out[i] = (unsigned char)(digit1 * 16 + digit2); } return true; } void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui) { if (ui == nullptr) return; unsigned char rgb[3]; std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder); if (! parse_color(extruder_color, rgb)) // Extruder color is not defined. extruder_color.clear(); // Fill in the list from scratch. ui->Freeze(); ui->Clear(); const Preset *selected_preset = this->filaments.find_preset(this->filament_presets[idx_extruder]); // Show wide icons if the currently selected preset is not compatible with the current printer, // and draw a red flag in front of the selected preset. bool wide_icons = selected_preset != nullptr && ! selected_preset->is_compatible && m_bitmapIncompatible != nullptr; assert(selected_preset != nullptr); for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++ i) { const Preset &preset = this->filaments.preset(i); bool selected = this->filament_presets[idx_extruder] == preset.name; if (! preset.is_visible || (! preset.is_compatible && ! selected)) continue; // Assign an extruder color to the selected item if the extruder color is defined. std::string filament_rgb = preset.config.opt_string("filament_colour", 0); std::string extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb; bool single_bar = filament_rgb == extruder_rgb; std::string bitmap_key = single_bar ? filament_rgb : filament_rgb + extruder_rgb; // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left // to the filament color image. if (wide_icons) bitmap_key += preset.is_compatible ? "comp" : "notcomp"; auto it = m_mapColorToBitmap.find(bitmap_key); wxBitmap *bitmap = (it == m_mapColorToBitmap.end()) ? nullptr : it->second; if (bitmap == nullptr) { // Create the bitmap with color bars. bitmap = new wxBitmap((wide_icons ? 16 : 0) + 24, 16); #if defined(__APPLE__) || defined(_MSC_VER) bitmap->UseAlpha(); #endif wxMemoryDC memDC; memDC.SelectObject(*bitmap); memDC.SetBackground(*wxTRANSPARENT_BRUSH); memDC.Clear(); if (wide_icons && ! preset.is_compatible) // Paint the red flag. memDC.DrawBitmap(*m_bitmapIncompatible, 0, 0, true); // Paint the color bars. parse_color(filament_rgb, rgb); wxImage image(24, 16); image.InitAlpha(); unsigned char* imgdata = image.GetData(); unsigned char* imgalpha = image.GetAlpha(); for (size_t i = 0; i < image.GetWidth() * image.GetHeight(); ++ i) { *imgdata ++ = rgb[0]; *imgdata ++ = rgb[1]; *imgdata ++ = rgb[2]; *imgalpha ++ = wxALPHA_OPAQUE; } if (! single_bar) { parse_color(extruder_rgb, rgb); imgdata = image.GetData(); for (size_t r = 0; r < 16; ++ r) { imgdata = image.GetData() + r * image.GetWidth() * 3; for (size_t c = 0; c < 16; ++ c) { *imgdata ++ = rgb[0]; *imgdata ++ = rgb[1]; *imgdata ++ = rgb[2]; } } } memDC.DrawBitmap(wxBitmap(image), wide_icons ? 16 : 0, 0, true); memDC.SelectObject(wxNullBitmap); m_mapColorToBitmap[bitmap_key] = bitmap; } ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), (bitmap == 0) ? wxNullBitmap : *bitmap); if (selected) ui->SetSelection(ui->GetCount() - 1); } ui->Thaw(); } void PresetBundle::set_default_suppressed(bool default_suppressed) { prints.set_default_suppressed(default_suppressed); filaments.set_default_suppressed(default_suppressed); printers.set_default_suppressed(default_suppressed); } } // namespace Slic3r Slic3r-version_1.39.1/xs/src/slic3r/GUI/PresetBundle.hpp000066400000000000000000000133041324354444700226750ustar00rootroot00000000000000#ifndef slic3r_PresetBundle_hpp_ #define slic3r_PresetBundle_hpp_ #include "AppConfig.hpp" #include "Preset.hpp" namespace Slic3r { class PlaceholderParser; // Bundle of Print + Filament + Printer presets. class PresetBundle { public: PresetBundle(); ~PresetBundle(); // Remove all the presets but the "-- default --". // Optionally remove all the files referenced by the presets from the user profile directory. void reset(bool delete_files); void setup_directories(); // Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets. void load_presets(); // Load selections (current print, current filaments, current printer) from config.ini // This is done just once on application start up. void load_selections(const AppConfig &config); // Export selections (current print, current filaments, current printer) into config.ini void export_selections(AppConfig &config); // Export selections (current print, current filaments, current printer) into a placeholder parser. void export_selections(PlaceholderParser &pp); PresetCollection prints; PresetCollection filaments; PresetCollection printers; // Filament preset names for a multi-extruder or multi-material print. // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() std::vector filament_presets; bool has_defauls_only() const { return prints.size() <= 1 && filaments.size() <= 1 && printers.size() <= 1; } DynamicPrintConfig full_config() const; // Load user configuration and store it into the user profiles. // This method is called by the configuration wizard. void load_config(const std::string &name, DynamicPrintConfig config) { this->load_config_file_config(name, false, std::move(config)); } // Load an external config file containing the print, filament and printer presets. // Instead of a config file, a G-code may be loaded containing the full set of parameters. // In the future the configuration will likely be read from an AMF file as well. // If the file is loaded successfully, its print / filament / printer profiles will be activated. void load_config_file(const std::string &path); // Load a config bundle file, into presets and store the loaded presets into separate files // of the local configuration directory. // Load settings into the provided settings instance. // Activate the presets stored in the config bundle. // Returns the number of presets loaded successfully. enum { // Save the profiles, which have been loaded. LOAD_CFGBNDLE_SAVE = 1, // Delete all old config profiles before loading. LOAD_CFGBNDLE_RESET_USER_PROFILE = 2 }; // Load the config bundle, store it to the user profile directory by default. size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE); // Export a config bundle file containing all the presets and the names of the active presets. void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings); // Update a filament selection combo box on the platter for an idx_extruder. void update_platter_filament_ui(unsigned int idx_extruder, wxBitmapComboBox *ui); // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); // Set the filament preset name. As the name could come from the UI selection box, // an optional "(modified)" suffix will be removed from the filament name. void set_filament_preset(size_t idx, const std::string &name); // Read out the number of extruders from an active printer preset, // update size and content of filament_presets. void update_multi_material_filament_presets(); // Update the is_compatible flag of all print and filament presets depending on whether they are marked // as compatible with the currently selected printer. // Also updates the is_visible flag of each preset. // If select_other_if_incompatible is true, then the print or filament preset is switched to some compatible // preset if the current print or filament preset is not compatible. void update_compatible_with_printer(bool select_other_if_incompatible); private: // Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path. // and the external config is just referenced, not stored into user profile directory. // If it is not an external config, then the config will be stored into the user profile directory. void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config); void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); bool load_compatible_bitmaps(); // Indicator, that the preset is compatible with the selected printer. wxBitmap *m_bitmapCompatible; // Indicator, that the preset is NOT compatible with the selected printer. wxBitmap *m_bitmapIncompatible; // Caching color bitmaps for the std::map m_mapColorToBitmap; }; } // namespace Slic3r #endif /* slic3r_PresetBundle_hpp_ */ Slic3r-version_1.39.1/xs/src/slic3r/GUI/PresetHints.cpp000066400000000000000000000374201324354444700225510ustar00rootroot00000000000000//#undef NDEBUG #include #include "PresetBundle.hpp" #include "PresetHints.hpp" #include "Flow.hpp" #include #include "../../libslic3r/libslic3r.h" namespace Slic3r { std::string PresetHints::cooling_description(const Preset &preset) { std::string out; char buf[4096]; if (preset.config.opt_bool("cooling", 0)) { int slowdown_below_layer_time = preset.config.opt_int("slowdown_below_layer_time", 0); int min_fan_speed = preset.config.opt_int("min_fan_speed", 0); int max_fan_speed = preset.config.opt_int("max_fan_speed", 0); int min_print_speed = int(preset.config.opt_float("min_print_speed", 0) + 0.5); int fan_below_layer_time = preset.config.opt_int("fan_below_layer_time", 0); sprintf(buf, "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).", slowdown_below_layer_time, max_fan_speed, slowdown_below_layer_time, min_print_speed); out += buf; if (fan_below_layer_time > slowdown_below_layer_time) { sprintf(buf, "\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.", fan_below_layer_time, max_fan_speed, min_fan_speed); out += buf; } out += "\nDuring the other layers, fan "; } else { out = "Fan "; } if (preset.config.opt_bool("fan_always_on", 0)) { int disable_fan_first_layers = preset.config.opt_int("disable_fan_first_layers", 0); int min_fan_speed = preset.config.opt_int("min_fan_speed", 0); sprintf(buf, "will always run at %d%% ", min_fan_speed); out += buf; if (disable_fan_first_layers > 1) { sprintf(buf, "except for the first %d layers", disable_fan_first_layers); out += buf; } else if (disable_fan_first_layers == 1) out += "except for the first layer"; } else out += "will be turned off."; return out; } static const ConfigOptionFloatOrPercent& first_positive(const ConfigOptionFloatOrPercent *v1, const ConfigOptionFloatOrPercent &v2, const ConfigOptionFloatOrPercent &v3) { return (v1 != nullptr && v1->value > 0) ? *v1 : ((v2.value > 0) ? v2 : v3); } std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle &preset_bundle) { // Find out, to which nozzle index is the current filament profile assigned. int idx_extruder = 0; int num_extruders = (int)preset_bundle.filament_presets.size(); for (; idx_extruder < num_extruders; ++ idx_extruder) if (preset_bundle.filament_presets[idx_extruder] == preset_bundle.filaments.get_selected_preset().name) break; if (idx_extruder == num_extruders) // The current filament preset is not active for any extruder. idx_extruder = -1; const DynamicPrintConfig &print_config = preset_bundle.prints .get_edited_preset().config; const DynamicPrintConfig &filament_config = preset_bundle.filaments.get_edited_preset().config; const DynamicPrintConfig &printer_config = preset_bundle.printers .get_edited_preset().config; // Current printer values. float nozzle_diameter = (float)printer_config.opt_float("nozzle_diameter", idx_extruder); // Print config values double layer_height = print_config.opt_float("layer_height"); double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); double support_material_speed = print_config.opt_float("support_material_speed"); double support_material_interface_speed = print_config.get_abs_value("support_material_interface_speed", support_material_speed); double bridge_speed = print_config.opt_float("bridge_speed"); double bridge_flow_ratio = print_config.opt_float("bridge_flow_ratio"); double perimeter_speed = print_config.opt_float("perimeter_speed"); double external_perimeter_speed = print_config.get_abs_value("external_perimeter_speed", perimeter_speed); double gap_fill_speed = print_config.opt_float("gap_fill_speed"); double infill_speed = print_config.opt_float("infill_speed"); double small_perimeter_speed = print_config.get_abs_value("small_perimeter_speed", perimeter_speed); double solid_infill_speed = print_config.get_abs_value("solid_infill_speed", infill_speed); double top_solid_infill_speed = print_config.get_abs_value("top_solid_infill_speed", solid_infill_speed); // Maximum print speed when auto-speed is enabled by setting any of the above speed values to zero. double max_print_speed = print_config.opt_float("max_print_speed"); // Maximum volumetric speed allowed for the print profile. double max_volumetric_speed = print_config.opt_float("max_volumetric_speed"); const auto &extrusion_width = *print_config.option("extrusion_width"); const auto &external_perimeter_extrusion_width = *print_config.option("external_perimeter_extrusion_width"); const auto &first_layer_extrusion_width = *print_config.option("first_layer_extrusion_width"); const auto &infill_extrusion_width = *print_config.option("infill_extrusion_width"); const auto &perimeter_extrusion_width = *print_config.option("perimeter_extrusion_width"); const auto &solid_infill_extrusion_width = *print_config.option("solid_infill_extrusion_width"); const auto &support_material_extrusion_width = *print_config.option("support_material_extrusion_width"); const auto &top_infill_extrusion_width = *print_config.option("top_infill_extrusion_width"); const auto &first_layer_speed = *print_config.option("first_layer_speed"); // Index of an extruder assigned to a feature. If set to 0, an active extruder will be used for a multi-material print. // If different from idx_extruder, it will not be taken into account for this hint. auto feature_extruder_active = [idx_extruder, num_extruders](int i) { return i <= 0 || i > num_extruders || idx_extruder == -1 || idx_extruder == i - 1; }; bool perimeter_extruder_active = feature_extruder_active(print_config.opt_int("perimeter_extruder")); bool infill_extruder_active = feature_extruder_active(print_config.opt_int("infill_extruder")); bool solid_infill_extruder_active = feature_extruder_active(print_config.opt_int("solid_infill_extruder")); bool support_material_extruder_active = feature_extruder_active(print_config.opt_int("support_material_extruder")); bool support_material_interface_extruder_active = feature_extruder_active(print_config.opt_int("support_material_interface_extruder")); // Current filament values double filament_diameter = filament_config.opt_float("filament_diameter", 0); double filament_crossection = M_PI * 0.25 * filament_diameter * filament_diameter; double extrusion_multiplier = filament_config.opt_float("extrusion_multiplier", 0); // The following value will be annotated by this hint, so it does not take part in the calculation. // double filament_max_volumetric_speed = filament_config.opt_float("filament_max_volumetric_speed", 0); std::string out; for (size_t idx_type = (first_layer_extrusion_width.value == 0) ? 1 : 0; idx_type < 3; ++ idx_type) { // First test the maximum volumetric extrusion speed for non-bridging extrusions. bool first_layer = idx_type == 0; bool bridging = idx_type == 2; const ConfigOptionFloatOrPercent *first_layer_extrusion_width_ptr = (first_layer && first_layer_extrusion_width.value > 0) ? &first_layer_extrusion_width : nullptr; const float lh = float(first_layer ? first_layer_height : layer_height); const float bfr = bridging ? bridge_flow_ratio : 0.f; double max_flow = 0.; std::string max_flow_extrusion_type; auto limit_by_first_layer_speed = [&first_layer_speed, first_layer](double speed_normal, double speed_max) { if (first_layer && first_layer_speed.value > 0) // Apply the first layer limit. speed_normal = first_layer_speed.get_abs_value(speed_normal); return (speed_normal > 0.) ? speed_normal : speed_max; }; if (perimeter_extruder_active) { double external_perimeter_rate = Flow::new_from_config_width(frExternalPerimeter, first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width), nozzle_diameter, lh, bfr).mm3_per_mm() * (bridging ? bridge_speed : limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed)); if (max_flow < external_perimeter_rate) { max_flow = external_perimeter_rate; max_flow_extrusion_type = "external perimeters"; } double perimeter_rate = Flow::new_from_config_width(frPerimeter, first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width), nozzle_diameter, lh, bfr).mm3_per_mm() * (bridging ? bridge_speed : limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed)); if (max_flow < perimeter_rate) { max_flow = perimeter_rate; max_flow_extrusion_type = "perimeters"; } } if (! bridging && infill_extruder_active) { double infill_rate = Flow::new_from_config_width(frInfill, first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width), nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(infill_speed, max_print_speed); if (max_flow < infill_rate) { max_flow = infill_rate; max_flow_extrusion_type = "infill"; } } if (solid_infill_extruder_active) { double solid_infill_rate = Flow::new_from_config_width(frInfill, first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width), nozzle_diameter, lh, 0).mm3_per_mm() * (bridging ? bridge_speed : limit_by_first_layer_speed(solid_infill_speed, max_print_speed)); if (max_flow < solid_infill_rate) { max_flow = solid_infill_rate; max_flow_extrusion_type = "solid infill"; } if (! bridging) { double top_solid_infill_rate = Flow::new_from_config_width(frInfill, first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width), nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(top_solid_infill_speed, max_print_speed); if (max_flow < top_solid_infill_rate) { max_flow = top_solid_infill_rate; max_flow_extrusion_type = "top solid infill"; } } } if (support_material_extruder_active) { double support_material_rate = Flow::new_from_config_width(frSupportMaterial, first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width), nozzle_diameter, lh, bfr).mm3_per_mm() * (bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed)); if (max_flow < support_material_rate) { max_flow = support_material_rate; max_flow_extrusion_type = "support"; } } if (support_material_interface_extruder_active) { double support_material_interface_rate = Flow::new_from_config_width(frSupportMaterialInterface, first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width), nozzle_diameter, lh, bfr).mm3_per_mm() * (bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed)); if (max_flow < support_material_interface_rate) { max_flow = support_material_interface_rate; max_flow_extrusion_type = "support interface"; } } //FIXME handle gap_fill_speed if (! out.empty()) out += "\n"; out += (first_layer ? "First layer volumetric" : (bridging ? "Bridging volumetric" : "Volumetric")); out += " flow rate is maximized "; bool limited_by_max_volumetric_speed = max_volumetric_speed > 0 && max_volumetric_speed < max_flow; out += (limited_by_max_volumetric_speed ? "by the print profile maximum" : ("when printing " + max_flow_extrusion_type)) + " with a volumetric rate "; if (limited_by_max_volumetric_speed) max_flow = max_volumetric_speed; char buf[2048]; sprintf(buf, "%3.2f mm³/s", max_flow); out += buf; sprintf(buf, " at filament speed %3.2f mm/s.", max_flow / filament_crossection); out += buf; } return out; } std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &preset_bundle) { const DynamicPrintConfig &print_config = preset_bundle.prints .get_edited_preset().config; const DynamicPrintConfig &printer_config = preset_bundle.printers .get_edited_preset().config; float layer_height = float(print_config.opt_float("layer_height")); int num_perimeters = print_config.opt_int("perimeters"); bool thin_walls = print_config.opt_bool("thin_walls"); float nozzle_diameter = float(printer_config.opt_float("nozzle_diameter", 0)); if (layer_height <= 0.f) return "Recommended object thin wall thickness: Not available due to invalid layer height."; Flow external_perimeter_flow = Flow::new_from_config_width( frExternalPerimeter, *print_config.opt("external_perimeter_extrusion_width"), nozzle_diameter, layer_height, false); Flow perimeter_flow = Flow::new_from_config_width( frPerimeter, *print_config.opt("perimeter_extrusion_width"), nozzle_diameter, layer_height, false); std::string out; if (num_perimeters > 0) { int num_lines = std::min(num_perimeters * 2, 10); char buf[256]; sprintf(buf, "Recommended object thin wall thickness for layer height %.2f and ", layer_height); out += buf; // Start with the width of two closely spaced double width = external_perimeter_flow.width + external_perimeter_flow.spacing(); for (int i = 2; i <= num_lines; thin_walls ? ++ i : i += 2) { if (i > 2) out += ", "; sprintf(buf, "%d lines: %.2lf mm", i, width); out += buf; width += perimeter_flow.spacing() * (thin_walls ? 1.f : 2.f); } } return out; } }; // namespace Slic3r Slic3r-version_1.39.1/xs/src/slic3r/GUI/PresetHints.hpp000066400000000000000000000021251324354444700225500ustar00rootroot00000000000000#ifndef slic3r_PresetHints_hpp_ #define slic3r_PresetHints_hpp_ #include #include "PresetBundle.hpp" namespace Slic3r { // GUI utility functions to produce hint messages from the current profile. class PresetHints { public: // Produce a textual description of the cooling logic of a currently active filament. static std::string cooling_description(const Preset &preset); // Produce a textual description of the maximum flow achived for the current configuration // (the current printer, filament and print settigns). // This description will be useful for getting a gut feeling for the maximum volumetric // print speed achievable with the extruder. static std::string maximum_volumetric_flow_description(const PresetBundle &preset_bundle); // Produce a textual description of a recommended thin wall thickness // from the provided number of perimeters and the external / internal perimeter width. static std::string recommended_thin_wall_thickness(const PresetBundle &preset_bundle); }; } // namespace Slic3r #endif /* slic3r_PresetHints_hpp_ */ Slic3r-version_1.39.1/xs/src/slic3r/GUI/wxPerlIface.cpp000066400000000000000000000052441324354444700225110ustar00rootroot00000000000000// Derived from the following: ///////////////////////////////////////////////////////////////////////////// // Name: cpp/helpers.cpp // Purpose: implementation for helpers.h // Author: Mattia Barbon // Modified by: // Created: 29/10/2000 // RCS-ID: $Id: helpers.cpp 3397 2012-09-30 02:26:07Z mdootson $ // Copyright: (c) 2000-2011 Mattia Barbon // Licence: This program is free software; you can redistribute it and/or // modify it under the same terms as Perl itself ///////////////////////////////////////////////////////////////////////////// #ifdef __cplusplus extern "C" { #endif #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" #undef do_open #undef do_close #ifdef __cplusplus } #endif //#include // ---------------------------------------------------------------------------- // Utility functions for working with MAGIC // ---------------------------------------------------------------------------- struct my_magic { my_magic() : object( NULL ), deleteable( true ) { } void* object; bool deleteable; }; //STATIC MGVTBL my_vtbl = { 0, 0, 0, 0, 0, 0, 0, 0 }; my_magic* wxPli_get_magic( pTHX_ SV* rv ) { // check for reference if( !SvROK( rv ) ) return NULL; SV* ref = SvRV( rv ); // if it isn't a SvPVMG, then it can't have MAGIC // so it is deleteable if( !ref || SvTYPE( ref ) < SVt_PVMG ) return NULL; // search for '~' / PERL_MAGIC_ext magic, and check the value // MAGIC* magic = mg_findext( ref, PERL_MAGIC_ext, &my_vtbl ); MAGIC* magic = mg_find( ref, '~' ); if( !magic ) return NULL; return (my_magic*)magic->mg_ptr; } // gets 'this' pointer from a blessed scalar/hash reference void* wxPli_sv_2_object( pTHX_ SV* scalar, const char* classname ) { // is it correct to use undef as 'NULL'? if( !SvOK( scalar ) ) { return NULL; } if( !SvROK( scalar ) ) croak( "variable is not an object: it must have type %s", classname ); if( !classname || sv_derived_from( scalar, (char*) classname ) ) { SV* ref = SvRV( scalar ); my_magic* mg = wxPli_get_magic( aTHX_ scalar ); // rationale: if this is an hash-ish object, it always // has both mg and mg->object; if however this is a // scalar-ish object that has been marked/unmarked deletable // it has mg, but not mg->object if( !mg || !mg->object ) return INT2PTR( void*, SvOK( ref ) ? SvIV( ref ) : 0 ); return mg->object; } else { croak( "variable is not of type %s", classname ); return NULL; // dummy, for compiler } } Slic3r-version_1.39.1/xs/src/xsinit.h000066400000000000000000000122001324354444700174260ustar00rootroot00000000000000#ifndef _xsinit_h_ #define _xsinit_h_ #ifdef _MSC_VER // Disable some obnoxious warnings given by Visual Studio with the default warning level 4. #pragma warning(disable: 4100 4127 4189 4244 4267 4700 4702 4800) #endif // undef some macros set by Perl which cause compilation errors on Win32 #undef read #undef seekdir #undef bind #undef send #undef connect #undef wait #undef accept #undef close #undef open #undef write #undef socket #undef listen #undef shutdown #undef ioctl #undef getpeername #undef rect #undef setsockopt #undef getsockopt #undef getsockname #undef gethostname #undef select #undef socketpair #undef recvfrom #undef sendto #undef pause // these need to be included early for Win32 (listing it in Build.PL is not enough) #include #include #include #include #ifdef SLIC3RXS extern "C" { #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" #undef do_open #undef do_close #undef bind #undef seed #undef push #undef pop #ifdef _MSC_VER // Undef some of the macros set by Perl , which cause compilation errors on Win32 #undef connect #undef link #undef unlink #undef seek #undef send #undef write #endif /* _MSC_VER */ } #endif #include #include #include #include #include #include #include #include namespace Slic3r { template struct ClassTraits { static const char* name; static const char* name_ref; }; // use this for typedefs for which the forward prototype // in REGISTER_CLASS won't work #define __REGISTER_CLASS(cname, perlname) \ template <>const char* ClassTraits::name = "Slic3r::" perlname; \ template <>const char* ClassTraits::name_ref = "Slic3r::" perlname "::Ref"; #define REGISTER_CLASS(cname,perlname) \ class cname; \ __REGISTER_CLASS(cname, perlname); template const char* perl_class_name(const T*) { return ClassTraits::name; } template const char* perl_class_name_ref(const T*) { return ClassTraits::name_ref; } template SV* perl_to_SV_ref(T &t) { SV* sv = newSV(0); sv_setref_pv( sv, perl_class_name_ref(&t), &t ); return sv; } template SV* perl_to_SV_clone_ref(const T &t) { SV* sv = newSV(0); sv_setref_pv( sv, perl_class_name(&t), new T(t) ); return sv; } template class Ref { T* val; public: Ref() : val(NULL) {} Ref(T* t) : val(t) {} Ref(const T* t) : val(const_cast(t)) {} operator T*() const { return val; } static const char* CLASS() { return ClassTraits::name_ref; } }; template class Clone { T* val; public: Clone() : val(NULL) {} Clone(T* t) : val(new T(*t)) {} Clone(const T& t) : val(new T(t)) {} operator T*() const { return val; } static const char* CLASS() { return ClassTraits::name; } }; SV* ConfigBase__as_hash(ConfigBase* THIS); SV* ConfigOption_to_SV(const ConfigOption &opt, const ConfigOptionDef &def); SV* ConfigBase__get(ConfigBase* THIS, const t_config_option_key &opt_key); SV* ConfigBase__get_at(ConfigBase* THIS, const t_config_option_key &opt_key, size_t i); bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value); bool ConfigBase__set_deserialize(ConfigBase* THIS, const t_config_option_key &opt_key, SV* str); void ConfigBase__set_ifndef(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value, bool deserialize = false); bool StaticConfig__set(StaticConfig* THIS, const t_config_option_key &opt_key, SV* value); SV* to_AV(ExPolygon* expolygon); SV* to_SV_pureperl(const ExPolygon* expolygon); void from_SV(SV* expoly_sv, ExPolygon* expolygon); void from_SV_check(SV* expoly_sv, ExPolygon* expolygon); void from_SV(SV* line_sv, Line* THIS); void from_SV_check(SV* line_sv, Line* THIS); SV* to_AV(Line* THIS); SV* to_SV_pureperl(const Line* THIS); void from_SV(SV* poly_sv, MultiPoint* THIS); void from_SV_check(SV* poly_sv, MultiPoint* THIS); SV* to_AV(MultiPoint* THIS); SV* to_SV_pureperl(const MultiPoint* THIS); void from_SV_check(SV* poly_sv, Polygon* THIS); void from_SV_check(SV* poly_sv, Polyline* THIS); SV* to_SV_pureperl(const Point* THIS); void from_SV(SV* point_sv, Point* point); void from_SV_check(SV* point_sv, Point* point); SV* to_SV_pureperl(const Pointf* point); bool from_SV(SV* point_sv, Pointf* point); bool from_SV_check(SV* point_sv, Pointf* point); void from_SV_check(SV* surface_sv, Surface* THIS); SV* to_SV(TriangleMesh* THIS); } #ifdef SLIC3R_HAS_BROKEN_CROAK #undef croak #ifdef _MSC_VER #define croak(...) confess_at(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) #else #define croak(...) confess_at(__FILE__, __LINE__, __func__, __VA_ARGS__) #endif #endif // Defined in wxPerlIface.cpp // Return a pointer to the associated wxWidgets object instance given by classname. extern void* wxPli_sv_2_object( pTHX_ SV* scalar, const char* classname ); using namespace Slic3r; #endif Slic3r-version_1.39.1/xs/t/000077500000000000000000000000001324354444700154205ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/t/01_trianglemesh.t000066400000000000000000000117461324354444700206000ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 49; is Slic3r::TriangleMesh::hello_world(), 'Hello world!', 'hello world'; my $cube = { vertices => [ [20,20,0], [20,0,0], [0,0,0], [0,20,0], [20,20,20], [0,20,20], [0,0,20], [20,0,20] ], 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] ], }; { my $m = Slic3r::TriangleMesh->new; $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); $m->repair; my ($vertices, $facets) = ($m->vertices, $m->facets); is_deeply $vertices, $cube->{vertices}, 'vertices arrayref roundtrip'; is_deeply $facets, $cube->{facets}, 'facets arrayref roundtrip'; is scalar(@{$m->normals}), scalar(@$facets), 'normals returns the right number of items'; { my $m2 = $m->clone; is_deeply $m2->vertices, $cube->{vertices}, 'cloned vertices arrayref roundtrip'; is_deeply $m2->facets, $cube->{facets}, 'cloned facets arrayref roundtrip'; $m2->scale(3); # check that it does not affect $m } { my $stats = $m->stats; is $stats->{number_of_facets}, scalar(@{ $cube->{facets} }), 'stats.number_of_facets'; ok abs($stats->{volume} - 20*20*20) < 1E-2, 'stats.volume'; } $m->scale(2); ok abs($m->stats->{volume} - 40*40*40) < 1E-2, 'scale'; $m->scale_xyz(Slic3r::Pointf3->new(2,1,1)); ok abs($m->stats->{volume} - 2*40*40*40) < 1E-2, 'scale_xyz'; $m->translate(5,10,0); is_deeply $m->vertices->[0], [85,50,0], 'translate'; $m->align_to_origin; is_deeply $m->vertices->[2], [0,0,0], 'align_to_origin'; is_deeply $m->size, [80,40,40], 'size'; $m->scale_xyz(Slic3r::Pointf3->new(0.5,1,1)); $m->rotate(45, Slic3r::Point->new(20,20)); ok abs($m->size->[0] - sqrt(2)*40) < 1E-4, 'rotate'; { my $meshes = $m->split; is scalar(@$meshes), 1, 'split'; isa_ok $meshes->[0], 'Slic3r::TriangleMesh', 'split'; is_deeply $m->bb3, $meshes->[0]->bb3, 'split populates stats'; } my $m2 = Slic3r::TriangleMesh->new; $m2->ReadFromPerl($cube->{vertices}, $cube->{facets}); $m2->repair; $m->merge($m2); $m->repair; is $m->stats->{number_of_facets}, 2 * $m2->stats->{number_of_facets}, 'merge'; { my $meshes = $m->split; is scalar(@$meshes), 2, 'split'; } } { my $m = Slic3r::TriangleMesh->new; $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); $m->repair; my @z = (0,2,4,8,6,8,10,12,14,16,18,20); my $result = $m->slice(\@z); my $SCALING_FACTOR = 0.000001; for my $i (0..$#z) { is scalar(@{$result->[$i]}), 1, "number of returned polygons per layer (z = " . $z[$i] . ")"; is $result->[$i][0]->area, 20*20/($SCALING_FACTOR**2), 'size of returned polygon'; } } { my $m = Slic3r::TriangleMesh->new; $m->ReadFromPerl( [ [0,0,0],[0,0,20],[0,5,0],[0,5,20],[50,0,0],[50,0,20],[15,5,0],[35,5,0],[15,20,0],[50,5,0],[35,20,0],[15,5,10],[50,5,20],[35,5,10],[35,20,10],[15,20,10] ], [ [0,1,2],[2,1,3],[1,0,4],[5,1,4],[0,2,4],[4,2,6],[7,6,8],[4,6,7],[9,4,7],[7,8,10],[2,3,6],[11,3,12],[7,12,9],[13,12,7],[6,3,11],[11,12,13],[3,1,5],[12,3,5],[5,4,9],[12,5,9],[13,7,10],[14,13,10],[8,15,10],[10,15,14],[6,11,8],[8,11,15],[15,11,13],[14,15,13] ], ); $m->repair; { # at Z = 10 we have a top horizontal surface my $slices = $m->slice([ 5, 10 ]); is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a top tangent plane includes its area'; } $m->mirror_z; { # this second test also checks that performing a second slice on a mesh after # a transformation works properly (shared_vertices is correctly invalidated); # at Z = -10 we have a bottom horizontal surface my $slices = $m->slice([ -5, -10 ]); is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a bottom tangent plane includes its area'; } } { my $m = Slic3r::TriangleMesh->new; $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); $m->repair; { my $upper = Slic3r::TriangleMesh->new; my $lower = Slic3r::TriangleMesh->new; $m->cut(0, $upper, $lower); $upper->repair; $lower->repair; is $upper->facets_count, 12, 'upper mesh has all facets except those belonging to the slicing plane'; is $lower->facets_count, 0, 'lower mesh has no facets'; } { my $upper = Slic3r::TriangleMesh->new; my $lower = Slic3r::TriangleMesh->new; $m->cut(10, $upper, $lower); #$upper->repair; $lower->repair; # we expect: # 2 facets on external horizontal surfaces # 3 facets on each side = 12 facets # 6 facets on the triangulated side (8 vertices) is $upper->facets_count, 2+12+6, 'upper mesh has the expected number of facets'; is $lower->facets_count, 2+12+6, 'lower mesh has the expected number of facets'; } } __END__ Slic3r-version_1.39.1/xs/t/03_point.t000066400000000000000000000054651324354444700172520ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 24; my $point = Slic3r::Point->new(10, 15); is_deeply [ @$point ], [10, 15], 'point roundtrip'; my $point2 = $point->clone; $point2->scale(2); is_deeply [ @$point2 ], [20, 30], 'scale'; $point2->translate(10, -15); is_deeply [ @$point2 ], [30, 15], 'translate'; ok $point->coincides_with($point->clone), 'coincides_with'; ok !$point->coincides_with($point2), 'coincides_with'; { my $point3 = Slic3r::Point->new(4300000, -9880845); is $point->[0], $point->x, 'x accessor'; is $point->[1], $point->y, 'y accessor'; #,, } { my $nearest = $point->nearest_point([ $point2, Slic3r::Point->new(100, 200) ]); ok $nearest->coincides_with($point2), 'nearest_point'; } { my $line = Slic3r::Line->new([0,0], [100,0]); is +Slic3r::Point->new(0,0) ->distance_to_line($line), 0, 'distance_to_line()'; is +Slic3r::Point->new(100,0)->distance_to_line($line), 0, 'distance_to_line()'; is +Slic3r::Point->new(50,0) ->distance_to_line($line), 0, 'distance_to_line()'; is +Slic3r::Point->new(150,0)->distance_to_line($line), 50, 'distance_to_line()'; is +Slic3r::Point->new(0,50) ->distance_to_line($line), 50, 'distance_to_line()'; is +Slic3r::Point->new(50,50)->distance_to_line($line), 50, 'distance_to_line()'; is +Slic3r::Point->new(50,50) ->perp_distance_to_line($line), 50, 'perp_distance_to_line()'; is +Slic3r::Point->new(150,50)->perp_distance_to_line($line), 50, 'perp_distance_to_line()'; } { my $line = Slic3r::Line->new([50,50], [125,-25]); is +Slic3r::Point->new(100,0)->distance_to_line($line), 0, 'distance_to_line()'; } { my $line = Slic3r::Line->new( [18335846,18335845], [18335846,1664160], ); $point = Slic3r::Point->new(1664161,18335848); is $point->perp_distance_to_line($line), 16671685, 'perp_distance_to_line() does not overflow'; } { my $p0 = Slic3r::Point->new(76975850,89989996); my $p1 = Slic3r::Point->new(76989990,109989991); my $p2 = Slic3r::Point->new(76989987,89989994); ok $p0->ccw($p1, $p2) < 0, 'ccw() does not overflow'; } { my $point = Slic3r::Point->new(15,15); my $line = Slic3r::Line->new([10,10], [20,10]); is_deeply $point->projection_onto_line($line)->pp, [15,10], 'project_onto_line'; $point = Slic3r::Point->new(0, 15); is_deeply $point->projection_onto_line($line)->pp, [10,10], 'project_onto_line'; $point = Slic3r::Point->new(25, 15); is_deeply $point->projection_onto_line($line)->pp, [20,10], 'project_onto_line'; $point = Slic3r::Point->new(10,10); is_deeply $point->projection_onto_line($line)->pp, [10,10], 'project_onto_line'; $point = Slic3r::Point->new(12, 10); is_deeply $point->projection_onto_line($line)->pp, [12,10], 'project_onto_line'; } __END__ Slic3r-version_1.39.1/xs/t/04_expolygon.t000066400000000000000000000111041324354444700201310ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use List::Util qw(first sum); use Slic3r::XS; use Test::More tests => 33; use constant PI => 4 * atan2(1, 1); my $square = [ # ccw [100, 100], [200, 100], [200, 200], [100, 200], ]; my $hole_in_square = [ # cw [140, 140], [140, 160], [160, 160], [160, 140], ]; my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); ok $expolygon->is_valid, 'is_valid'; is ref($expolygon->pp), 'ARRAY', 'expolygon pp is unblessed'; is_deeply $expolygon->pp, [$square, $hole_in_square], 'expolygon roundtrip'; is ref($expolygon->arrayref), 'ARRAY', 'expolygon arrayref is unblessed'; isa_ok $expolygon->[0], 'Slic3r::Polygon::Ref', 'expolygon polygon is blessed'; isa_ok $expolygon->contour, 'Slic3r::Polygon::Ref', 'expolygon contour is blessed'; isa_ok $expolygon->holes->[0], 'Slic3r::Polygon::Ref', 'expolygon hole is blessed'; isa_ok $expolygon->[0][0], 'Slic3r::Point::Ref', 'expolygon point is blessed'; { my $expolygon2 = $expolygon->clone; my $polygon = $expolygon2->[0]; $polygon->scale(2); is $expolygon2->[0][0][0], $polygon->[0][0], 'polygons are returned by reference'; } is_deeply $expolygon->clone->pp, [$square, $hole_in_square], 'clone'; is $expolygon->area, 100*100-20*20, 'area'; { my $expolygon2 = $expolygon->clone; $expolygon2->scale(2.5); is_deeply $expolygon2->pp, [ [map [ 2.5*$_->[0], 2.5*$_->[1] ], @$square], [map [ 2.5*$_->[0], 2.5*$_->[1] ], @$hole_in_square] ], 'scale'; } { my $expolygon2 = $expolygon->clone; $expolygon2->translate(10, -5); is_deeply $expolygon2->pp, [ [map [ $_->[0]+10, $_->[1]-5 ], @$square], [map [ $_->[0]+10, $_->[1]-5 ], @$hole_in_square] ], 'translate'; } { my $expolygon2 = $expolygon->clone; $expolygon2->rotate(PI/2, Slic3r::Point->new(150,150)); is_deeply $expolygon2->pp, [ [ @$square[1,2,3,0] ], [ @$hole_in_square[3,0,1,2] ] ], 'rotate around Point'; } { my $expolygon2 = $expolygon->clone; $expolygon2->rotate(PI/2, [150,150]); is_deeply $expolygon2->pp, [ [ @$square[1,2,3,0] ], [ @$hole_in_square[3,0,1,2] ] ], 'rotate around pure-Perl Point'; } { my $expolygon2 = $expolygon->clone; $expolygon2->scale(2); my $collection = Slic3r::ExPolygon::Collection->new($expolygon->pp, $expolygon2->pp); is_deeply $collection->pp, [ $expolygon->pp, $expolygon2->pp ], 'expolygon collection (pure Perl) roundtrip'; my $collection2 = Slic3r::ExPolygon::Collection->new($expolygon, $expolygon2); is_deeply $collection->pp, $collection2->pp, 'expolygon collection (XS) roundtrip'; $collection->clear; is scalar(@$collection), 0, 'clear collection'; $collection->append($expolygon); is scalar(@$collection), 1, 'append to collection'; my $exp = $collection->[0]; $exp->scale(3); is $collection->[0][0][0][0], $exp->[0][0][0], 'collection items are returned by reference'; is_deeply $collection->[0]->clone->pp, $collection->[0]->pp, 'clone collection item'; } { my $expolygon = Slic3r::ExPolygon->new($square); my $polygons = $expolygon->get_trapezoids2(PI/2); is scalar(@$polygons), 1, 'correct number of trapezoids returned'; is scalar(@{$polygons->[0]}), 4, 'trapezoid has 4 points'; is $polygons->[0]->area, $expolygon->area, 'trapezoid has correct area'; } { my $polygons = $expolygon->get_trapezoids2(PI/2); is scalar(@$polygons), 4, 'correct number of trapezoids returned'; # trapezoid polygons might have more than 4 points in case of collinear segments $polygons = [ map @{$_->simplify(1)}, @$polygons ]; ok !defined(first { @$_ != 4 } @$polygons), 'all trapezoids have 4 points'; is scalar(grep { $_->area == 40*100 } @$polygons), 2, 'trapezoids have expected area'; is scalar(grep { $_->area == 20*40 } @$polygons), 2, 'trapezoids have expected area'; } { my $expolygon = Slic3r::ExPolygon->new([ [0,100],[100,0],[200,0],[300,100],[200,200],[100,200] ]); my $polygons = $expolygon->get_trapezoids2(PI/2); is scalar(@$polygons), 3, 'correct number of trapezoids returned'; is scalar(grep { $_->area == 100*200/2 } @$polygons), 2, 'trapezoids have expected area'; is scalar(grep { $_->area == 100*200 } @$polygons), 1, 'trapezoids have expected area'; } { my $triangles = $expolygon->triangulate_pp; is scalar(@$triangles), 8, 'expected number of triangles'; is sum(map $_->area, @$triangles), $expolygon->area, 'sum of triangles area equals original expolygon area'; } __END__ Slic3r-version_1.39.1/xs/t/05_surface.t000066400000000000000000000046671324354444700175560ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 15; my $square = [ # ccw [100, 100], [200, 100], [200, 200], [100, 200], ]; my $hole_in_square = [ # cw [140, 140], [140, 160], [160, 160], [160, 140], ]; my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); my $surface = Slic3r::Surface->new( expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_INTERNAL, ); $surface = $surface->clone; isa_ok $surface->expolygon, 'Slic3r::ExPolygon::Ref', 'expolygon'; is_deeply [ @{$surface->expolygon->pp} ], [$square, $hole_in_square], 'expolygon roundtrip'; is scalar(@{$surface->polygons}), 2, 'polygons roundtrip'; is $surface->surface_type, Slic3r::Surface::S_TYPE_INTERNAL, 'surface_type'; $surface->surface_type(Slic3r::Surface::S_TYPE_BOTTOM); is $surface->surface_type, Slic3r::Surface::S_TYPE_BOTTOM, 'modify surface_type'; $surface->bridge_angle(30); is $surface->bridge_angle, 30, 'bridge_angle'; $surface->extra_perimeters(2); is $surface->extra_perimeters, 2, 'extra_perimeters'; { my $surface2 = $surface->clone; $surface2->expolygon->scale(2); isnt $surface2->expolygon->area, $expolygon->area, 'expolygon is returned by reference'; } { my $collection = Slic3r::Surface::Collection->new; $collection->append($_) for $surface, $surface->clone; is scalar(@$collection), 2, 'collection has the right number of items'; is_deeply $collection->[0]->expolygon->pp, [$square, $hole_in_square], 'collection returns a correct surface expolygon'; $collection->clear; is scalar(@$collection), 0, 'clear collection'; $collection->append($surface); is scalar(@$collection), 1, 'append to collection'; my $item = $collection->[0]; isa_ok $item, 'Slic3r::Surface::Ref'; $item->surface_type(Slic3r::Surface::S_TYPE_INTERNAL); is $item->surface_type, $collection->[0]->surface_type, 'collection returns items by reference'; } { my $collection = Slic3r::Surface::Collection->new; $collection->append($_) for Slic3r::Surface->new(expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_BOTTOM), Slic3r::Surface->new(expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_BOTTOM), Slic3r::Surface->new(expolygon => $expolygon, surface_type => Slic3r::Surface::S_TYPE_TOP); is scalar(@{$collection->group}), 2, 'group() returns correct number of groups'; } __END__ Slic3r-version_1.39.1/xs/t/06_polygon.t000066400000000000000000000055611324354444700176100ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use List::Util qw(first); use Slic3r::XS; use Test::More tests => 21; use constant PI => 4 * atan2(1, 1); my $square = [ # ccw [100, 100], [200, 100], [200, 200], [100, 200], ]; my $polygon = Slic3r::Polygon->new(@$square); my $cw_polygon = $polygon->clone; $cw_polygon->reverse; ok $polygon->is_valid, 'is_valid'; is_deeply $polygon->pp, $square, 'polygon roundtrip'; is ref($polygon->arrayref), 'ARRAY', 'polygon arrayref is unblessed'; isa_ok $polygon->[0], 'Slic3r::Point::Ref', 'polygon point is blessed'; my $lines = $polygon->lines; is_deeply [ map $_->pp, @$lines ], [ [ [100, 100], [200, 100] ], [ [200, 100], [200, 200] ], [ [200, 200], [100, 200] ], [ [100, 200], [100, 100] ], ], 'polygon lines'; is_deeply $polygon->split_at_first_point->pp, [ @$square[0,1,2,3,0] ], 'split_at_first_point'; is_deeply $polygon->split_at_index(2)->pp, [ @$square[2,3,0,1,2] ], 'split_at_index'; is_deeply $polygon->split_at_vertex(Slic3r::Point->new(@{$square->[2]}))->pp, [ @$square[2,3,0,1,2] ], 'split_at'; is $polygon->area, 100*100, 'area'; ok $polygon->is_counter_clockwise, 'is_counter_clockwise'; ok !$cw_polygon->is_counter_clockwise, 'is_counter_clockwise'; { my $clone = $polygon->clone; $clone->reverse; ok !$clone->is_counter_clockwise, 'is_counter_clockwise'; $clone->make_counter_clockwise; ok $clone->is_counter_clockwise, 'make_counter_clockwise'; $clone->make_counter_clockwise; ok $clone->is_counter_clockwise, 'make_counter_clockwise'; } ok ref($polygon->first_point) eq 'Slic3r::Point', 'first_point'; ok $polygon->contains_point(Slic3r::Point->new(150,150)), 'ccw contains_point'; ok $cw_polygon->contains_point(Slic3r::Point->new(150,150)), 'cw contains_point'; { my @points = (Slic3r::Point->new(100,0)); foreach my $i (1..5) { my $point = $points[0]->clone; $point->rotate(PI/3*$i, [0,0]); push @points, $point; } my $hexagon = Slic3r::Polygon->new(@points); my $triangles = $hexagon->triangulate_convex; is scalar(@$triangles), 4, 'right number of triangles'; ok !(defined first { $_->is_clockwise } @$triangles), 'all triangles are ccw'; } { is_deeply $polygon->centroid->pp, [150,150], 'centroid'; } { my $polygon = Slic3r::Polygon->new( [50000000, 100000000], [300000000, 102000000], [50000000, 104000000], ); my $line = Slic3r::Line->new([175992032,102000000], [47983964,102000000]); my $intersection = $polygon->intersection($line); is_deeply $intersection->pp, [50000000, 102000000], 'polygon-line intersection'; } # this is not a test: this just demonstrates bad usage, where $polygon->clone gets # DESTROY'ed before the derived object ($point), causing bad memory access if (0) { my $point; { $point = $polygon->clone->[0]; } $point->scale(2); } __END__ Slic3r-version_1.39.1/xs/t/07_extrusionpath.t000066400000000000000000000015501324354444700210310ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 7; my $points = [ [100, 100], [200, 100], [200, 200], ]; my $path = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$points), role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1, ); isa_ok $path->polyline, 'Slic3r::Polyline::Ref', 'path polyline'; is_deeply $path->polyline->pp, $points, 'path points roundtrip'; $path->reverse; is_deeply $path->polyline->pp, [ reverse @$points ], 'reverse path'; $path->append([ 150, 150 ]); is scalar(@$path), 4, 'append to path'; $path->pop_back; is scalar(@$path), 3, 'pop_back from path'; ok $path->first_point->coincides_with($path->polyline->[0]), 'first_point'; $path = $path->clone; is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; __END__ Slic3r-version_1.39.1/xs/t/08_extrusionloop.t000066400000000000000000000173501324354444700210540ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use List::Util qw(sum); use Slic3r::XS; use Test::More tests => 47; { my $square = [ [100, 100], [200, 100], [200, 200], [100, 200], ]; my $square_p = Slic3r::Polygon->new(@$square); my $loop = Slic3r::ExtrusionLoop->new; $loop->append(Slic3r::ExtrusionPath->new( polyline => $square_p->split_at_first_point, role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1, )); isa_ok $loop, 'Slic3r::ExtrusionLoop'; isa_ok $loop->polygon, 'Slic3r::Polygon', 'loop polygon'; is $loop->polygon->area, $square_p->area, 'polygon area'; is $loop->length, $square_p->length(), 'loop length'; $loop = $loop->clone; is scalar(@$loop), 1, 'loop contains one path'; { my $path = $loop->[0]; isa_ok $path, 'Slic3r::ExtrusionPath::Ref'; is $path->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'role'; } $loop->split_at_vertex($square_p->[2]); is scalar(@$loop), 1, 'splitting a single-path loop results in a single path'; is scalar(@{$loop->[0]->polyline}), 5, 'path has correct number of points'; ok $loop->[0]->polyline->[0]->coincides_with($square_p->[2]), 'expected point order'; ok $loop->[0]->polyline->[1]->coincides_with($square_p->[3]), 'expected point order'; ok $loop->[0]->polyline->[2]->coincides_with($square_p->[0]), 'expected point order'; ok $loop->[0]->polyline->[3]->coincides_with($square_p->[1]), 'expected point order'; ok $loop->[0]->polyline->[4]->coincides_with($square_p->[2]), 'expected point order'; } { my $polyline1 = Slic3r::Polyline->new([100,100], [200,100], [200,200]); my $polyline2 = Slic3r::Polyline->new([200,200], [100,200], [100,100]); my $loop = Slic3r::ExtrusionLoop->new_from_paths( Slic3r::ExtrusionPath->new( polyline => $polyline1, role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1, ), Slic3r::ExtrusionPath->new( polyline => $polyline2, role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1, ), ); my $tot_len = sum($polyline1->length, $polyline2->length); is $loop->length, $tot_len, 'length'; is scalar(@$loop), 2, 'loop contains two paths'; { # check splitting at intermediate point my $loop2 = $loop->clone; isa_ok $loop2, 'Slic3r::ExtrusionLoop'; $loop2->split_at_vertex($polyline1->[1]); is $loop2->length, $tot_len, 'length after splitting is unchanged'; is scalar(@$loop2), 3, 'loop contains three paths after splitting'; ok $loop2->[0]->polyline->[0]->coincides_with($polyline1->[1]), 'expected starting point'; ok $loop2->[-1]->polyline->[-1]->coincides_with($polyline1->[1]), 'expected ending point'; ok $loop2->[0]->polyline->[-1]->coincides_with($loop2->[1]->polyline->[0]), 'paths have common point'; ok $loop2->[1]->polyline->[-1]->coincides_with($loop2->[2]->polyline->[0]), 'paths have common point'; is $loop2->[0]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; is $loop2->[1]->role, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, 'expected order after splitting'; is $loop2->[2]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; is scalar(@{$loop2->[0]->polyline}), 2, 'path has correct number of points'; is scalar(@{$loop2->[1]->polyline}), 3, 'path has correct number of points'; is scalar(@{$loop2->[2]->polyline}), 2, 'path has correct number of points'; my @paths = @{$loop2->clip_end(3)}; is sum(map $_->length, @paths), $loop2->length - 3, 'returned paths have expected length'; } { # check splitting at endpoint my $loop2 = $loop->clone; $loop2->split_at_vertex($polyline2->[0]); is $loop2->length, $tot_len, 'length after splitting is unchanged'; is scalar(@$loop2), 2, 'loop contains two paths after splitting'; ok $loop2->[0]->polyline->[0]->coincides_with($polyline2->[0]), 'expected starting point'; ok $loop2->[-1]->polyline->[-1]->coincides_with($polyline2->[0]), 'expected ending point'; ok $loop2->[0]->polyline->[-1]->coincides_with($loop2->[1]->polyline->[0]), 'paths have common point'; ok $loop2->[1]->polyline->[-1]->coincides_with($loop2->[0]->polyline->[0]), 'paths have common point'; is $loop2->[0]->role, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, 'expected order after splitting'; is $loop2->[1]->role, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, 'expected order after splitting'; is scalar(@{$loop2->[0]->polyline}), 3, 'path has correct number of points'; is scalar(@{$loop2->[1]->polyline}), 3, 'path has correct number of points'; } { my $loop2 = $loop->clone; my $point = Slic3r::Point->new(250,150); $loop2->split_at($point); is $loop2->length, $tot_len, 'length after splitting is unchanged'; is scalar(@$loop2), 3, 'loop contains three paths after splitting'; my $expected_start_point = Slic3r::Point->new(200,150); ok $loop2->[0]->polyline->[0]->coincides_with($expected_start_point), 'expected starting point'; ok $loop2->[-1]->polyline->[-1]->coincides_with($expected_start_point), 'expected ending point'; } } { my @polylines = ( Slic3r::Polyline->new([59312736,4821067],[64321068,4821067],[64321068,4821067],[64321068,9321068],[59312736,9321068]), Slic3r::Polyline->new([59312736,9321068],[9829401,9321068]), Slic3r::Polyline->new([9829401,9321068],[4821067,9321068],[4821067,4821067],[9829401,4821067]), Slic3r::Polyline->new([9829401,4821067],[59312736,4821067]), ); my $loop = Slic3r::ExtrusionLoop->new; $loop->append($_) for ( Slic3r::ExtrusionPath->new(polyline => $polylines[0], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1), Slic3r::ExtrusionPath->new(polyline => $polylines[1], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1), Slic3r::ExtrusionPath->new(polyline => $polylines[2], role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1), Slic3r::ExtrusionPath->new(polyline => $polylines[3], role => Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, mm3_per_mm => 1), ); my $len = $loop->length; my $point = Slic3r::Point->new(4821067,9321068); $loop->split_at_vertex($point) or $loop->split_at($point); is $loop->length, $len, 'total length is preserved after splitting'; is_deeply [ map $_->role, @$loop ], [ Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, Slic3r::ExtrusionPath::EXTR_ROLE_OVERHANG_PERIMETER, Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, ], 'order is correctly preserved after splitting'; } { my $loop = Slic3r::ExtrusionLoop->new; $loop->append(Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new([15896783,15868739],[24842049,12117558],[33853238,15801279],[37591780,24780128],[37591780,24844970],[33853231,33825297],[24842049,37509013],[15896798,33757841],[12211841,24812544],[15896783,15868739]), role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1 )); my $len = $loop->length; $loop->split_at(Slic3r::Point->new(15896783,15868739)); is $loop->length, $len, 'split_at() preserves total length'; } __END__ Slic3r-version_1.39.1/xs/t/09_polyline.t000066400000000000000000000070421324354444700177530ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 18; my $points = [ [100, 100], [200, 100], [200, 200], ]; my $polyline = Slic3r::Polyline->new(@$points); is_deeply $polyline->pp, $points, 'polyline roundtrip'; is ref($polyline->arrayref), 'ARRAY', 'polyline arrayref is unblessed'; isa_ok $polyline->[0], 'Slic3r::Point::Ref', 'polyline point is blessed'; my $lines = $polyline->lines; is_deeply [ map $_->pp, @$lines ], [ [ [100, 100], [200, 100] ], [ [200, 100], [200, 200] ], ], 'polyline lines'; $polyline->append_polyline($polyline->clone); is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline'; { my $len = $polyline->length; $polyline->clip_end($len/3); ok abs($polyline->length - ($len-($len/3))) < 1, 'clip_end'; } { my $polyline = Slic3r::Polyline->new( [0,0], [20,0], [50,0], [80,0], [100,0], ); $polyline->simplify(2); is_deeply $polyline->pp, [ [0,0], [100,0] ], 'Douglas-Peucker'; } { my $polyline = Slic3r::Polyline->new( [0,0], [50,50], [100,0], [125,-25], [150,50], ); $polyline->simplify(25); is_deeply $polyline->pp, [ [0, 0], [50, 50], [125, -25], [150, 50] ], 'Douglas-Peucker'; } { my $polyline = Slic3r::Polyline->new( [0,0], [100,0], [50,10], ); $polyline->simplify(25); is_deeply $polyline->pp, [ [0,0], [100,0], [50,10] ], 'Douglas-Peucker uses shortest distance instead of perpendicular distance'; } { my $polyline = Slic3r::Polyline->new(@$points); is $polyline->length, 100*2, 'length'; $polyline->extend_end(50); is $polyline->length, 100*2 + 50, 'extend_end'; $polyline->extend_start(50); is $polyline->length, 100*2 + 50 + 50, 'extend_start'; } { my $polyline = Slic3r::Polyline->new(@$points); my $p1 = Slic3r::Polyline->new; my $p2 = Slic3r::Polyline->new; my $point = Slic3r::Point->new(150, 100); $polyline->split_at($point, $p1, $p2); is scalar(@$p1), 2, 'split_at'; is scalar(@$p2), 3, 'split_at'; ok $p1->last_point->coincides_with($point), 'split_at'; ok $p2->first_point->coincides_with($point), 'split_at'; } { my $polyline = Slic3r::Polyline->new(@$points[0,1,2,0]); my $p1 = Slic3r::Polyline->new; my $p2 = Slic3r::Polyline->new; $polyline->split_at($polyline->first_point, $p1, $p2); is scalar(@$p1), 1, 'split_at'; is scalar(@$p2), 4, 'split_at'; } # disabled because we now use a more efficient but incomplete algorithm if (0) { my $polyline = Slic3r::Polyline->new( map [$_,10], (0,10,20,30,40,50,60) ); { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( [25,0], [55,0], [55,30], [25,30], )); my $p = $polyline->clone; $p->simplify_by_visibility($expolygon); is_deeply $p->pp, [ map [$_,10], (0,10,20,30,50,60) ], 'simplify_by_visibility()'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( [-15,0], [75,0], [75,30], [-15,30], )); my $p = $polyline->clone; $p->simplify_by_visibility($expolygon); is_deeply $p->pp, [ map [$_,10], (0,60) ], 'simplify_by_visibility()'; } { my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( [-15,0], [25,0], [25,30], [-15,30], )); my $p = $polyline->clone; $p->simplify_by_visibility($expolygon); is_deeply $p->pp, [ map [$_,10], (0,20,30,40,50,60) ], 'simplify_by_visibility()'; } } __END__ Slic3r-version_1.39.1/xs/t/10_line.t000066400000000000000000000043421324354444700170370ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 40; use constant PI => 4 * atan2(1, 1); use constant EPSILON => 1E-4; my $points = [ [100, 100], [200, 100], ]; my $line = Slic3r::Line->new(@$points); is_deeply $line->pp, $points, 'line roundtrip'; is ref($line->arrayref), 'ARRAY', 'line arrayref is unblessed'; isa_ok $line->[0], 'Slic3r::Point::Ref', 'line point is blessed'; { my $clone = $line->clone; $clone->reverse; is_deeply $clone->pp, [ reverse @$points ], 'reverse'; } { my $line2 = Slic3r::Line->new($line->a->clone, $line->b->clone); is_deeply $line2->pp, $points, 'line roundtrip with cloned points'; } { my $clone = $line->clone; $clone->translate(10, -5); is_deeply $clone->pp, [ [110, 95], [210, 95], ], 'translate'; } { ok +Slic3r::Line->new([0,0],[200,0])->parallel_to_line(Slic3r::Line->new([200,200],[0,200])), 'parallel_to'; } foreach my $base_angle (0, PI/4, PI/2, PI) { my $line = Slic3r::Line->new([0,0], [100,0]); $line->rotate($base_angle, [0,0]); my $clone = $line->clone; ok $line->parallel_to_line($clone), 'line is parallel to self'; $clone->reverse; ok $line->parallel_to_line($clone), 'line is parallel to self + PI'; ok $line->parallel_to($line->direction), 'line is parallel to its direction'; ok $line->parallel_to($line->direction + PI), 'line is parallel to its direction + PI'; ok $line->parallel_to($line->direction - PI), 'line is parallel to its direction - PI'; { my $line2 = $line->clone; $line2->reverse; ok $line->parallel_to_line($line2), 'line is parallel to its opposite'; } { my $line2 = $line->clone; $line2->rotate(+(EPSILON)/2, [0,0]); ok $line->parallel_to_line($line2), 'line is parallel within epsilon'; } { my $line2 = $line->clone; $line2->rotate(-(EPSILON)/2, [0,0]); ok $line->parallel_to_line($line2), 'line is parallel within epsilon'; } } { my $a = Slic3r::Line->new([100, 0], [200, 0]); my $b = Slic3r::Line->new([300, 300], [300, 100]); my $r = $a->intersection_infinite($b); is_deeply $r->pp, [300, 0], 'intersection_infinite'; } __END__ Slic3r-version_1.39.1/xs/t/11_clipper.t000066400000000000000000000173561324354444700175600ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use List::Util qw(sum); use Slic3r::XS; use Test::More tests => 16; my $square = Slic3r::Polygon->new( # ccw [200, 100], [200, 200], [100, 200], [100, 100], ); my $hole_in_square = Slic3r::Polygon->new( # cw [160, 140], [140, 140], [140, 160], [160, 160], ); my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); { my $result = Slic3r::Geometry::Clipper::offset([ $square, $hole_in_square ], 5); is_deeply [ map $_->pp, @$result ], [ [ [205, 205], [95, 205], [95, 95], [205, 95], ], [ [145, 145], [145, 155], [155, 155], [155, 145], ] ], 'offset'; } { my $result = Slic3r::Geometry::Clipper::offset_ex([ @$expolygon ], 5); is_deeply $result->[0]->pp, [ [ [205, 205], [95, 205], [95, 95], [205, 95], ], [ [145, 145], [145, 155], [155, 155], [155, 145], ] ], 'offset_ex'; } { my $result = Slic3r::Geometry::Clipper::offset2_ex([ @$expolygon ], 5, -2); is_deeply $result->[0]->pp, [ [ [203, 203], [97, 203], [97, 97], [203, 97], ], [ [143, 143], [143, 157], [157, 157], [157, 143], ] ], 'offset2_ex'; } { my $expolygon2 = Slic3r::ExPolygon->new([ [20000000, 20000000], [0, 20000000], [0, 0], [20000000, 0], ], [ [5000000, 15000000], [15000000, 15000000], [15000000, 5000000], [5000000, 5000000], ]); my $result = Slic3r::Geometry::Clipper::offset2_ex([ @$expolygon2 ], -1, +1); is $result->[0]->area, $expolygon2->area, 'offset2_ex'; } { my $polygon1 = Slic3r::Polygon->new(@$square); my $polygon2 = Slic3r::Polygon->new(reverse @$hole_in_square); my $result = Slic3r::Geometry::Clipper::diff_ex([$polygon1], [$polygon2]); is $result->[0]->area, $expolygon->area, 'diff_ex'; } { my $polyline = Slic3r::Polyline->new([50,150], [300,150]); { my $result = Slic3r::Geometry::Clipper::intersection_pl([$polyline], [$square, $hole_in_square]); is scalar(@$result), 2, 'intersection_pl - correct number of result lines'; # results are in no particular order is scalar(grep $_->length == 40, @$result), 2, 'intersection_pl - result lines have correct length'; } { my $result = Slic3r::Geometry::Clipper::diff_pl([$polyline], [$square, $hole_in_square]); is scalar(@$result), 3, 'diff_pl - correct number of result lines'; # results are in no particular order is scalar(grep $_->length == 50, @$result), 1, 'diff_pl - the left result line has correct length'; is scalar(grep $_->length == 100, @$result), 1, 'diff_pl - two right result line has correct length'; is scalar(grep $_->length == 20, @$result), 1, 'diff_pl - the central result line has correct length'; } } if (0) { # Clipper does not preserve polyline orientation my $polyline = Slic3r::Polyline->new([50,150], [300,150]); my $result = Slic3r::Geometry::Clipper::intersection_pl([$polyline], [$square]); is scalar(@$result), 1, 'intersection_pl - correct number of result lines'; is_deeply $result->[0]->pp, [[100,150], [200,150]], 'clipped line orientation is preserved'; } if (0) { # Clipper does not preserve polyline orientation my $polyline = Slic3r::Polyline->new([300,150], [50,150]); my $result = Slic3r::Geometry::Clipper::intersection_pl([$polyline], [$square]); is scalar(@$result), 1, 'intersection_pl - correct number of result lines'; is_deeply $result->[0]->pp, [[200,150], [100,150]], 'clipped line orientation is preserved'; } { # Clipper bug #96 (our issue #2028) my $subject = Slic3r::Polyline->new( [44735000,31936670],[55270000,31936670],[55270000,25270000],[74730000,25270000],[74730000,44730000],[68063296,44730000],[68063296,55270000],[74730000,55270000],[74730000,74730000],[55270000,74730000],[55270000,68063296],[44730000,68063296],[44730000,74730000],[25270000,74730000],[25270000,55270000],[31936670,55270000],[31936670,44730000],[25270000,44730000],[25270000,25270000],[44730000,25270000],[44730000,31936670] ); my $clip = [ Slic3r::Polygon->new([75200000,45200000],[54800000,45200000],[54800000,24800000],[75200000,24800000]), ]; my $result = Slic3r::Geometry::Clipper::intersection_pl([$subject], $clip); is scalar(@$result), 1, 'intersection_pl - result is not empty'; } { # Clipper bug #122 my $subject = [ Slic3r::Polyline->new([1975,1975],[25,1975],[25,25],[1975,25],[1975,1975]), ]; my $clip = [ Slic3r::Polygon->new([2025,2025],[-25,2025],[-25,-25],[2025,-25]), Slic3r::Polygon->new([525,525],[525,1475],[1475,1475],[1475,525]), ]; my $result = Slic3r::Geometry::Clipper::intersection_pl($subject, $clip); is scalar(@$result), 1, 'intersection_pl - result is not empty'; is scalar(@{$result->[0]}), 5, 'intersection_pl - result is not empty'; } { # Clipper bug #126 my $subject = Slic3r::Polyline->new( [200000,19799999],[200000,200000],[24304692,200000],[15102879,17506106],[13883200,19799999],[200000,19799999], ); my $clip = [ Slic3r::Polygon->new([15257205,18493894],[14350057,20200000],[-200000,20200000],[-200000,-200000],[25196917,-200000]), ]; my $result = Slic3r::Geometry::Clipper::intersection_pl([$subject], $clip); is scalar(@$result), 1, 'intersection_pl - result is not empty'; is $result->[0]->length, $subject->length, 'intersection_pl - result has same length as subject polyline'; } if (0) { # Disabled until Clipper bug #127 is fixed my $subject = [ Slic3r::Polyline->new([-90000000,-100000000],[-90000000,100000000]), # vertical Slic3r::Polyline->new([-100000000,-10000000],[100000000,-10000000]), # horizontal Slic3r::Polyline->new([-100000000,0],[100000000,0]), # horizontal Slic3r::Polyline->new([-100000000,10000000],[100000000,10000000]), # horizontal ]; my $clip = Slic3r::Polygon->new( # a circular, convex, polygon [99452190,10452846],[97814760,20791169],[95105652,30901699],[91354546,40673664],[86602540,50000000], [80901699,58778525],[74314483,66913061],[66913061,74314483],[58778525,80901699],[50000000,86602540], [40673664,91354546],[30901699,95105652],[20791169,97814760],[10452846,99452190],[0,100000000], [-10452846,99452190],[-20791169,97814760],[-30901699,95105652],[-40673664,91354546], [-50000000,86602540],[-58778525,80901699],[-66913061,74314483],[-74314483,66913061], [-80901699,58778525],[-86602540,50000000],[-91354546,40673664],[-95105652,30901699], [-97814760,20791169],[-99452190,10452846],[-100000000,0],[-99452190,-10452846], [-97814760,-20791169],[-95105652,-30901699],[-91354546,-40673664],[-86602540,-50000000], [-80901699,-58778525],[-74314483,-66913061],[-66913061,-74314483],[-58778525,-80901699], [-50000000,-86602540],[-40673664,-91354546],[-30901699,-95105652],[-20791169,-97814760], [-10452846,-99452190],[0,-100000000],[10452846,-99452190],[20791169,-97814760], [30901699,-95105652],[40673664,-91354546],[50000000,-86602540],[58778525,-80901699], [66913061,-74314483],[74314483,-66913061],[80901699,-58778525],[86602540,-50000000], [91354546,-40673664],[95105652,-30901699],[97814760,-20791169],[99452190,-10452846],[100000000,0] ); my $result = Slic3r::Geometry::Clipper::intersection_pl($subject, [$clip]); is scalar(@$result), scalar(@$subject), 'intersection_pl - expected number of polylines'; is sum(map scalar(@$_), @$result), scalar(@$subject)*2, 'intersection_pl - expected number of points in polylines'; } __END__ Slic3r-version_1.39.1/xs/t/12_extrusionpathcollection.t000066400000000000000000000062321324354444700231030ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 18; my $points = [ [100, 100], [200, 100], [200, 200], ]; my $path = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@$points), role => Slic3r::ExtrusionPath::EXTR_ROLE_EXTERNAL_PERIMETER, mm3_per_mm => 1, ); my $loop = Slic3r::ExtrusionLoop->new_from_paths( Slic3r::ExtrusionPath->new( polyline => Slic3r::Polygon->new(@$points)->split_at_first_point, role => Slic3r::ExtrusionPath::EXTR_ROLE_FILL, mm3_per_mm => 1, ), ); my $collection = Slic3r::ExtrusionPath::Collection->new( $path, ); isa_ok $collection, 'Slic3r::ExtrusionPath::Collection', 'collection object with items in constructor'; ok !$collection->no_sort, 'no_sort is false by default'; $collection->append($collection); is scalar(@$collection), 2, 'append ExtrusionPath::Collection'; $collection->append($path); is scalar(@$collection), 3, 'append ExtrusionPath'; $collection->append($loop); is scalar(@$collection), 4, 'append ExtrusionLoop'; isa_ok $collection->[1], 'Slic3r::ExtrusionPath::Collection::Ref', 'correct object returned for collection'; isa_ok $collection->[2], 'Slic3r::ExtrusionPath::Ref', 'correct object returned for path'; isa_ok $collection->[3], 'Slic3r::ExtrusionLoop::Ref', 'correct object returned for loop'; is ref($collection->[2]->clone), 'Slic3r::ExtrusionPath', 'correct object returned for cloned path'; is ref($collection->[3]->clone), 'Slic3r::ExtrusionLoop', 'correct object returned for cloned loop'; is scalar(@{$collection->[1]}), 1, 'appended collection was duplicated'; { my $collection_loop = $collection->[3]; $collection_loop->polygon->scale(2); is_deeply $collection->[3]->polygon->pp, $collection_loop->polygon->pp, 'items are returned by reference'; } { my $collection = Slic3r::ExtrusionPath::Collection->new( map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), ); is_deeply [ map $_->y, map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], [20, 18, 15, 10, 8, 5], 'chained_path_from'; is_deeply [ map $_->y, map @{$_->polyline}, @{$collection->chained_path(0)} ], [15, 18, 20, 10, 8, 5], 'chained_path'; } { my $collection = Slic3r::ExtrusionPath::Collection->new( map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1), Slic3r::Polyline->new([15,0], [10,0], [4,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), ); is_deeply [ map $_->x, map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], [reverse 4, 10, 15, 10, 15, 20], 'chained_path_from'; $collection->no_sort(1); my @foo = @{$collection->chained_path(0)}; pass 'chained_path with no_sort'; } { my $coll2 = $collection->clone; ok !$coll2->no_sort, 'expected no_sort value'; $coll2->no_sort(1); ok $coll2->clone->no_sort, 'no_sort is kept after clone'; } __END__ Slic3r-version_1.39.1/xs/t/13_polylinecollection.t000066400000000000000000000016471324354444700220270ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 3; { my $collection = Slic3r::Polyline::Collection->new( Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,10], [0,8], [0,5]), ); is_deeply [ map $_->y, map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ], [20, 18, 15, 10, 8, 5], 'chained_path_from'; is_deeply [ map $_->y, map @$_, @{$collection->chained_path(0)} ], [15, 18, 20, 10, 8, 5], 'chained_path'; } { my $collection = Slic3r::Polyline::Collection->new( Slic3r::Polyline->new([15,0], [10,0], [4,0]), Slic3r::Polyline->new([10,5], [15,5], [20,5]), ); is_deeply [ map $_->x, map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ], [reverse 4, 10, 15, 10, 15, 20], 'chained_path_from'; } __END__ Slic3r-version_1.39.1/xs/t/14_geometry.t000066400000000000000000000026301324354444700177450ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 9; use constant PI => 4 * atan2(1, 1); { my @points = ( Slic3r::Point->new(100,100), Slic3r::Point->new(100,200), Slic3r::Point->new(200,200), Slic3r::Point->new(200,100), Slic3r::Point->new(150,150), ); my $hull = Slic3r::Geometry::convex_hull(\@points); isa_ok $hull, 'Slic3r::Polygon', 'convex_hull returns a Polygon'; is scalar(@$hull), 4, 'convex_hull returns the correct number of points'; } # directions_parallel() and directions_parallel_within() are tested # also with Slic3r::Line::parallel_to() tests in 10_line.t { ok Slic3r::Geometry::directions_parallel_within(0, 0, 0), 'directions_parallel_within'; ok Slic3r::Geometry::directions_parallel_within(0, PI, 0), 'directions_parallel_within'; ok Slic3r::Geometry::directions_parallel_within(0, 0, PI/180), 'directions_parallel_within'; ok Slic3r::Geometry::directions_parallel_within(0, PI, PI/180), 'directions_parallel_within'; ok !Slic3r::Geometry::directions_parallel_within(PI/2, PI, 0), 'directions_parallel_within'; ok !Slic3r::Geometry::directions_parallel_within(PI/2, PI, PI/180), 'directions_parallel_within'; } { my $positions = Slic3r::Geometry::arrange(4, Slic3r::Pointf->new(20, 20), 5); is scalar(@$positions), 4, 'arrange() returns expected number of positions'; } __END__ Slic3r-version_1.39.1/xs/t/15_config.t000066400000000000000000000263201324354444700173620ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 147; foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { $config->set('layer_height', 0.3); ok abs($config->get('layer_height') - 0.3) < 1e-4, 'set/get float'; is $config->serialize('layer_height'), '0.3', 'serialize float'; $config->set('perimeters', 2); is $config->get('perimeters'), 2, 'set/get int'; is $config->serialize('perimeters'), '2', 'serialize int'; $config->set('extrusion_axis', 'A'); is $config->get('extrusion_axis'), 'A', 'set/get string'; is $config->serialize('extrusion_axis'), 'A', 'serialize string'; $config->set('notes', "foo\nbar"); is $config->get('notes'), "foo\nbar", 'set/get string with newline'; is $config->serialize('notes'), 'foo\nbar', 'serialize string with newline'; $config->set_deserialize('notes', 'bar\nbaz'); is $config->get('notes'), "bar\nbaz", 'deserialize string with newline'; foreach my $test_data ( { name => 'empty', values => [], serialized => '' }, { name => 'single empty', values => [''], serialized => '""' }, { name => 'single noempty, simple', values => ['RGB'], serialized => 'RGB' }, { name => 'multiple noempty, simple', values => ['ABC', 'DEF', '09182745@!#$*(&'], serialized => 'ABC;DEF;09182745@!#$*(&' }, { name => 'multiple, simple, some empty', values => ['ABC', 'DEF', '', '09182745@!#$*(&', ''], serialized => 'ABC;DEF;;09182745@!#$*(&;' }, { name => 'complex', values => ['some "quoted" notes', "yet\n some notes", "whatever \n notes", ''], serialized => '"some \"quoted\" notes";"yet\n some notes";"whatever \n notes";' } ) { $config->set('filament_notes', $test_data->{values}); is $config->serialize('filament_notes'), $test_data->{serialized}, 'serialize multi-string value ' . $test_data->{name}; $config->set_deserialize('filament_notes', ''); is_deeply $config->get('filament_notes'), [], 'deserialize multi-string value - empty ' . $test_data->{name}; $config->set_deserialize('filament_notes', $test_data->{serialized}); is_deeply $config->get('filament_notes'), $test_data->{values}, 'deserialize complex multi-string value ' . $test_data->{name}; } $config->set('first_layer_height', 0.3); ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent'; is $config->serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; $config->set('first_layer_height', '50%'); $config->get_abs_value('first_layer_height'); ok abs($config->get_abs_value('first_layer_height') - 0.15) < 1e-4, 'set/get relative floatOrPercent'; is $config->serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; # Uh-oh, we have no point option to test at the moment #ok $config->set('print_center', [50,80]), 'valid point coordinates'; #is_deeply $config->get('print_center'), [50,80], 'set/get point'; #is $config->serialize('print_center'), '50,80', 'serialize point'; #$config->set_deserialize('print_center', '20,10'); #is_deeply $config->get('print_center'), [20,10], 'deserialize point'; #ok !$config->set('print_center', ['t',80]), 'invalid point X'; #ok !$config->set('print_center', [50,'t']), 'invalid point Y'; $config->set('use_relative_e_distances', 1); is $config->get('use_relative_e_distances'), 1, 'set/get bool'; is $config->serialize('use_relative_e_distances'), '1', 'serialize bool'; $config->set('gcode_flavor', 'teacup'); is $config->get('gcode_flavor'), 'teacup', 'set/get enum'; is $config->serialize('gcode_flavor'), 'teacup', 'serialize enum'; $config->set_deserialize('gcode_flavor', 'mach3'); is $config->get('gcode_flavor'), 'mach3', 'deserialize enum (gcode_flavor)'; $config->set_deserialize('gcode_flavor', 'machinekit'); is $config->get('gcode_flavor'), 'machinekit', 'deserialize enum (gcode_flavor)'; $config->set_deserialize('fill_pattern', 'line'); is $config->get('fill_pattern'), 'line', 'deserialize enum (fill_pattern)'; $config->set_deserialize('support_material_pattern', 'pillars'); is $config->get('support_material_pattern'), 'pillars', 'deserialize enum (support_material_pattern)'; $config->set('extruder_offset', [[10,20],[30,45]]); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points'; $config->set('extruder_offset', [Slic3r::Pointf->new(10,20),Slic3r::Pointf->new(30,45)]); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points'; is $config->serialize('extruder_offset'), '10x20,30x45', 'serialize points'; $config->set_deserialize('extruder_offset', '20x10'); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[20,10]], 'deserialize points'; $config->set_deserialize('extruder_offset', '0x0'); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[0,0]], 'deserialize points'; { my @values = ([10,20]); $values[2] = [10,20]; # implicitely extend array; this is not the same as explicitely assigning undef to second item ok !$config->set('extruder_offset', \@values), 'reject undef points'; } # truncate ->get() to first decimal digit $config->set('nozzle_diameter', [0.2,3]); is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.2,3], 'set/get floats'; is $config->serialize('nozzle_diameter'), '0.2,3', 'serialize floats'; $config->set_deserialize('nozzle_diameter', '0.1,0.4'); is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.1,0.4], 'deserialize floats'; $config->set_deserialize('nozzle_diameter', '3'); is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [3], 'deserialize a single float'; { my @values = (0.4); $values[2] = 2; # implicitely extend array; this is not the same as explicitely assigning undef to second item ok !$config->set('nozzle_diameter', \@values), 'reject undef floats'; } $config->set('temperature', [180,210]); is_deeply $config->get('temperature'), [180,210], 'set/get ints'; is $config->serialize('temperature'), '180,210', 'serialize ints'; $config->set_deserialize('temperature', '195,220'); is_deeply $config->get('temperature'), [195,220], 'deserialize ints'; { my @values = (180); $values[2] = 200; # implicitely extend array; this is not the same as explicitely assigning undef to second item ok !$config->set('temperature', \@values), 'reject undef ints'; } $config->set('wipe', [1,0]); is_deeply $config->get('wipe'), [1,0], 'set/get bools'; is $config->get_at('wipe', 0), 1, 'get_at bools'; is $config->get_at('wipe', 1), 0, 'get_at bools'; is $config->get_at('wipe', 9), 1, 'get_at bools'; is $config->serialize('wipe'), '1,0', 'serialize bools'; $config->set_deserialize('wipe', '0,1,1'); is_deeply $config->get('wipe'), [0,1,1], 'deserialize bools'; $config->set_deserialize('wipe', ''); is_deeply $config->get('wipe'), [], 'deserialize bools from empty string'; $config->set_deserialize('retract_layer_change', 0); is_deeply $config->get('retract_layer_change'), [0], 'deserialize bools from non-string value'; { my @values = (1); $values[2] = 1; # implicitely extend array; this is not the same as explicitely assigning undef to second item ok !$config->set('wipe', \@values), 'reject undef bools'; } $config->set('post_process', ['foo','bar']); is_deeply $config->get('post_process'), ['foo','bar'], 'set/get strings'; is $config->serialize('post_process'), 'foo;bar', 'serialize strings'; $config->set_deserialize('post_process', 'bar;baz'); is_deeply $config->get('post_process'), ['bar','baz'], 'deserialize strings'; { my @values = ('foo'); $values[2] = 'bar'; # implicitely extend array; this is not the same as explicitely assigning undef to second item ok !$config->set('post_process', \@values), 'reject undef strings'; } is_deeply [ sort @{$config->get_keys} ], [ sort keys %{$config->as_hash} ], 'get_keys and as_hash'; } { my $config = Slic3r::Config->new; $config->set('perimeters', 2); # test that no crash happens when using set_deserialize() with a key that hasn't been set() yet $config->set_deserialize('filament_diameter', '3'); my $config2 = Slic3r::Config::Static::new_FullPrintConfig; $config2->apply_dynamic($config); is $config2->get('perimeters'), 2, 'apply_dynamic'; } { my $config = Slic3r::Config::Static::new_FullPrintConfig; my $config2 = Slic3r::Config->new; $config2->apply_static($config); is $config2->get('perimeters'), Slic3r::Config::print_config_def()->{perimeters}{default}, 'apply_static and print_config_def'; $config->set('top_solid_infill_speed', 70); is $config->get_abs_value('top_solid_infill_speed'), 70, 'get_abs_value() works when ratio_over references a floatOrPercent option'; } { my $config = Slic3r::Config->new; $config->set('fill_pattern', 'line'); my $config2 = Slic3r::Config->new; $config2->set('fill_pattern', 'hilbertcurve'); is $config->get('fill_pattern'), 'line', 'no interferences between DynamicConfig objects'; } { my $config = Slic3r::Config->new; # the pair [0,0] is part of the test, since it checks whether the 0x0 serialized value is correctly parsed $config->set('extruder_offset', [ [0,0], [20,0], [0,20] ]); my $config2 = Slic3r::Config->new; $config2->apply($config); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [ map $_->pp, @{$config2->get('extruder_offset')} ], 'apply dynamic over dynamic'; } { my $config = Slic3r::Config->new; $config->set('extruder', 2); $config->set('perimeter_extruder', 3); $config->normalize; ok !$config->has('extruder'), 'extruder option is removed after normalize()'; is $config->get('infill_extruder'), 2, 'undefined extruder is populated with default extruder'; is $config->get('perimeter_extruder'), 3, 'defined extruder is not overwritten by default extruder'; } { my $config = Slic3r::Config->new; $config->set('infill_extruder', 2); $config->normalize; is $config->get('solid_infill_extruder'), 2, 'undefined solid infill extruder is populated with infill extruder'; } { my $config = Slic3r::Config->new; $config->set('spiral_vase', 1); $config->set('retract_layer_change', [1,0]); $config->normalize; is_deeply $config->get('retract_layer_change'), [0,0], 'retract_layer_change is disabled with spiral_vase'; } { use Cwd qw(abs_path); use File::Basename qw(dirname); my $path = abs_path($0); my $config = Slic3r::Config::load(dirname($path)."/inc/22_config_bad_config_options.ini"); ok 1, 'did not crash on reading invalid items in config'; } __END__ Slic3r-version_1.39.1/xs/t/16_flow.t000066400000000000000000000011201324354444700170540ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 2; { my $flow = Slic3r::Flow->new_from_width( role => Slic3r::Flow::FLOW_ROLE_PERIMETER, width => '1', nozzle_diameter => 0.5, layer_height => 0.3, bridge_flow_ratio => 1, ); isa_ok $flow, 'Slic3r::Flow', 'new_from_width'; } { my $flow = Slic3r::Flow->new( width => 1, height => 0.4, nozzle_diameter => 0.5, ); isa_ok $flow, 'Slic3r::Flow', 'new'; } __END__ Slic3r-version_1.39.1/xs/t/17_boundingbox.t000066400000000000000000000013461324354444700204360ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 5; { my @points = ( Slic3r::Point->new(100, 200), Slic3r::Point->new(500, -600), ); my $bb = Slic3r::Geometry::BoundingBox->new_from_points(\@points); isa_ok $bb, 'Slic3r::Geometry::BoundingBox', 'new_from_points'; is_deeply $bb->min_point->pp, [100,-600], 'min_point'; is_deeply $bb->max_point->pp, [500,200], 'max_point'; } { my $bb = Slic3r::Geometry::BoundingBox->new; $bb->merge_point(Slic3r::Point->new(10, 10)); is_deeply $bb->min_point->pp, [10,10], 'min_point equals to the only defined point'; is_deeply $bb->max_point->pp, [10,10], 'max_point equals to the only defined point'; } __END__ Slic3r-version_1.39.1/xs/t/18_motionplanner.t000066400000000000000000000072211324354444700210040ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; BEGIN { use FindBin; use lib "$FindBin::Bin/../../lib"; } use Slic3r::XS; use Test::More tests => 20; my $square = Slic3r::Polygon->new( # ccw [100, 100], [200, 100], [200, 200], [100, 200], ); my $hole_in_square = Slic3r::Polygon->new( # cw [140, 140], [140, 160], [160, 160], [160, 140], ); $_->scale(1/0.000001) for $square, $hole_in_square; my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); { my $mp = Slic3r::MotionPlanner->new([ $expolygon ]); isa_ok $mp, 'Slic3r::MotionPlanner'; my $from = Slic3r::Point->new(120, 120); my $to = Slic3r::Point->new(180,180); $_->scale(1/0.000001) for $from, $to; my $path = $mp->shortest_path($from, $to); ok $path->is_valid(), 'return path is valid'; ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line'; ok $path->first_point->coincides_with($from), 'first path point coincides with initial point'; ok $path->last_point->coincides_with($to), 'last path point coincides with destination point'; ok $expolygon->contains_polyline($path), 'path is fully contained in expolygon'; } { my $mp = Slic3r::MotionPlanner->new([ $expolygon ]); isa_ok $mp, 'Slic3r::MotionPlanner'; my $from = Slic3r::Point->new(80, 100); my $to = Slic3r::Point->new(220,200); $_->scale(1/0.000001) for $from, $to; my $path = $mp->shortest_path($from, $to); ok $path->is_valid(), 'return path is valid'; ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line'; ok $path->first_point->coincides_with($from), 'first path point coincides with initial point'; ok $path->last_point->coincides_with($to), 'last path point coincides with destination point'; is scalar(@{ Slic3r::Geometry::Clipper::intersection_pl([$path], [@$expolygon]) }), 0, 'path has no intersection with expolygon'; } { my $expolygon2 = $expolygon->clone; $expolygon2->translate(300/0.000001, 0); my $mp = Slic3r::MotionPlanner->new([ $expolygon, $expolygon2 ]); isa_ok $mp, 'Slic3r::MotionPlanner'; my $from = Slic3r::Point->new(120, 120); my $to = Slic3r::Point->new(120 + 300, 120); $_->scale(1/0.000001) for $from, $to; ok $expolygon->contains_point($from), 'start point is contained in first expolygon'; ok $expolygon2->contains_point($to), 'end point is contained in second expolygon'; my $path = $mp->shortest_path($from, $to); ok $path->is_valid(), 'return path is valid'; } { my $expolygons = [ Slic3r::ExPolygon->new([[123800962,89330311],[123959159,89699438],[124000004,89898430],[124000012,110116427],[123946510,110343065],[123767391,110701303],[123284087,111000001],[102585791,111000009],[102000004,110414223],[102000004,89585787],[102585790,89000000],[123300022,88999993]]), Slic3r::ExPolygon->new([[97800954,89330311],[97959151,89699438],[97999996,89898430],[98000004,110116427],[97946502,110343065],[97767383,110701303],[97284079,111000001],[76585783,111000009],[75999996,110414223],[75999996,89585787],[76585782,89000000],[97300014,88999993]]), ]; my $mp = Slic3r::MotionPlanner->new($expolygons); isa_ok $mp, 'Slic3r::MotionPlanner'; my $from = Slic3r::Point->new(79120520, 107839491); my $to = Slic3r::Point->new(104664164, 108335852); ok $expolygons->[1]->contains_point($from), 'start point is contained in second expolygon'; ok $expolygons->[0]->contains_point($to), 'end point is contained in first expolygon'; my $path = $mp->shortest_path($from, $to); ok $path->is_valid(), 'return path is valid'; } __END__ Slic3r-version_1.39.1/xs/t/19_model.t000066400000000000000000000011351324354444700172160ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 4; { my $model = Slic3r::Model->new; my $object = $model->_add_object; isa_ok $object, 'Slic3r::Model::Object::Ref'; isa_ok $object->origin_translation, 'Slic3r::Pointf3::Ref'; $object->origin_translation->translate(10,0,0); is_deeply \@{$object->origin_translation}, [10,0,0], 'origin_translation is modified by ref'; my $lhr = [ [ 5, 10, 0.1 ] ]; $object->set_layer_height_ranges($lhr); is_deeply $object->layer_height_ranges, $lhr, 'layer_height_ranges roundtrip'; } __END__ Slic3r-version_1.39.1/xs/t/20_print.t000066400000000000000000000007111324354444700172410ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 5; { my $print = Slic3r::Print->new; isa_ok $print, 'Slic3r::Print'; isa_ok $print->config, 'Slic3r::Config::Static::Ref'; isa_ok $print->default_object_config, 'Slic3r::Config::Static::Ref'; isa_ok $print->default_region_config, 'Slic3r::Config::Static::Ref'; isa_ok $print->placeholder_parser, 'Slic3r::GCode::PlaceholderParser::Ref'; } __END__ Slic3r-version_1.39.1/xs/t/21_gcode.t000066400000000000000000000005661324354444700171770ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 2; { my $gcodegen = Slic3r::GCode->new; $gcodegen->set_origin(Slic3r::Pointf->new(10,0)); is_deeply $gcodegen->origin->pp, [10,0], 'set_origin'; $gcodegen->origin->translate(5,5); is_deeply $gcodegen->origin->pp, [15,5], 'origin returns reference to point'; } __END__ Slic3r-version_1.39.1/xs/t/22_exception.t000066400000000000000000000007021324354444700201050ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Slic3r::XS; use Test::More tests => 1; if ($ENV{SLIC3R_HAS_BROKEN_CROAK}) { ok 1, 'SLIC3R_HAS_BROKEN_CROAK set, croaks and confesses from a C++ code will lead to an application exit!'; } else { eval { Slic3r::xspp_test_croak_hangs_on_strawberry(); }; is $@, "xspp_test_croak_hangs_on_strawberry: exception catched\n", 'croak from inside a C++ exception delivered'; } __END__ Slic3r-version_1.39.1/xs/t/inc/000077500000000000000000000000001324354444700161715ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/t/inc/22_config_bad_config_options.ini000066400000000000000000000003161324354444700243500ustar00rootroot00000000000000# generated by Slic3r 1.1.7 on Tue Aug 19 21:49:50 2014 avoid_crossing_perimeters = 1 bed_size = 200,180 g0 = 0 perimeter_acceleration = 0 support_material_extruder = 1 support_material_extrusion_width = 0 Slic3r-version_1.39.1/xs/xsp/000077500000000000000000000000001324354444700157675ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/xsp/BoundingBox.xsp000066400000000000000000000103461324354444700207450ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/BoundingBox.hpp" #include "libslic3r/Point.hpp" %} %name{Slic3r::Geometry::BoundingBox} class BoundingBox { BoundingBox(); ~BoundingBox(); Clone clone() %code{% RETVAL = THIS; %}; void merge(BoundingBox* bb) %code{% THIS->merge(*bb); %}; void merge_point(Point* point) %code{% THIS->merge(*point); %}; void scale(double factor); void translate(double x, double y); void offset(double delta); bool contains_point(Point* point) %code{% RETVAL = THIS->contains(*point); %}; bool overlap(BoundingBox* bbox) %code{% RETVAL = THIS->overlap(*bbox); %}; Clone polygon(); Clone size(); Clone center(); bool empty() %code{% RETVAL = empty(*THIS); %}; double radius(); Clone min_point() %code{% RETVAL = THIS->min; %}; Clone max_point() %code{% RETVAL = THIS->max; %}; int x_min() %code{% RETVAL = THIS->min.x; %}; int x_max() %code{% RETVAL = THIS->max.x; %}; int y_min() %code{% RETVAL = THIS->min.y; %}; int y_max() %code{% RETVAL = THIS->max.y; %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld;%ld,%ld", THIS->min.x, THIS->min.y, THIS->max.x, THIS->max.y); RETVAL = buf; %}; bool defined() %code{% RETVAL = THIS->defined; %}; %{ BoundingBox* new_from_points(CLASS, points) char* CLASS Points points CODE: RETVAL = new BoundingBox(points); OUTPUT: RETVAL %} }; %name{Slic3r::Geometry::BoundingBoxf} class BoundingBoxf { BoundingBoxf(); ~BoundingBoxf(); Clone clone() %code{% RETVAL = THIS; %}; void merge(BoundingBoxf* bb) %code{% THIS->merge(*bb); %}; void merge_point(Pointf* point) %code{% THIS->merge(*point); %}; void scale(double factor); void translate(double x, double y); Clone size(); Clone center(); double radius(); bool empty() %code{% RETVAL = empty(*THIS); %}; Clone min_point() %code{% RETVAL = THIS->min; %}; Clone max_point() %code{% RETVAL = THIS->max; %}; double x_min() %code{% RETVAL = THIS->min.x; %}; double x_max() %code{% RETVAL = THIS->max.x; %}; double y_min() %code{% RETVAL = THIS->min.y; %}; double y_max() %code{% RETVAL = THIS->max.y; %}; void set_x_min(double val) %code{% THIS->min.x = val; %}; void set_x_max(double val) %code{% THIS->max.x = val; %}; void set_y_min(double val) %code{% THIS->min.y = val; %}; void set_y_max(double val) %code{% THIS->max.y = val; %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf;%lf,%lf", THIS->min.x, THIS->min.y, THIS->max.x, THIS->max.y); RETVAL = buf; %}; bool defined() %code{% RETVAL = THIS->defined; %}; %{ BoundingBoxf* new_from_points(CLASS, points) char* CLASS Pointfs points CODE: RETVAL = new BoundingBoxf(points); OUTPUT: RETVAL %} }; %name{Slic3r::Geometry::BoundingBoxf3} class BoundingBoxf3 { BoundingBoxf3(); ~BoundingBoxf3(); Clone clone() %code{% RETVAL = THIS; %}; void merge(BoundingBoxf3* bb) %code{% THIS->merge(*bb); %}; void merge_point(Pointf3* point) %code{% THIS->merge(*point); %}; void scale(double factor); void translate(double x, double y, double z); void offset(double delta); bool contains_point(Pointf3* point) %code{% RETVAL = THIS->contains(*point); %}; Clone size(); Clone center(); double radius(); bool empty() %code{% RETVAL = empty(*THIS); %}; Clone min_point() %code{% RETVAL = THIS->min; %}; Clone max_point() %code{% RETVAL = THIS->max; %}; double x_min() %code{% RETVAL = THIS->min.x; %}; double x_max() %code{% RETVAL = THIS->max.x; %}; double y_min() %code{% RETVAL = THIS->min.y; %}; double y_max() %code{% RETVAL = THIS->max.y; %}; double z_min() %code{% RETVAL = THIS->min.z; %}; double z_max() %code{% RETVAL = THIS->max.z; %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf,%lf;%lf,%lf,%lf", THIS->min.x, THIS->min.y, THIS->min.z, THIS->max.x, THIS->max.y, THIS->max.z); RETVAL = buf; %}; bool defined() %code{% RETVAL = THIS->defined; %}; }; Slic3r-version_1.39.1/xs/xsp/BridgeDetector.xsp000066400000000000000000000022731324354444700214150ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/BridgeDetector.hpp" %} %name{Slic3r::BridgeDetector} class BridgeDetector { ~BridgeDetector(); bool detect_angle(); Polygons coverage(); %name{coverage_by_angle} Polygons coverage(double angle); Polylines unsupported_edges(); %name{unsupported_edges_by_angle} Polylines unsupported_edges(double angle); double angle() %code{% RETVAL = THIS->angle; %}; double resolution() %code{% RETVAL = THIS->resolution; %}; %{ BridgeDetector* BridgeDetector::new(expolygon, lower_slices, extrusion_width) ExPolygon* expolygon; ExPolygonCollection* lower_slices; int extrusion_width; CODE: RETVAL = new BridgeDetector(*expolygon, *lower_slices, extrusion_width); OUTPUT: RETVAL BridgeDetector* BridgeDetector::new_expolygons(expolygons, lower_slices, extrusion_width) ExPolygonCollection* expolygons; ExPolygonCollection* lower_slices; int extrusion_width; CODE: RETVAL = new BridgeDetector(expolygons->expolygons, *lower_slices, extrusion_width); OUTPUT: RETVAL %} }; Slic3r-version_1.39.1/xs/xsp/Clipper.xsp000066400000000000000000000067421324354444700201320ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "clipper.hpp" #include "libslic3r/ClipperUtils.hpp" %} %package{Slic3r::Geometry::Clipper}; %{ IV _constant() ALIAS: JT_MITER = jtMiter JT_ROUND = jtRound JT_SQUARE = jtSquare CODE: RETVAL = ix; OUTPUT: RETVAL Polygons offset(polygons, delta, joinType = ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta ClipperLib::JoinType joinType double miterLimit CODE: RETVAL = offset(polygons, delta, joinType, miterLimit); OUTPUT: RETVAL ExPolygons offset_ex(polygons, delta, joinType = ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta ClipperLib::JoinType joinType double miterLimit CODE: RETVAL = offset_ex(polygons, delta, joinType, miterLimit); OUTPUT: RETVAL Polygons offset2(polygons, delta1, delta2, joinType = ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta1 const float delta2 ClipperLib::JoinType joinType double miterLimit CODE: RETVAL = offset2(polygons, delta1, delta2, joinType, miterLimit); OUTPUT: RETVAL ExPolygons offset2_ex(polygons, delta1, delta2, joinType = ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta1 const float delta2 ClipperLib::JoinType joinType double miterLimit CODE: RETVAL = offset2_ex(polygons, delta1, delta2, joinType, miterLimit); OUTPUT: RETVAL Polygons diff(subject, clip, safety_offset = false) Polygons subject Polygons clip bool safety_offset CODE: RETVAL = diff(subject, clip, safety_offset); OUTPUT: RETVAL ExPolygons diff_ex(subject, clip, safety_offset = false) Polygons subject Polygons clip bool safety_offset CODE: RETVAL = diff_ex(subject, clip, safety_offset); OUTPUT: RETVAL Polylines diff_pl(subject, clip) Polylines subject Polygons clip CODE: RETVAL = diff_pl(subject, clip); OUTPUT: RETVAL Polygons intersection(subject, clip, safety_offset = false) Polygons subject Polygons clip bool safety_offset CODE: RETVAL = intersection(subject, clip, safety_offset); OUTPUT: RETVAL ExPolygons intersection_ex(subject, clip, safety_offset = false) Polygons subject Polygons clip bool safety_offset CODE: RETVAL = intersection_ex(subject, clip, safety_offset); OUTPUT: RETVAL Polylines intersection_pl(subject, clip) Polylines subject Polygons clip CODE: RETVAL = intersection_pl(subject, clip); OUTPUT: RETVAL Polygons union(subject, safety_offset = false) Polygons subject bool safety_offset CODE: RETVAL = union_(subject, safety_offset); OUTPUT: RETVAL ExPolygons union_ex(subject, safety_offset = false) Polygons subject bool safety_offset CODE: RETVAL = union_ex(subject, safety_offset); OUTPUT: RETVAL %} Slic3r-version_1.39.1/xs/xsp/Config.xsp000066400000000000000000000262071324354444700177370ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/PrintConfig.hpp" %} %name{Slic3r::Config} class DynamicPrintConfig { DynamicPrintConfig(); ~DynamicPrintConfig(); static DynamicPrintConfig* new_from_defaults(); static DynamicPrintConfig* new_from_defaults_keys(std::vector keys); DynamicPrintConfig* clone() %code{% RETVAL = new DynamicPrintConfig(*THIS); %}; DynamicPrintConfig* clone_only(std::vector keys) %code{% RETVAL = new DynamicPrintConfig(); RETVAL->apply_only(*THIS, keys, true); %}; bool has(t_config_option_key opt_key); SV* as_hash() %code{% RETVAL = ConfigBase__as_hash(THIS); %}; SV* get(t_config_option_key opt_key) %code{% RETVAL = ConfigBase__get(THIS, opt_key); %}; SV* get_at(t_config_option_key opt_key, int i) %code{% RETVAL = ConfigBase__get_at(THIS, opt_key, i); %}; SV* get_value(t_config_option_key opt_key) %code{% const ConfigOptionDef *def = THIS->def()->get(opt_key); RETVAL = (def != nullptr && ! def->ratio_over.empty()) ? newSVnv(THIS->get_abs_value(opt_key)) : ConfigBase__get(THIS, opt_key); %}; bool set(t_config_option_key opt_key, SV* value) %code{% RETVAL = ConfigBase__set(THIS, opt_key, value); %}; bool set_deserialize(t_config_option_key opt_key, SV* str) %code{% RETVAL = ConfigBase__set_deserialize(THIS, opt_key, str); %}; void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false) %code{% ConfigBase__set_ifndef(THIS, opt_key, value, deserialize); %}; std::string serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; std::vector diff(DynamicPrintConfig* other) %code{% RETVAL = THIS->diff(*other); %}; bool equals(DynamicPrintConfig* other) %code{% RETVAL = THIS->equals(*other); %}; void apply_static(StaticPrintConfig* other) %code{% THIS->apply(*other, true); %}; %name{get_keys} std::vector keys(); void erase(t_config_option_key opt_key); void normalize(); %name{setenv} void setenv_(); double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; static DynamicPrintConfig* load(char *path) %code%{ auto config = new DynamicPrintConfig(); try { config->load(path); RETVAL = config; } catch (std::exception& e) { delete config; croak("Error extracting configuration from %s:\n%s\n", path, e.what()); } %}; void save(std::string file); int validate() %code%{ std::string err = THIS->validate(); if (! err.empty()) croak("Configuration is not valid: %s\n", err.c_str()); RETVAL = 1; %}; }; %name{Slic3r::Config::Static} class StaticPrintConfig { static StaticPrintConfig* new_GCodeConfig() %code{% RETVAL = new GCodeConfig(); %}; static StaticPrintConfig* new_PrintConfig() %code{% RETVAL = new PrintConfig(); %}; static StaticPrintConfig* new_PrintObjectConfig() %code{% RETVAL = new PrintObjectConfig(); %}; static StaticPrintConfig* new_PrintRegionConfig() %code{% RETVAL = new PrintRegionConfig(); %}; static StaticPrintConfig* new_FullPrintConfig() %code{% RETVAL = static_cast(new FullPrintConfig()); %}; ~StaticPrintConfig(); bool has(t_config_option_key opt_key); SV* as_hash() %code{% RETVAL = ConfigBase__as_hash(THIS); %}; SV* get(t_config_option_key opt_key) %code{% RETVAL = ConfigBase__get(THIS, opt_key); %}; SV* get_at(t_config_option_key opt_key, int i) %code{% RETVAL = ConfigBase__get_at(THIS, opt_key, i); %}; bool set(t_config_option_key opt_key, SV* value) %code{% RETVAL = StaticConfig__set(THIS, opt_key, value); %}; bool set_deserialize(t_config_option_key opt_key, SV* str) %code{% RETVAL = ConfigBase__set_deserialize(THIS, opt_key, str); %}; void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false) %code{% ConfigBase__set_ifndef(THIS, opt_key, value, deserialize); %}; std::string serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); void apply_static(StaticPrintConfig* other) %code{% THIS->apply(*other, true); %}; void apply_dynamic(DynamicPrintConfig* other) %code{% THIS->apply(*other, true); %}; %name{get_keys} std::vector keys(); std::string get_extrusion_axis() %code{% if (GCodeConfig* config = dynamic_cast(THIS)) { RETVAL = config->get_extrusion_axis(); } else { CONFESS("This StaticConfig object does not provide get_extrusion_axis()"); } %}; %name{setenv} void setenv_(); double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; static StaticPrintConfig* load(char *path) %code%{ auto config = new FullPrintConfig(); try { config->load(path); RETVAL = static_cast(config); } catch (std::exception& e) { delete config; croak("Error extracting configuration from %s:\n%s\n", path, e.what()); } %}; void save(std::string file); }; %package{Slic3r::Config}; %{ PROTOTYPES: DISABLE SV* print_config_def() CODE: t_optiondef_map &def = Slic3r::print_config_def.options; HV* options_hv = newHV(); for (t_optiondef_map::iterator oit = def.begin(); oit != def.end(); ++oit) { HV* hv = newHV(); t_config_option_key opt_key = oit->first; ConfigOptionDef* optdef = &oit->second; const char* opt_type; if (optdef->type == coFloat || optdef->type == coFloats || optdef->type == coFloatOrPercent) { opt_type = "f"; } else if (optdef->type == coPercent || optdef->type == coPercents) { opt_type = "percent"; } else if (optdef->type == coInt || optdef->type == coInts) { opt_type = "i"; } else if (optdef->type == coString) { opt_type = "s"; } else if (optdef->type == coStrings) { opt_type = "s@"; } else if (optdef->type == coPoint || optdef->type == coPoints) { opt_type = "point"; } else if (optdef->type == coBool || optdef->type == coBools) { opt_type = "bool"; } else if (optdef->type == coEnum) { opt_type = "select"; } else { throw "Unknown option type"; } (void)hv_stores( hv, "type", newSVpv(opt_type, 0) ); (void)hv_stores( hv, "gui_type", newSVpvn(optdef->gui_type.c_str(), optdef->gui_type.length()) ); (void)hv_stores( hv, "gui_flags", newSVpvn(optdef->gui_flags.c_str(), optdef->gui_flags.length()) ); (void)hv_stores( hv, "label", newSVpvn_utf8(optdef->label.c_str(), optdef->label.length(), true) ); if (!optdef->full_label.empty()) (void)hv_stores( hv, "full_label", newSVpvn_utf8(optdef->full_label.c_str(), optdef->full_label.length(), true) ); (void)hv_stores( hv, "category", newSVpvn_utf8(optdef->category.c_str(), optdef->category.length(), true) ); (void)hv_stores( hv, "tooltip", newSVpvn_utf8(optdef->tooltip.c_str(), optdef->tooltip.length(), true) ); (void)hv_stores( hv, "sidetext", newSVpvn_utf8(optdef->sidetext.c_str(), optdef->sidetext.length(), true) ); (void)hv_stores( hv, "cli", newSVpvn(optdef->cli.c_str(), optdef->cli.length()) ); (void)hv_stores( hv, "ratio_over", newSVpvn(optdef->ratio_over.c_str(), optdef->ratio_over.length()) ); (void)hv_stores( hv, "multiline", newSViv(optdef->multiline ? 1 : 0) ); (void)hv_stores( hv, "full_width", newSViv(optdef->full_width ? 1 : 0) ); (void)hv_stores( hv, "readonly", newSViv(optdef->readonly ? 1 : 0) ); (void)hv_stores( hv, "height", newSViv(optdef->height) ); (void)hv_stores( hv, "width", newSViv(optdef->width) ); (void)hv_stores( hv, "min", newSViv(optdef->min) ); (void)hv_stores( hv, "max", newSViv(optdef->max) ); // aliases if (!optdef->aliases.empty()) { AV* av = newAV(); av_fill(av, optdef->aliases.size()-1); for (std::vector::iterator it = optdef->aliases.begin(); it != optdef->aliases.end(); ++it) av_store(av, it - optdef->aliases.begin(), newSVpvn(it->c_str(), it->length())); (void)hv_stores( hv, "aliases", newRV_noinc((SV*)av) ); } // shortcut if (!optdef->shortcut.empty()) { AV* av = newAV(); av_fill(av, optdef->shortcut.size()-1); for (std::vector::iterator it = optdef->shortcut.begin(); it != optdef->shortcut.end(); ++it) av_store(av, it - optdef->shortcut.begin(), newSVpvn(it->c_str(), it->length())); (void)hv_stores( hv, "shortcut", newRV_noinc((SV*)av) ); } // enum_values if (!optdef->enum_values.empty()) { AV* av = newAV(); av_fill(av, optdef->enum_values.size()-1); for (std::vector::iterator it = optdef->enum_values.begin(); it != optdef->enum_values.end(); ++it) av_store(av, it - optdef->enum_values.begin(), newSVpvn(it->c_str(), it->length())); (void)hv_stores( hv, "values", newRV_noinc((SV*)av) ); } // enum_labels if (!optdef->enum_labels.empty()) { AV* av = newAV(); av_fill(av, optdef->enum_labels.size()-1); for (std::vector::iterator it = optdef->enum_labels.begin(); it != optdef->enum_labels.end(); ++it) av_store(av, it - optdef->enum_labels.begin(), newSVpvn_utf8(it->c_str(), it->length(), true)); (void)hv_stores( hv, "labels", newRV_noinc((SV*)av) ); } if (optdef->default_value != NULL) (void)hv_stores( hv, "default", ConfigOption_to_SV(*optdef->default_value, *optdef) ); (void)hv_store( options_hv, opt_key.c_str(), opt_key.length(), newRV_noinc((SV*)hv), 0 ); } RETVAL = newRV_noinc((SV*)options_hv); OUTPUT: RETVAL %} Slic3r-version_1.39.1/xs/xsp/ExPolygon.xsp000066400000000000000000000037521324354444700204560ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/ExPolygon.hpp" %} %name{Slic3r::ExPolygon} class ExPolygon { ~ExPolygon(); Clone clone() %code{% RETVAL = THIS; %}; SV* arrayref() %code{% RETVAL = to_AV(THIS); %}; SV* pp() %code{% RETVAL = to_SV_pureperl(THIS); %}; Ref contour() %code{% RETVAL = &(THIS->contour); %}; Polygons* holes() %code{% RETVAL = &(THIS->holes); %}; void scale(double factor); void translate(double x, double y); double area(); bool is_valid(); bool contains_line(Line* line) %code{% RETVAL = THIS->contains(*line); %}; bool contains_polyline(Polyline* polyline) %code{% RETVAL = THIS->contains(*polyline); %}; bool contains_point(Point* point) %code{% RETVAL = THIS->contains(*point); %}; ExPolygons simplify(double tolerance); Polygons simplify_p(double tolerance); Polylines medial_axis(double max_width, double min_width) %code{% THIS->medial_axis(max_width, min_width, &RETVAL); %}; Polygons get_trapezoids(double angle) %code{% THIS->get_trapezoids(&RETVAL, angle); %}; Polygons get_trapezoids2(double angle) %code{% THIS->get_trapezoids2(&RETVAL, angle); %}; Polygons triangulate() %code{% THIS->triangulate(&RETVAL); %}; Polygons triangulate_pp() %code{% THIS->triangulate_pp(&RETVAL); %}; %{ ExPolygon* ExPolygon::new(...) CODE: RETVAL = new ExPolygon (); // ST(0) is class name, ST(1) is contour and others are holes from_SV_check(ST(1), &RETVAL->contour); RETVAL->holes.resize(items-2); for (unsigned int i = 2; i < items; i++) { from_SV_check(ST(i), &RETVAL->holes[i-2]); } OUTPUT: RETVAL void ExPolygon::rotate(angle, center_sv) double angle; SV* center_sv; CODE: Point center; from_SV_check(center_sv, ¢er); THIS->rotate(angle, center); %} }; Slic3r-version_1.39.1/xs/xsp/ExPolygonCollection.xsp000066400000000000000000000044741324354444700224740ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/ExPolygonCollection.hpp" %} %name{Slic3r::ExPolygon::Collection} class ExPolygonCollection { ~ExPolygonCollection(); Clone clone() %code{% RETVAL = THIS; %}; void clear() %code{% THIS->expolygons.clear(); %}; void scale(double factor); void translate(double x, double y); void rotate(double angle, Point* center) %code{% THIS->rotate(angle, *center); %}; int count() %code{% RETVAL = THIS->expolygons.size(); %}; bool contains_point(Point* point) %code{% RETVAL = THIS->contains(*point); %}; bool contains_line(Line* line) %code{% RETVAL = THIS->contains(*line); %}; bool contains_polyline(Polyline* polyline) %code{% RETVAL = THIS->contains(*polyline); %}; void simplify(double tolerance); Polygons polygons() %code{% RETVAL = *THIS; %}; Clone convex_hull(); %{ ExPolygonCollection* ExPolygonCollection::new(...) CODE: RETVAL = new ExPolygonCollection (); // ST(0) is class name, others are expolygons RETVAL->expolygons.resize(items-1); for (unsigned int i = 1; i < items; i++) { // Note: a COPY of the input is stored from_SV_check(ST(i), &RETVAL->expolygons[i-1]); } OUTPUT: RETVAL SV* ExPolygonCollection::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->expolygons.size()-1); int i = 0; for (ExPolygons::iterator it = THIS->expolygons.begin(); it != THIS->expolygons.end(); ++it) { av_store(av, i++, perl_to_SV_ref(*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL SV* ExPolygonCollection::pp() CODE: AV* av = newAV(); av_fill(av, THIS->expolygons.size()-1); int i = 0; for (ExPolygons::iterator it = THIS->expolygons.begin(); it != THIS->expolygons.end(); ++it) { av_store(av, i++, to_SV_pureperl(&*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL void ExPolygonCollection::append(...) CODE: for (unsigned int i = 1; i < items; i++) { ExPolygon expolygon; from_SV_check(ST(i), &expolygon); THIS->expolygons.push_back(expolygon); } %} }; Slic3r-version_1.39.1/xs/xsp/ExtrusionEntityCollection.xsp000066400000000000000000000077401324354444700237440ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/ExtrusionEntityCollection.hpp" %} %name{Slic3r::ExtrusionPath::Collection} class ExtrusionEntityCollection { %name{_new} ExtrusionEntityCollection(); ~ExtrusionEntityCollection(); Clone clone() %code{% RETVAL = THIS->clone(); %}; void reverse(); void clear(); ExtrusionEntityCollection* chained_path(bool no_reverse, ExtrusionRole role = erMixed) %code{% RETVAL = new ExtrusionEntityCollection(); THIS->chained_path(RETVAL, no_reverse, role); %}; ExtrusionEntityCollection* chained_path_from(Point* start_near, bool no_reverse, ExtrusionRole role = erMixed) %code{% RETVAL = new ExtrusionEntityCollection(); THIS->chained_path_from(*start_near, RETVAL, no_reverse, role); %}; Clone first_point(); Clone last_point(); int count() %code{% RETVAL = THIS->entities.size(); %}; int items_count() %code{% RETVAL = THIS->items_count(); %}; ExtrusionEntityCollection* flatten() %code{% RETVAL = new ExtrusionEntityCollection(); THIS->flatten(RETVAL); %}; double min_mm3_per_mm(); bool empty() %code{% RETVAL = THIS->entities.empty(); %}; std::vector orig_indices() %code{% RETVAL = THIS->orig_indices; %}; Polygons polygons_covered_by_width(); Polygons polygons_covered_by_spacing(); %{ SV* ExtrusionEntityCollection::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->entities.size()-1); int i = 0; for (ExtrusionEntitiesPtr::iterator it = THIS->entities.begin(); it != THIS->entities.end(); ++it) { SV* sv = newSV(0); // return our item by reference if (ExtrusionPath* path = dynamic_cast(*it)) { sv_setref_pv( sv, perl_class_name_ref(path), path ); } else if (ExtrusionMultiPath* multipath = dynamic_cast(*it)) { sv_setref_pv( sv, perl_class_name_ref(multipath), multipath ); } else if (ExtrusionLoop* loop = dynamic_cast(*it)) { sv_setref_pv( sv, perl_class_name_ref(loop), loop ); } else if (ExtrusionEntityCollection* collection = dynamic_cast(*it)) { sv_setref_pv( sv, perl_class_name_ref(collection), collection ); } else { croak("Unexpected type in ExtrusionEntityCollection"); } av_store(av, i++, sv); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL void ExtrusionEntityCollection::append(...) CODE: for (unsigned int i = 1; i < items; i++) { if(!sv_isobject( ST(i) ) || (SvTYPE(SvRV( ST(i) )) != SVt_PVMG)) { croak("Argument %d is not object", i); } ExtrusionEntity* entity = (ExtrusionEntity *)SvIV((SV*)SvRV( ST(i) )); // append COPIES if (ExtrusionPath* path = dynamic_cast(entity)) { THIS->entities.push_back( new ExtrusionPath(*path) ); } else if (ExtrusionMultiPath* multipath = dynamic_cast(entity)) { THIS->entities.push_back( new ExtrusionMultiPath(*multipath) ); } else if (ExtrusionLoop* loop = dynamic_cast(entity)) { THIS->entities.push_back( new ExtrusionLoop(*loop) ); } else if(ExtrusionEntityCollection* collection = dynamic_cast(entity)) { THIS->entities.push_back( collection->clone() ); } else { croak("Argument %d is of unknown type", i); } } bool ExtrusionEntityCollection::no_sort(...) CODE: if (items > 1) { THIS->no_sort = SvTRUE(ST(1)); } RETVAL = THIS->no_sort; OUTPUT: RETVAL %} }; Slic3r-version_1.39.1/xs/xsp/ExtrusionLoop.xsp000066400000000000000000000034121324354444700213550ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/ExtrusionEntity.hpp" %} %name{Slic3r::ExtrusionLoop} class ExtrusionLoop { ExtrusionLoop(); ~ExtrusionLoop(); Clone clone() %code{% RETVAL = THIS; %}; void reverse(); bool make_clockwise(); bool make_counter_clockwise(); Clone first_point(); Clone last_point(); Clone polygon(); void append(ExtrusionPath* path) %code{% THIS->paths.push_back(*path); %}; double length(); bool split_at_vertex(Point* point) %code{% RETVAL = THIS->split_at_vertex(*point); %}; void split_at(Point* point, int prefer_non_overhang = 0) %code{% THIS->split_at(*point, prefer_non_overhang != 0); %}; ExtrusionPaths clip_end(double distance) %code{% THIS->clip_end(distance, &RETVAL); %}; bool has_overhang_point(Point* point) %code{% RETVAL = THIS->has_overhang_point(*point); %}; ExtrusionRole role() const; ExtrusionLoopRole loop_role() const; Polygons polygons_covered_by_width(); Polygons polygons_covered_by_spacing(); %{ SV* ExtrusionLoop::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->paths.size()-1); for (ExtrusionPaths::iterator it = THIS->paths.begin(); it != THIS->paths.end(); ++it) { av_store(av, it - THIS->paths.begin(), perl_to_SV_ref(*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL %} }; %package{Slic3r::ExtrusionLoop}; %{ IV _constant() ALIAS: EXTRL_ROLE_DEFAULT = elrDefault EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER = elrContourInternalPerimeter EXTRL_ROLE_SKIRT = elrSkirt PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} Slic3r-version_1.39.1/xs/xsp/ExtrusionMultiPath.xsp000066400000000000000000000017561324354444700223640ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/ExtrusionEntity.hpp" %} %name{Slic3r::ExtrusionMultiPath} class ExtrusionMultiPath { ExtrusionMultiPath(); ~ExtrusionMultiPath(); Clone clone() %code{% RETVAL = THIS; %}; void reverse(); Clone first_point(); Clone last_point(); void append(ExtrusionPath* path) %code{% THIS->paths.push_back(*path); %}; double length(); Polygons polygons_covered_by_width(); Polygons polygons_covered_by_spacing(); Clone polyline() %code{% RETVAL = THIS->as_polyline(); %}; %{ SV* ExtrusionMultiPath::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->paths.size()-1); for (ExtrusionPaths::iterator it = THIS->paths.begin(); it != THIS->paths.end(); ++it) { av_store(av, it - THIS->paths.begin(), perl_to_SV_ref(*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL %} }; Slic3r-version_1.39.1/xs/xsp/ExtrusionPath.xsp000066400000000000000000000072541324354444700213500ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" %} %name{Slic3r::ExtrusionPath} class ExtrusionPath { ~ExtrusionPath(); SV* arrayref() %code{% RETVAL = to_AV(&THIS->polyline); %}; SV* pp() %code{% RETVAL = to_SV_pureperl(&THIS->polyline); %}; void pop_back() %code{% THIS->polyline.points.pop_back(); %}; void reverse(); Lines lines() %code{% RETVAL = THIS->polyline.lines(); %}; Clone first_point(); Clone last_point(); void clip_end(double distance); void simplify(double tolerance); double length(); ExtrusionRole role() const; bool is_bridge() %code{% RETVAL = is_bridge(THIS->role()); %}; Polygons polygons_covered_by_width(); Polygons polygons_covered_by_spacing(); %{ ExtrusionPath* _new(CLASS, polyline_sv, role, mm3_per_mm, width, height) char* CLASS; SV* polyline_sv; ExtrusionRole role; double mm3_per_mm; float width; float height; CODE: RETVAL = new ExtrusionPath (role); from_SV_check(polyline_sv, &RETVAL->polyline); RETVAL->mm3_per_mm = mm3_per_mm; RETVAL->width = width; RETVAL->height = height; OUTPUT: RETVAL Ref ExtrusionPath::polyline(...) CODE: if (items > 1) { from_SV_check(ST(1), &THIS->polyline); } RETVAL = &(THIS->polyline); OUTPUT: RETVAL double ExtrusionPath::mm3_per_mm(...) CODE: if (items > 1) { THIS->mm3_per_mm = (double)SvNV(ST(1)); } RETVAL = THIS->mm3_per_mm; OUTPUT: RETVAL float ExtrusionPath::width(...) CODE: if (items > 1) { THIS->width = (float)SvNV(ST(1)); } RETVAL = THIS->width; OUTPUT: RETVAL float ExtrusionPath::height(...) CODE: if (items > 1) { THIS->height = (float)SvNV(ST(1)); } RETVAL = THIS->height; OUTPUT: RETVAL void ExtrusionPath::append(...) CODE: for (unsigned int i = 1; i < items; i++) { Point p; from_SV_check(ST(i), &p); THIS->polyline.points.push_back(p); } ExtrusionEntityCollection* ExtrusionPath::intersect_expolygons(ExPolygonCollection* collection) CODE: RETVAL = new ExtrusionEntityCollection (); THIS->intersect_expolygons(*collection, RETVAL); OUTPUT: RETVAL ExtrusionEntityCollection* ExtrusionPath::subtract_expolygons(ExPolygonCollection* collection) CODE: RETVAL = new ExtrusionEntityCollection (); THIS->subtract_expolygons(*collection, RETVAL); OUTPUT: RETVAL %} }; %package{Slic3r::ExtrusionPath}; %{ IV _constant() ALIAS: EXTR_ROLE_NONE = erNone EXTR_ROLE_PERIMETER = erPerimeter EXTR_ROLE_EXTERNAL_PERIMETER = erExternalPerimeter EXTR_ROLE_OVERHANG_PERIMETER = erOverhangPerimeter EXTR_ROLE_FILL = erInternalInfill EXTR_ROLE_SOLIDFILL = erSolidInfill EXTR_ROLE_TOPSOLIDFILL = erTopSolidInfill EXTR_ROLE_BRIDGE = erBridgeInfill EXTR_ROLE_GAPFILL = erGapFill EXTR_ROLE_SKIRT = erSkirt EXTR_ROLE_SUPPORTMATERIAL = erSupportMaterial EXTR_ROLE_SUPPORTMATERIAL_INTERFACE = erSupportMaterialInterface EXTR_ROLE_MIXED = erMixed PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} Slic3r-version_1.39.1/xs/xsp/ExtrusionSimulator.xsp000066400000000000000000000026631324354444700224320ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/ExtrusionSimulator.hpp" %} %name{Slic3r::ExtrusionSimulator} class ExtrusionSimulator { ~ExtrusionSimulator(); %name{_new} ExtrusionSimulator(); Clone clone() %code{% RETVAL = THIS; %}; void set_image_size(Point *image_size) %code{% THIS->set_image_size(*image_size); %}; void set_viewport(BoundingBox *viewport) %code{% THIS->set_viewport(*viewport); %}; void set_bounding_box(BoundingBox *bbox) %code{% THIS->set_bounding_box(*bbox); %}; void reset_accumulator(); void extrude_to_accumulator(ExtrusionPath *path, Point *shift, ExtrusionSimulationType simulationType) %code{% THIS->extrude_to_accumulator(*path, *shift, simulationType); %}; void evaluate_accumulator(ExtrusionSimulationType simulationType); void* image_ptr() %code{% RETVAL = const_cast(const_cast(THIS)->image_ptr()); %}; %{ %} }; %package{Slic3r::ExtrusionSimulator}; %{ IV _constant() ALIAS: EXTRSIM_SIMPLE = ExtrusionSimulationSimple EXTRSIM_DONT_SPREAD = ExtrusionSimulationDontSpread EXTRSIM_SPREAD_NFULL = ExtrisopmSimulationSpreadNotOverfilled EXTRSIM_SPREAD_FULL = ExtrusionSimulationSpreadFull EXTRSIM_SPREAD_EXCESS = ExtrusionSimulationSpreadExcess PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} Slic3r-version_1.39.1/xs/xsp/Filler.xsp000066400000000000000000000044631324354444700177470ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Fill/Fill.hpp" #include "libslic3r/PolylineCollection.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" %} %name{Slic3r::Filler} class Filler { ~Filler(); void set_bounding_box(BoundingBox *bbox) %code{% THIS->fill->set_bounding_box(*bbox); %}; void set_spacing(coordf_t spacing) %code{% THIS->fill->spacing = spacing; %}; coordf_t spacing() %code{% RETVAL = THIS->fill->spacing; %}; void set_layer_id(size_t layer_id) %code{% THIS->fill->layer_id = layer_id; %}; void set_z(coordf_t z) %code{% THIS->fill->z = z; %}; void set_angle(float angle) %code{% THIS->fill->angle = angle; %}; void set_link_max_length(coordf_t len) %code{% THIS->fill->link_max_length = len; %}; void set_loop_clipping(coordf_t clipping) %code{% THIS->fill->loop_clipping = clipping; %}; bool use_bridge_flow() %code{% RETVAL = THIS->fill->use_bridge_flow(); %}; bool no_sort() %code{% RETVAL = THIS->fill->no_sort(); %}; void set_density(float density) %code{% THIS->params.density = density; %}; void set_dont_connect(bool dont_connect) %code{% THIS->params.dont_connect = dont_connect; %}; void set_dont_adjust(bool dont_adjust) %code{% THIS->params.dont_adjust = dont_adjust; %}; void set_complete(bool complete) %code{% THIS->params.complete = complete; %}; PolylineCollection* _fill_surface(Surface *surface) %code{% PolylineCollection *pc = NULL; if (THIS->fill != NULL) { pc = new PolylineCollection(); pc->polylines = THIS->fill->fill_surface(surface, THIS->params); } RETVAL = pc; %}; %{ Filler* new_from_type(CLASS, type) char* CLASS; std::string type; CODE: Filler *filler = new Filler(); filler->fill = Fill::new_from_type(type); RETVAL = filler; OUTPUT: RETVAL void make_fill(CLASS, layer_region, out_append) char* CLASS; LayerRegion* layer_region; ExtrusionEntityCollection* out_append; CODE: make_fill(*layer_region, *out_append); %} }; Slic3r-version_1.39.1/xs/xsp/Flow.xsp000066400000000000000000000036131324354444700174350ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Flow.hpp" %} %name{Slic3r::Flow} class Flow { ~Flow(); %name{_new} Flow(float width, float height, float nozzle_diameter); void set_height(float height) %code{% THIS->height = height; %}; void set_bridge(bool bridge) %code{% THIS->bridge = bridge; %}; Clone clone() %code{% RETVAL = THIS; %}; float width() %code{% RETVAL = THIS->width; %}; float height() %code{% RETVAL = THIS->height; %}; float nozzle_diameter() %code{% RETVAL = THIS->nozzle_diameter; %}; bool bridge() %code{% RETVAL = THIS->bridge; %}; float spacing(); float spacing_to(Flow* other) %code{% RETVAL = THIS->spacing(*other); %}; int scaled_width(); int scaled_spacing(); double mm3_per_mm(); %{ Flow* _new_from_width(CLASS, role, width, nozzle_diameter, height, bridge_flow_ratio) char* CLASS; FlowRole role; std::string width; float nozzle_diameter; float height; float bridge_flow_ratio; CODE: ConfigOptionFloatOrPercent optwidth; optwidth.deserialize(width); RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height, bridge_flow_ratio)); OUTPUT: RETVAL %} }; %package{Slic3r::Flow}; %{ IV _constant() ALIAS: FLOW_ROLE_EXTERNAL_PERIMETER = frExternalPerimeter FLOW_ROLE_PERIMETER = frPerimeter FLOW_ROLE_INFILL = frInfill FLOW_ROLE_SOLID_INFILL = frSolidInfill FLOW_ROLE_TOP_SOLID_INFILL = frTopSolidInfill FLOW_ROLE_SUPPORT_MATERIAL = frSupportMaterial FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE = frSupportMaterialInterface PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} Slic3r-version_1.39.1/xs/xsp/GCode.xsp000066400000000000000000000033041324354444700175040ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/GCode.hpp" #include "libslic3r/GCode/CoolingBuffer.hpp" %} %name{Slic3r::GCode::CoolingBuffer} class CoolingBuffer { CoolingBuffer(GCode* gcode) %code{% RETVAL = new CoolingBuffer(*gcode); %}; ~CoolingBuffer(); Ref gcodegen(); std::string process_layer(std::string gcode, size_t layer_id); }; %name{Slic3r::GCode} class GCode { GCode(); ~GCode(); void do_export(Print *print, const char *path) %code%{ try { THIS->do_export(print, path); } catch (std::exception& e) { croak(e.what()); } %}; Ref origin() %code{% RETVAL = &(THIS->origin()); %}; void set_origin(Pointf* pointf) %code{% THIS->set_origin(*pointf); %}; Ref last_pos() %code{% RETVAL = &(THIS->last_pos()); %}; unsigned int layer_count() const; void set_layer_count(unsigned int value); void set_extruders(std::vector extruders) %code{% THIS->writer().set_extruders(extruders); THIS->writer().set_extruder(0); %}; void apply_print_config(StaticPrintConfig* print_config) %code{% if (const PrintConfig* config = dynamic_cast(print_config)) { THIS->apply_print_config(*config); } else { CONFESS("A PrintConfig object was not supplied to apply_print_config()"); } %}; Ref config() %code{% RETVAL = const_cast(static_cast(static_cast(&THIS->config()))); %}; }; Slic3r-version_1.39.1/xs/xsp/GCodeSender.xsp000066400000000000000000000011301324354444700206400ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/GCodeSender.hpp" %} %name{Slic3r::GCode::Sender} class GCodeSender { GCodeSender(); ~GCodeSender(); bool connect(std::string port, unsigned int baud_rate); void disconnect(); bool is_connected(); bool wait_connected(unsigned int timeout = 3); int queue_size(); void send(std::string s, bool priority = false); void pause_queue(); void resume_queue(); void purge_queue(bool priority = false); std::vector purge_log(); std::string getT(); std::string getB(); }; Slic3r-version_1.39.1/xs/xsp/GUI.xsp000066400000000000000000000021131324354444700171440ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "slic3r/GUI/GUI.hpp" %} %package{Slic3r::GUI}; void disable_screensaver() %code{% Slic3r::GUI::disable_screensaver(); %}; void enable_screensaver() %code{% Slic3r::GUI::enable_screensaver(); %}; std::vector scan_serial_ports() %code{% RETVAL=Slic3r::GUI::scan_serial_ports(); %}; bool debugged() %code{% RETVAL=Slic3r::GUI::debugged(); %}; void break_to_debugger() %code{% Slic3r::GUI::break_to_debugger(); %}; void set_wxapp(SV *ui) %code%{ Slic3r::GUI::set_wxapp((wxApp*)wxPli_sv_2_object(aTHX_ ui, "Wx::App")); %}; void set_main_frame(SV *ui) %code%{ Slic3r::GUI::set_main_frame((wxFrame*)wxPli_sv_2_object(aTHX_ ui, "Wx::Frame")); %}; void set_tab_panel(SV *ui) %code%{ Slic3r::GUI::set_tab_panel((wxNotebook*)wxPli_sv_2_object(aTHX_ ui, "Wx::Notebook")); %}; void add_debug_menu(SV *ui) %code%{ Slic3r::GUI::add_debug_menu((wxMenuBar*)wxPli_sv_2_object(aTHX_ ui, "Wx::MenuBar")); %}; void create_preset_tab(const char *name) %code%{ Slic3r::GUI::create_preset_tab(name); %}; Slic3r-version_1.39.1/xs/xsp/GUI_3DScene.xsp000066400000000000000000000133401324354444700204540ustar00rootroot00000000000000%module{Slic3r::XS}; #include #include "slic3r/GUI/GLShader.hpp" #include "slic3r/GUI/3DScene.hpp" %name{Slic3r::GUI::_3DScene::GLShader} class GLShader { GLShader(); ~GLShader(); bool load(const char *fragment_shader, const char *vertex_shader); void release(); int get_attrib_location(const char *name) const; int get_uniform_location(const char *name) const; bool set_uniform(const char *name, float value) const; void enable() const; void disable() const; std::string last_error() const %code%{ RETVAL = THIS->last_error; %}; }; %name{Slic3r::GUI::_3DScene::GLVolume} class GLVolume { GLVolume(); ~GLVolume(); std::vector color() %code%{ RETVAL.reserve(4); RETVAL.push_back(THIS->color[0]); RETVAL.push_back(THIS->color[1]); RETVAL.push_back(THIS->color[2]); RETVAL.push_back(THIS->color[3]); %}; int select_group_id() %code%{ RETVAL = THIS->select_group_id; %}; int drag_group_id() %code%{ RETVAL = THIS->drag_group_id; %}; int selected() %code%{ RETVAL = THIS->selected; %}; void set_selected(int i) %code%{ THIS->selected = i; %}; int hover() %code%{ RETVAL = THIS->hover; %}; void set_hover(int i) %code%{ THIS->hover = i; %}; int object_idx() const; int volume_idx() const; int instance_idx() const; Clone origin() const %code%{ RETVAL = THIS->origin; %}; void translate(double x, double y, double z) %code%{ THIS->origin.translate(x, y, z); %}; Clone bounding_box() const %code%{ RETVAL = THIS->bounding_box; %}; Clone transformed_bounding_box() const; bool empty() const; bool indexed() const; void render() const; bool has_layer_height_texture(); int layer_height_texture_width(); int layer_height_texture_height(); int layer_height_texture_cells(); void* layer_height_texture_data_ptr_level0(); void* layer_height_texture_data_ptr_level1(); double layer_height_texture_z_to_row_id() const; void generate_layer_height_texture(PrintObject *print_object, bool force); }; %name{Slic3r::GUI::_3DScene::GLVolume::Collection} class GLVolumeCollection { GLVolumeCollection(); ~GLVolumeCollection(); std::vector load_object(ModelObject *object, int obj_idx, std::vector instance_idxs, std::string color_by, std::string select_by, std::string drag_by, bool use_VBOs); int load_wipe_tower_preview(int obj_idx, float pos_x, float pos_y, float width, float depth, float height, bool use_VBOs); void erase() %code{% THIS->clear(); %}; int count() %code{% RETVAL = THIS->volumes.size(); %}; void set_range(double low, double high); void render_VBOs() const; void render_legacy() const; void finalize_geometry(bool use_VBOs); void release_geometry(); bool move_volume_up(int idx) %code%{ if (idx > 0 && idx < int(THIS->volumes.size())) { std::swap(THIS->volumes[idx-1], THIS->volumes[idx]); std::swap(THIS->volumes[idx-1]->composite_id, THIS->volumes[idx]->composite_id); std::swap(THIS->volumes[idx-1]->select_group_id, THIS->volumes[idx]->select_group_id); std::swap(THIS->volumes[idx-1]->drag_group_id, THIS->volumes[idx]->drag_group_id); RETVAL = true; } else RETVAL = false; %}; bool move_volume_down(int idx) %code%{ if (idx >= 0 && idx + 1 < int(THIS->volumes.size())) { std::swap(THIS->volumes[idx+1], THIS->volumes[idx]); std::swap(THIS->volumes[idx+1]->composite_id, THIS->volumes[idx]->composite_id); std::swap(THIS->volumes[idx+1]->select_group_id, THIS->volumes[idx]->select_group_id); std::swap(THIS->volumes[idx+1]->drag_group_id, THIS->volumes[idx]->drag_group_id); RETVAL = true; } else RETVAL = false; %}; %{ SV* GLVolumeCollection::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->volumes.size()-1); int i = 0; for (GLVolume *v : THIS->volumes) { av_store(av, i++, perl_to_SV_ref(*v)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL %} }; %package{Slic3r::GUI::_3DScene}; %{ void _glew_init() CODE: _3DScene::_glew_init(); void _load_print_toolpaths(print, volumes, tool_colors, use_VBOs) Print *print; GLVolumeCollection *volumes; std::vector tool_colors; int use_VBOs; CODE: _3DScene::_load_print_toolpaths(print, volumes, tool_colors, use_VBOs != 0); void _load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs) PrintObject *print_object; GLVolumeCollection *volumes; std::vector tool_colors; int use_VBOs; CODE: _3DScene::_load_print_object_toolpaths(print_object, volumes, tool_colors, use_VBOs != 0); void _load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs) Print *print; GLVolumeCollection *volumes; std::vector tool_colors; int use_VBOs; CODE: _3DScene::_load_wipe_tower_toolpaths(print, volumes, tool_colors, use_VBOs != 0); %} Slic3r-version_1.39.1/xs/xsp/GUI_AppConfig.xsp000066400000000000000000000023641324354444700211020ustar00rootroot00000000000000 %module{Slic3r::XS}; %{ #include #include "slic3r/GUI/AppConfig.hpp" %} %name{Slic3r::GUI::AppConfig} class AppConfig { AppConfig(); ~AppConfig(); void reset(); void set_defaults(); void load() %code%{ try { THIS->load(); } catch (std::exception& e) { croak("Loading an application config file failed:\n%s\n", e.what()); } %}; void save() %code%{ try { THIS->save(); } catch (std::exception& e) { croak("Saving an application config file failed:\n%s\n", e.what()); } %}; bool exists(); bool dirty(); std::string get(char *name); void set(char *name, char *value); bool has(char *section); std::string get_last_dir(); void update_config_dir(char *dir); void update_skein_dir(char *dir); std::string get_last_output_dir(const char *alt = ""); void update_last_output_dir(char *dir); }; Slic3r-version_1.39.1/xs/xsp/GUI_Preset.xsp000066400000000000000000000177431324354444700205050ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "slic3r/GUI/Preset.hpp" #include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/PresetHints.hpp" %} %name{Slic3r::GUI::Preset} class Preset { // owned by PresetCollection, no constructor/destructor bool default() %code%{ RETVAL = THIS->is_default; %}; bool external() %code%{ RETVAL = THIS->is_external; %}; bool visible() %code%{ RETVAL = THIS->is_visible; %}; bool dirty() %code%{ RETVAL = THIS->is_dirty; %}; bool compatible() %code%{ RETVAL = THIS->is_compatible; %}; bool is_compatible_with_printer(Preset *active_printer) %code%{ RETVAL = THIS->is_compatible_with_printer(*active_printer); %}; std::string name() %code%{ RETVAL = THIS->name; %}; std::string file() %code%{ RETVAL = THIS->file; %}; bool loaded() %code%{ RETVAL = THIS->loaded; %}; Ref config() %code%{ RETVAL = &THIS->config; %}; void set_num_extruders(int num_extruders); }; %name{Slic3r::GUI::PresetCollection} class PresetCollection { Ref preset(size_t idx) %code%{ RETVAL = &THIS->preset(idx); %}; Ref default_preset() %code%{ RETVAL = &THIS->default_preset(); %}; size_t size() const; size_t num_visible() const; std::string name() const; Ref get_selected_preset() %code%{ RETVAL = &THIS->get_selected_preset(); %}; Ref get_current_preset() %code%{ RETVAL = &THIS->get_edited_preset(); %}; std::string get_current_preset_name() %code%{ RETVAL = THIS->get_selected_preset().name; %}; Ref get_edited_preset() %code%{ RETVAL = &THIS->get_edited_preset(); %}; Ref find_preset(char *name, bool first_visible_if_not_found = false) %code%{ RETVAL = THIS->find_preset(name, first_visible_if_not_found); %}; bool current_is_dirty(); std::vector current_dirty_options(); void update_tab_ui(SV *ui, bool show_incompatible) %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" ); THIS->update_tab_ui(cb, show_incompatible); %}; void update_platter_ui(SV *ui) %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object( aTHX_ ui, "Wx::BitmapComboBox" ); THIS->update_platter_ui(cb); %}; bool update_dirty_ui(SV *ui) %code%{ RETVAL = THIS->update_dirty_ui((wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox")); %}; void select_preset(int idx); bool select_preset_by_name(char *name) %code%{ RETVAL = THIS->select_preset_by_name(name, true); %}; void discard_current_changes(); void save_current_preset(char *new_name) %code%{ try { THIS->save_current_preset(new_name); } catch (std::exception& e) { croak("Error saving a preset %s:\n%s\n", new_name, e.what()); } %}; void delete_current_preset() %code%{ try { THIS->delete_current_preset(); } catch (std::exception& e) { croak("Error deleting a preset file %s:\n%s\n", THIS->get_selected_preset().file.c_str(), e.what()); } %}; %{ SV* PresetCollection::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->size()-1); for (int i = 0; i < int(THIS->size()); ++ i) { Preset &preset = THIS->preset(i); av_store(av, i, perl_to_SV_ref(preset)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL %} }; %name{Slic3r::GUI::PresetBundle} class PresetBundle { PresetBundle(); ~PresetBundle(); void reset(bool delete_files); void setup_directories() %code%{ try { THIS->setup_directories(); } catch (std::exception& e) { croak("Cannot create configuration directories:\n%s\n", e.what()); } %}; void load_presets() %code%{ try { THIS->load_presets(); } catch (std::exception& e) { croak("Loading of Slic3r presets from %s failed.\n\n%s\n", Slic3r::data_dir().c_str(), e.what()); } %}; void load_config(const char *name, DynamicPrintConfig *config) %code%{ try { THIS->load_config(name, *config); } catch (std::exception& e) { croak("Loading a configuration %s failed:\n%s\n", name, e.what()); } %}; void load_config_file(const char *path) %code%{ try { THIS->load_config_file(path); } catch (std::exception& e) { croak("Loading a configuration file %s failed:\n%s\n", path, e.what()); } %}; size_t load_configbundle(const char *path) %code%{ try { RETVAL = THIS->load_configbundle(path, PresetBundle::LOAD_CFGBNDLE_SAVE); } catch (std::exception& e) { croak("Loading of a config bundle %s failed:\n%s\n", path, e.what()); } %}; void export_configbundle(char *path) %code%{ try { THIS->export_configbundle(path); } catch (std::exception& e) { croak("Export of a config bundle %s failed:\n%s\n", path, e.what()); } %}; void set_default_suppressed(bool default_suppressed); void load_selections (AppConfig *config) %code%{ THIS->load_selections(*config); %}; void export_selections(AppConfig *config) %code%{ THIS->export_selections(*config); %}; void export_selections_pp(PlaceholderParser *pp) %code%{ THIS->export_selections(*pp); %}; Ref print() %code%{ RETVAL = &THIS->prints; %}; Ref filament() %code%{ RETVAL = &THIS->filaments; %}; Ref printer() %code%{ RETVAL = &THIS->printers; %}; bool has_defauls_only(); std::vector filament_presets() %code%{ RETVAL = THIS->filament_presets; %}; void set_filament_preset(int idx, const char *name); void update_multi_material_filament_presets(); void update_compatible_with_printer(bool select_other_if_incompatible); Clone full_config() %code%{ RETVAL = THIS->full_config(); %}; void update_platter_filament_ui(int extruder_idx, SV *ui) %code%{ auto cb = (wxBitmapComboBox*)wxPli_sv_2_object(aTHX_ ui, "Wx::BitmapComboBox"); THIS->update_platter_filament_ui(extruder_idx, cb); %}; }; %name{Slic3r::GUI::PresetHints} class PresetHints { PresetHints(); ~PresetHints(); static std::string cooling_description(Preset *preset) %code%{ RETVAL = PresetHints::cooling_description(*preset); %}; static std::string maximum_volumetric_flow_description(PresetBundle *preset) %code%{ RETVAL = PresetHints::maximum_volumetric_flow_description(*preset); %}; static std::string recommended_thin_wall_thickness(PresetBundle *preset) %code%{ RETVAL = PresetHints::recommended_thin_wall_thickness(*preset); %}; }; Slic3r-version_1.39.1/xs/xsp/Geometry.xsp000066400000000000000000000042301324354444700203150ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Geometry.hpp" %} %package{Slic3r::Geometry}; Pointfs arrange(size_t total_parts, Pointf* part, coordf_t dist, BoundingBoxf* bb = NULL) %code{% Pointfs points; if (! Slic3r::Geometry::arrange(total_parts, *part, dist, bb, points)) CONFESS(PRINTF_ZU " parts won't fit in your print area!\n", total_parts); RETVAL = points; %}; %{ bool directions_parallel(angle1, angle2) double angle1 double angle2 CODE: RETVAL = Slic3r::Geometry::directions_parallel(angle1, angle2); OUTPUT: RETVAL bool directions_parallel_within(angle1, angle2, max_diff) double angle1 double angle2 double max_diff CODE: RETVAL = Slic3r::Geometry::directions_parallel(angle1, angle2, max_diff); OUTPUT: RETVAL Clone convex_hull(points) Points points CODE: RETVAL = Slic3r::Geometry::convex_hull(points); OUTPUT: RETVAL std::vector chained_path(points) Points points CODE: Slic3r::Geometry::chained_path(points, RETVAL); OUTPUT: RETVAL std::vector chained_path_from(points, start_from) Points points Point* start_from CODE: Slic3r::Geometry::chained_path(points, RETVAL, *start_from); OUTPUT: RETVAL double rad2deg(angle) double angle CODE: RETVAL = Slic3r::Geometry::rad2deg(angle); OUTPUT: RETVAL double rad2deg_dir(angle) double angle CODE: RETVAL = Slic3r::Geometry::rad2deg_dir(angle); OUTPUT: RETVAL double deg2rad(angle) double angle CODE: RETVAL = Slic3r::Geometry::deg2rad(angle); OUTPUT: RETVAL Polygons simplify_polygons(polygons, tolerance) Polygons polygons double tolerance CODE: Slic3r::Geometry::simplify_polygons(polygons, tolerance, &RETVAL); OUTPUT: RETVAL IV _constant() ALIAS: X = X Y = Y Z = Z PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} Slic3r-version_1.39.1/xs/xsp/Layer.xsp000066400000000000000000000101631324354444700176000ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Layer.hpp" %} %name{Slic3r::Layer::Region} class LayerRegion { // owned by Layer, no constructor/destructor Ref layer(); Ref region(); Ref slices() %code%{ RETVAL = &THIS->slices; %}; Ref thin_fills() %code%{ RETVAL = &THIS->thin_fills; %}; Ref fill_surfaces() %code%{ RETVAL = &THIS->fill_surfaces; %}; Ref perimeter_surfaces() %code%{ RETVAL = &THIS->perimeter_surfaces; %}; Polygons bridged() %code%{ RETVAL = THIS->bridged; %}; Ref unsupported_bridge_edges() %code%{ RETVAL = &THIS->unsupported_bridge_edges; %}; Ref perimeters() %code%{ RETVAL = &THIS->perimeters; %}; Ref fills() %code%{ RETVAL = &THIS->fills; %}; Clone flow(FlowRole role, bool bridge = false, double width = -1) %code%{ RETVAL = THIS->flow(role, bridge, width); %}; void prepare_fill_surfaces(); void make_perimeters(SurfaceCollection* slices, SurfaceCollection* fill_surfaces) %code%{ THIS->make_perimeters(*slices, fill_surfaces); %}; double infill_area_threshold(); void export_region_slices_to_svg(const char *path) const; void export_region_fill_surfaces_to_svg(const char *path) const; void export_region_slices_to_svg_debug(const char *name) const; void export_region_fill_surfaces_to_svg_debug(const char *name) const; }; %name{Slic3r::Layer} class Layer { // owned by PrintObject, no constructor/destructor Ref as_layer() %code%{ RETVAL = THIS; %}; int id(); void set_id(int id); Ref object(); bool slicing_errors() %code%{ RETVAL = THIS->slicing_errors; %}; coordf_t slice_z() %code%{ RETVAL = THIS->slice_z; %}; coordf_t print_z() %code%{ RETVAL = THIS->print_z; %}; coordf_t height() %code%{ RETVAL = THIS->height; %}; size_t region_count(); Ref get_region(int idx); Ref add_region(PrintRegion* print_region); Ref slices() %code%{ RETVAL = &THIS->slices; %}; int ptr() %code%{ RETVAL = (int)(intptr_t)THIS; %}; Ref as_support_layer() %code%{ RETVAL = dynamic_cast(THIS); %}; void make_slices(); void merge_slices(); void make_perimeters(); void make_fills(); void export_region_slices_to_svg(const char *path); void export_region_fill_surfaces_to_svg(const char *path); void export_region_slices_to_svg_debug(const char *name); void export_region_fill_surfaces_to_svg_debug(const char *name); }; %name{Slic3r::Layer::Support} class SupportLayer { // owned by PrintObject, no constructor/destructor Ref as_layer() %code%{ RETVAL = THIS; %}; Ref support_islands() %code%{ RETVAL = &THIS->support_islands; %}; Ref support_fills() %code%{ RETVAL = &THIS->support_fills; %}; // copies of some Layer methods, because the parameter wrapper code // gets confused about getting a Layer::Support instead of a Layer int id(); void set_id(int id); Ref object(); bool slicing_errors() %code%{ RETVAL = THIS->slicing_errors; %}; coordf_t slice_z() %code%{ RETVAL = THIS->slice_z; %}; coordf_t print_z() %code%{ RETVAL = THIS->print_z; %}; coordf_t height() %code%{ RETVAL = THIS->height; %}; size_t region_count(); Ref get_region(int idx); Ref add_region(PrintRegion* print_region); Ref slices() %code%{ RETVAL = &THIS->slices; %}; void export_region_slices_to_svg(const char *path); void export_region_fill_surfaces_to_svg(const char *path); void export_region_slices_to_svg_debug(const char *name); void export_region_fill_surfaces_to_svg_debug(const char *name); }; Slic3r-version_1.39.1/xs/xsp/Line.xsp000066400000000000000000000044261324354444700174200ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Line.hpp" #include "libslic3r/Polyline.hpp" %} %name{Slic3r::Line} class Line { ~Line(); Clone clone() %code{% RETVAL = THIS; %}; SV* arrayref() %code{% RETVAL = to_AV(THIS); %}; SV* pp() %code{% RETVAL = to_SV_pureperl(THIS); %}; Ref a() %code{% RETVAL=&THIS->a; %}; Ref b() %code{% RETVAL=&THIS->b; %}; void reverse(); void scale(double factor); void translate(double x, double y); double length(); double atan2_(); double orientation(); double direction(); bool parallel_to(double angle); bool parallel_to_line(Line* line) %code{% RETVAL = THIS->parallel_to(*line); %}; Clone midpoint(); Clone point_at(double distance); Clone intersection_infinite(Line* other) %code{% Point p; bool res = THIS->intersection_infinite(*other, &p); if (!res) CONFESS("Intersection failed"); RETVAL = p; %}; Clone as_polyline() %code{% RETVAL = Polyline(*THIS); %}; Clone normal(); Clone vector(); double ccw(Point* point) %code{% RETVAL = THIS->ccw(*point); %}; %{ Line* Line::new(...) CODE: RETVAL = new Line (); // ST(0) is class name, ST(1) and ST(2) are endpoints from_SV_check(ST(1), &RETVAL->a); from_SV_check(ST(2), &RETVAL->b); OUTPUT: RETVAL void Line::rotate(angle, center_sv) double angle; SV* center_sv; CODE: Point center; from_SV_check(center_sv, ¢er); THIS->rotate(angle, center); bool Line::coincides_with(line_sv) SV* line_sv; CODE: Line line; from_SV_check(line_sv, &line); RETVAL = THIS->coincides_with(line); OUTPUT: RETVAL %} }; %name{Slic3r::Linef3} class Linef3 { Linef3(Pointf3* a, Pointf3* b) %code{% RETVAL = new Linef3(*a, *b); %}; ~Linef3(); Clone clone() %code{% RETVAL = THIS; %}; Ref a() %code{% RETVAL = &THIS->a; %}; Ref b() %code{% RETVAL = &THIS->b; %}; Clone intersect_plane(double z); void scale(double factor); }; Slic3r-version_1.39.1/xs/xsp/Model.xsp000066400000000000000000000247641324354444700176000ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Model.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/Slicing.hpp" #include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/OBJ.hpp" #include "libslic3r/Format/PRUS.hpp" #include "libslic3r/Format/STL.hpp" %} %name{Slic3r::Model} class Model { Model(); ~Model(); %name{read_from_file} Model(std::string input_file, bool add_default_instances = true) %code%{ try { RETVAL = new Model(Model::read_from_file(input_file, add_default_instances)); } catch (std::exception& e) { croak("Error while opening %s: %s\n", input_file.c_str(), e.what()); } %}; Clone clone() %code%{ RETVAL = THIS; %}; %name{_add_object} Ref add_object(); Ref _add_object_clone(ModelObject* other, bool copy_volumes = true) %code%{ RETVAL = THIS->add_object(*other, copy_volumes); %}; void delete_object(size_t idx); void clear_objects(); size_t objects_count() %code%{ RETVAL = THIS->objects.size(); %}; Ref get_object(int idx) %code%{ RETVAL = THIS->objects.at(idx); %}; Ref get_material(t_model_material_id material_id) %code%{ RETVAL = THIS->get_material(material_id); if (RETVAL == NULL) { XSRETURN_UNDEF; } %}; %name{add_material} Ref add_material(t_model_material_id material_id); Ref add_material_clone(t_model_material_id material_id, ModelMaterial* other) %code%{ RETVAL = THIS->add_material(material_id, *other); %}; bool has_material(t_model_material_id material_id) const %code%{ RETVAL = (THIS->get_material(material_id) != NULL); %}; void delete_material(t_model_material_id material_id); void clear_materials(); std::vector material_names() const %code%{ for (ModelMaterialMap::iterator i = THIS->materials.begin(); i != THIS->materials.end(); ++i) { RETVAL.push_back(i->first); } %}; size_t material_count() const %code%{ RETVAL = THIS->materials.size(); %}; bool add_default_instances(); Clone bounding_box(); void center_instances_around_point(Pointf* point) %code%{ THIS->center_instances_around_point(*point); %}; void translate(double x, double y, double z); Clone mesh(); ModelObjectPtrs* objects() %code%{ RETVAL = &THIS->objects; %}; bool arrange_objects(double dist, BoundingBoxf* bb = NULL); void duplicate(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); void duplicate_objects_grid(unsigned int x, unsigned int y, double dist); bool looks_like_multipart_object() const; void convert_multipart_object(); void print_info() const; bool store_stl(char *path, bool binary) %code%{ TriangleMesh mesh = THIS->mesh(); RETVAL = Slic3r::store_stl(path, &mesh, binary); %}; bool store_amf(char *path) %code%{ RETVAL = Slic3r::store_amf(path, THIS); %}; %{ Model* load_stl(CLASS, path, object_name) char* CLASS; char* path; char* object_name; CODE: RETVAL = new Model(); if (! load_stl(path, RETVAL, object_name)) { delete RETVAL; RETVAL = NULL; } OUTPUT: RETVAL Model* load_obj(CLASS, path, object_name) char* CLASS; char* path; char* object_name; CODE: RETVAL = new Model(); if (! load_obj(path, RETVAL, object_name)) { delete RETVAL; RETVAL = NULL; } OUTPUT: RETVAL Model* load_amf(CLASS, path) char* CLASS; char* path; CODE: RETVAL = new Model(); if (! load_amf(path, RETVAL)) { delete RETVAL; RETVAL = NULL; } OUTPUT: RETVAL Model* load_prus(CLASS, path) char* CLASS; char* path; CODE: #ifdef SLIC3R_PRUS RETVAL = new Model(); if (! load_prus(path, RETVAL)) { delete RETVAL; RETVAL = NULL; } #else RETVAL = nullptr; #endif OUTPUT: RETVAL %} }; %name{Slic3r::Model::Material} class ModelMaterial { Ref model() %code%{ RETVAL = THIS->get_model(); %}; Ref config() %code%{ RETVAL = &THIS->config; %}; std::string get_attribute(std::string name) %code%{ if (THIS->attributes.find(name) != THIS->attributes.end()) RETVAL = THIS->attributes[name]; %}; void set_attribute(std::string name, std::string value) %code%{ THIS->attributes[name] = value; %}; %{ SV* ModelMaterial::attributes() CODE: HV* hv = newHV(); for (t_model_material_attributes::const_iterator attr = THIS->attributes.begin(); attr != THIS->attributes.end(); ++attr) { (void)hv_store( hv, attr->first.c_str(), attr->first.length(), newSVpv(attr->second.c_str(), attr->second.length()), 0 ); } RETVAL = (SV*)newRV_noinc((SV*)hv); OUTPUT: RETVAL %} }; %name{Slic3r::Model::Object} class ModelObject { ModelVolumePtrs* volumes() %code%{ RETVAL = &THIS->volumes; %}; ModelInstancePtrs* instances() %code%{ RETVAL = &THIS->instances; %}; void invalidate_bounding_box(); Clone mesh(); Clone raw_mesh(); Clone instance_bounding_box(int idx) %code%{ RETVAL = THIS->instance_bounding_box(idx, true); %}; Clone bounding_box(); %name{_add_volume} Ref add_volume(TriangleMesh* mesh) %code%{ RETVAL = THIS->add_volume(*mesh); %}; Ref _add_volume_clone(ModelVolume* other) %code%{ RETVAL = THIS->add_volume(*other); %}; void delete_volume(size_t idx); void clear_volumes(); int volumes_count() %code%{ RETVAL = THIS->volumes.size(); %}; Ref get_volume(int idx) %code%{ RETVAL = THIS->volumes.at(idx); %}; bool move_volume_up(int idx) %code%{ if (idx > 0 && idx < int(THIS->volumes.size())) { std::swap(THIS->volumes[idx-1], THIS->volumes[idx]); RETVAL = true; } else RETVAL = false; %}; bool move_volume_down(int idx) %code%{ if (idx >= 0 && idx + 1 < int(THIS->volumes.size())) { std::swap(THIS->volumes[idx+1], THIS->volumes[idx]); RETVAL = true; } else RETVAL = false; %}; %name{_add_instance} Ref add_instance(); Ref _add_instance_clone(ModelInstance* other) %code%{ RETVAL = THIS->add_instance(*other); %}; void delete_last_instance(); void clear_instances(); int instances_count() %code%{ RETVAL = THIS->instances.size(); %}; std::string name() %code%{ RETVAL = THIS->name; %}; void set_name(std::string value) %code%{ THIS->name = value; %}; std::string input_file() %code%{ RETVAL = THIS->input_file; %}; void set_input_file(std::string value) %code%{ THIS->input_file = value; %}; Ref config() %code%{ RETVAL = &THIS->config; %}; Ref model() %code%{ RETVAL = THIS->get_model(); %}; t_layer_height_ranges layer_height_ranges() %code%{ RETVAL = THIS->layer_height_ranges; %}; void set_layer_height_ranges(t_layer_height_ranges ranges) %code%{ THIS->layer_height_ranges = ranges; %}; std::vector layer_height_profile() %code%{ RETVAL = THIS->layer_height_profile_valid ? THIS->layer_height_profile : std::vector(); %}; void set_layer_height_profile(std::vector profile) %code%{ THIS->layer_height_profile = profile; THIS->layer_height_profile_valid = true; %}; Ref origin_translation() %code%{ RETVAL = &THIS->origin_translation; %}; void set_origin_translation(Pointf3* point) %code%{ THIS->origin_translation = *point; %}; bool needed_repair() const; int materials_count() const; int facets_count(); void center_around_origin(); void translate(double x, double y, double z); void scale_xyz(Pointf3* versor) %code{% THIS->scale(*versor); %}; void rotate(float angle, Axis axis); void mirror(Axis axis); Model* cut(double z) %code%{ RETVAL = new Model(); THIS->cut(z, RETVAL); %}; ModelObjectPtrs* split_object() %code%{ RETVAL = new ModelObjectPtrs(); // leak? THIS->split(RETVAL); %}; void print_info() const; }; %name{Slic3r::Model::Volume} class ModelVolume { Ref object() %code%{ RETVAL = THIS->get_object(); %}; std::string name() %code%{ RETVAL = THIS->name; %}; void set_name(std::string value) %code%{ THIS->name = value; %}; t_model_material_id material_id(); void set_material_id(t_model_material_id material_id) %code%{ THIS->material_id(material_id); %}; Ref material(); Ref config() %code%{ RETVAL = &THIS->config; %}; Ref mesh() %code%{ RETVAL = &THIS->mesh; %}; bool modifier() %code%{ RETVAL = THIS->modifier; %}; void set_modifier(bool modifier) %code%{ THIS->modifier = modifier; %}; size_t split(); ModelMaterial* assign_unique_material(); }; %name{Slic3r::Model::Instance} class ModelInstance { Ref object() %code%{ RETVAL = THIS->get_object(); %}; double rotation() %code%{ RETVAL = THIS->rotation; %}; double scaling_factor() %code%{ RETVAL = THIS->scaling_factor; %}; Ref offset() %code%{ RETVAL = &THIS->offset; %}; void set_rotation(double val) %code%{ THIS->rotation = val; %}; void set_scaling_factor(double val) %code%{ THIS->scaling_factor = val; %}; void set_offset(Pointf *offset) %code%{ THIS->offset = *offset; %}; void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; void transform_polygon(Polygon* polygon) const; }; Slic3r-version_1.39.1/xs/xsp/MotionPlanner.xsp000066400000000000000000000005411324354444700213100ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/MotionPlanner.hpp" %} %name{Slic3r::MotionPlanner} class MotionPlanner { MotionPlanner(ExPolygons islands); ~MotionPlanner(); int islands_count(); Clone shortest_path(Point* from, Point* to) %code%{ RETVAL = THIS->shortest_path(*from, *to); %}; }; Slic3r-version_1.39.1/xs/xsp/PerimeterGenerator.xsp000066400000000000000000000027361324354444700223360ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/PerimeterGenerator.hpp" %} %name{Slic3r::Layer::PerimeterGenerator} class PerimeterGenerator { PerimeterGenerator(SurfaceCollection* slices, double layer_height, Flow* flow, StaticPrintConfig* region_config, StaticPrintConfig* object_config, StaticPrintConfig* print_config, ExtrusionEntityCollection* loops, ExtrusionEntityCollection* gap_fill, SurfaceCollection* fill_surfaces) %code{% RETVAL = new PerimeterGenerator(slices, layer_height, *flow, dynamic_cast(region_config), dynamic_cast(object_config), dynamic_cast(print_config), loops, gap_fill, fill_surfaces); %}; ~PerimeterGenerator(); void set_lower_slices(ExPolygonCollection* lower_slices) %code{% THIS->lower_slices = lower_slices; %}; void set_layer_id(int layer_id) %code{% THIS->layer_id = layer_id; %}; void set_perimeter_flow(Flow* flow) %code{% THIS->perimeter_flow = *flow; %}; void set_ext_perimeter_flow(Flow* flow) %code{% THIS->ext_perimeter_flow = *flow; %}; void set_overhang_flow(Flow* flow) %code{% THIS->overhang_flow = *flow; %}; void set_solid_infill_flow(Flow* flow) %code{% THIS->solid_infill_flow = *flow; %}; Ref config() %code{% RETVAL = THIS->config; %}; void process(); }; Slic3r-version_1.39.1/xs/xsp/PlaceholderParser.xsp000066400000000000000000000015731324354444700221300ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include #include "libslic3r/PlaceholderParser.hpp" %} %name{Slic3r::GCode::PlaceholderParser} class PlaceholderParser { PlaceholderParser(); ~PlaceholderParser(); void apply_config(DynamicPrintConfig *config) %code%{ THIS->apply_config(*config); %}; void set(std::string key, int value); std::string process(std::string str) const %code%{ try { RETVAL = THIS->process(str, 0); } catch (std::exception& e) { croak(e.what()); } %}; bool evaluate_boolean_expression(const char *str) const %code%{ try { RETVAL = THIS->evaluate_boolean_expression(str, THIS->config()); } catch (std::exception& e) { croak(e.what()); } %}; }; Slic3r-version_1.39.1/xs/xsp/Point.xsp000066400000000000000000000114241324354444700176160ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Point.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Polyline.hpp" %} %name{Slic3r::Point} class Point { Point(int _x = 0, int _y = 0); ~Point(); Clone clone() %code{% RETVAL=THIS; %}; void scale(double factor); void translate(double x, double y); SV* arrayref() %code{% RETVAL = to_SV_pureperl(THIS); %}; SV* pp() %code{% RETVAL = to_SV_pureperl(THIS); %}; int x() %code{% RETVAL = THIS->x; %}; int y() %code{% RETVAL = THIS->y; %}; void set_x(int val) %code{% THIS->x = val; %}; void set_y(int val) %code{% THIS->y = val; %}; int nearest_point_index(Points points); Clone nearest_point(Points points) %code{% Point p; THIS->nearest_point(points, &p); RETVAL = p; %}; double distance_to(Point* point) %code{% RETVAL = THIS->distance_to(*point); %}; double distance_to_line(Line* line) %code{% RETVAL = THIS->distance_to(*line); %}; double perp_distance_to_line(Line* line) %code{% RETVAL = THIS->perp_distance_to(*line); %}; double ccw(Point* p1, Point* p2) %code{% RETVAL = THIS->ccw(*p1, *p2); %}; double ccw_angle(Point* p1, Point* p2) %code{% RETVAL = THIS->ccw_angle(*p1, *p2); %}; Clone projection_onto_polygon(Polygon* polygon) %code{% RETVAL = new Point(THIS->projection_onto(*polygon)); %}; Clone projection_onto_polyline(Polyline* polyline) %code{% RETVAL = new Point(THIS->projection_onto(*polyline)); %}; Clone projection_onto_line(Line* line) %code{% RETVAL = new Point(THIS->projection_onto(*line)); %}; Clone negative() %code{% RETVAL = new Point(THIS->negative()); %}; bool coincides_with_epsilon(Point* point) %code{% RETVAL = THIS->coincides_with_epsilon(*point); %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld", THIS->x, THIS->y); RETVAL = buf; %}; %{ void Point::rotate(angle, center_sv) double angle; SV* center_sv; CODE: Point center; from_SV_check(center_sv, ¢er); THIS->rotate(angle, center); bool Point::coincides_with(point_sv) SV* point_sv; CODE: Point point; from_SV_check(point_sv, &point); RETVAL = THIS->coincides_with(point); OUTPUT: RETVAL %} }; %name{Slic3r::Point3} class Point3 { Point3(int _x = 0, int _y = 0, int _z = 0); ~Point3(); Clone clone() %code{% RETVAL = THIS; %}; int x() %code{% RETVAL = THIS->x; %}; int y() %code{% RETVAL = THIS->y; %}; int z() %code{% RETVAL = THIS->z; %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld,%ld", THIS->x, THIS->y, THIS->z); RETVAL = buf; %}; }; %name{Slic3r::Pointf} class Pointf { Pointf(double _x = 0, double _y = 0); ~Pointf(); Clone clone() %code{% RETVAL = THIS; %}; SV* arrayref() %code{% RETVAL = to_SV_pureperl(THIS); %}; SV* pp() %code{% RETVAL = to_SV_pureperl(THIS); %}; double x() %code{% RETVAL = THIS->x; %}; double y() %code{% RETVAL = THIS->y; %}; void set_x(double val) %code{% THIS->x = val; %}; void set_y(double val) %code{% THIS->y = val; %}; void translate(double x, double y); void scale(double factor); void rotate(double angle, Pointf* center) %code{% THIS->rotate(angle, *center); %}; Clone negative() %code{% RETVAL = THIS->negative(); %}; Clone vector_to(Pointf* point) %code{% RETVAL = THIS->vector_to(*point); %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf", THIS->x, THIS->y); RETVAL = buf; %}; }; %name{Slic3r::Pointf3} class Pointf3 { Pointf3(double _x = 0, double _y = 0, double _z = 0); ~Pointf3(); Clone clone() %code{% RETVAL = THIS; %}; double x() %code{% RETVAL = THIS->x; %}; double y() %code{% RETVAL = THIS->y; %}; double z() %code{% RETVAL = THIS->z; %}; void set_x(double val) %code{% THIS->x = val; %}; void set_y(double val) %code{% THIS->y = val; %}; void set_z(double val) %code{% THIS->z = val; %}; void translate(double x, double y, double z); void scale(double factor); double distance_to(Pointf3* point) %code{% RETVAL = THIS->distance_to(*point); %}; Clone negative() %code{% RETVAL = THIS->negative(); %}; Clone vector_to(Pointf3* point) %code{% RETVAL = THIS->vector_to(*point); %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf,%lf", THIS->x, THIS->y, THIS->z); RETVAL = buf; %}; }; Slic3r-version_1.39.1/xs/xsp/Polygon.xsp000066400000000000000000000044271324354444700201610ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/BoundingBox.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/BoundingBox.hpp" %} %name{Slic3r::Polygon} class Polygon { ~Polygon(); Clone clone() %code{% RETVAL = THIS; %}; SV* arrayref() %code{% RETVAL = to_AV(THIS); %}; SV* pp() %code{% RETVAL = to_SV_pureperl(THIS); %}; void scale(double factor); void translate(double x, double y); void reverse(); Lines lines(); Clone split_at_vertex(Point* point) %code{% RETVAL = THIS->split_at_vertex(*point); %}; Clone split_at_index(int index); Clone split_at_first_point(); Points equally_spaced_points(double distance); double length(); double area(); bool is_counter_clockwise(); bool is_clockwise(); bool make_counter_clockwise(); bool make_clockwise(); bool is_valid(); Clone first_point(); bool contains_point(Point* point) %code{% RETVAL = THIS->contains(*point); %}; Polygons simplify(double tolerance); Polygons triangulate_convex() %code{% THIS->triangulate_convex(&RETVAL); %}; Clone centroid(); Clone bounding_box(); std::string wkt(); Points concave_points(double angle); Points convex_points(double angle); Clone point_projection(Point* point) %code{% RETVAL = THIS->point_projection(*point); %}; Clone intersection(Line* line) %code{% Point p; (void)THIS->intersection(*line, &p); RETVAL = p; %}; Clone first_intersection(Line* line) %code{% Point p; (void)THIS->first_intersection(*line, &p); RETVAL = p; %}; %{ Polygon* Polygon::new(...) CODE: RETVAL = new Polygon (); // ST(0) is class name, ST(1) is first point RETVAL->points.resize(items-1); for (unsigned int i = 1; i < items; i++) { from_SV_check(ST(i), &RETVAL->points[i-1]); } OUTPUT: RETVAL void Polygon::rotate(angle, center_sv) double angle; SV* center_sv; CODE: Point center; from_SV_check(center_sv, ¢er); THIS->rotate(angle, center); %} }; Slic3r-version_1.39.1/xs/xsp/Polyline.xsp000066400000000000000000000047161324354444700203260ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/BoundingBox.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Polyline.hpp" %} %name{Slic3r::Polyline} class Polyline { ~Polyline(); Clone clone() %code{% RETVAL = THIS; %}; SV* arrayref() %code{% RETVAL = to_AV(THIS); %}; SV* pp() %code{% RETVAL = to_SV_pureperl(THIS); %}; void scale(double factor); void translate(double x, double y); void pop_back() %code{% THIS->points.pop_back(); %}; void reverse(); Lines lines(); Clone first_point(); Clone last_point(); Points equally_spaced_points(double distance); double length(); bool is_valid(); void clip_end(double distance); void clip_start(double distance); void extend_end(double distance); void extend_start(double distance); void simplify(double tolerance); void simplify_by_visibility(ExPolygon* expolygon) %code{% THIS->simplify_by_visibility(*expolygon); %}; void split_at(Point* point, Polyline* p1, Polyline* p2) %code{% THIS->split_at(*point, p1, p2); %}; bool is_straight(); Clone bounding_box(); void remove_duplicate_points(); std::string wkt(); %{ Polyline* Polyline::new(...) CODE: RETVAL = new Polyline (); // ST(0) is class name, ST(1) is first point RETVAL->points.resize(items-1); for (unsigned int i = 1; i < items; i++) { from_SV_check(ST(i), &RETVAL->points[i-1]); } OUTPUT: RETVAL void Polyline::append(...) CODE: for (unsigned int i = 1; i < items; i++) { Point p; from_SV_check(ST(i), &p); THIS->points.push_back(p); } void Polyline::append_polyline(polyline) Polyline* polyline; CODE: for (Points::const_iterator it = polyline->points.begin(); it != polyline->points.end(); ++it) { THIS->points.push_back((*it)); } void Polyline::rotate(angle, center_sv) double angle; SV* center_sv; CODE: Point center; from_SV_check(center_sv, ¢er); THIS->rotate(angle, center); Polygons Polyline::grow(delta, joinType = ClipperLib::jtSquare, miterLimit = 3) const float delta ClipperLib::JoinType joinType double miterLimit CODE: RETVAL = offset(*THIS, delta, joinType, miterLimit); OUTPUT: RETVAL %} }; Slic3r-version_1.39.1/xs/xsp/PolylineCollection.xsp000066400000000000000000000042231324354444700223330ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/PolylineCollection.hpp" %} %name{Slic3r::Polyline::Collection} class PolylineCollection { ~PolylineCollection(); Clone clone() %code{% RETVAL = THIS; %}; void clear() %code{% THIS->polylines.clear(); %}; PolylineCollection* chained_path(bool no_reverse) %code{% RETVAL = new PolylineCollection(); THIS->chained_path(RETVAL, no_reverse); %}; PolylineCollection* chained_path_from(Point* start_near, bool no_reverse) %code{% RETVAL = new PolylineCollection(); THIS->chained_path_from(*start_near, RETVAL, no_reverse); %}; int count() %code{% RETVAL = THIS->polylines.size(); %}; Clone leftmost_point(); %{ PolylineCollection* PolylineCollection::new(...) CODE: RETVAL = new PolylineCollection (); // ST(0) is class name, others are Polylines RETVAL->polylines.resize(items-1); for (unsigned int i = 1; i < items; i++) { // Note: a COPY of the input is stored from_SV_check(ST(i), &RETVAL->polylines[i-1]); } OUTPUT: RETVAL SV* PolylineCollection::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->polylines.size()-1); int i = 0; for (Polylines::iterator it = THIS->polylines.begin(); it != THIS->polylines.end(); ++it) { av_store(av, i++, perl_to_SV_ref(*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL SV* PolylineCollection::pp() CODE: AV* av = newAV(); av_fill(av, THIS->polylines.size()-1); int i = 0; for (Polylines::iterator it = THIS->polylines.begin(); it != THIS->polylines.end(); ++it) { av_store(av, i++, to_SV_pureperl(&*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL void PolylineCollection::append(...) CODE: for (unsigned int i = 1; i < items; i++) { Polyline polyline; from_SV_check(ST(i), &polyline); THIS->polylines.push_back(polyline); } %} }; Slic3r-version_1.39.1/xs/xsp/Print.xsp000066400000000000000000000221611324354444700176210ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Print.hpp" #include "libslic3r/PlaceholderParser.hpp" %} %package{Slic3r::Print::State}; %{ IV _constant() ALIAS: STEP_SLICE = posSlice STEP_PERIMETERS = posPerimeters STEP_PREPARE_INFILL = posPrepareInfill STEP_INFILL = posInfill STEP_SUPPORTMATERIAL = posSupportMaterial STEP_SKIRT = psSkirt STEP_BRIM = psBrim STEP_WIPE_TOWER = psWipeTower PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} %name{Slic3r::Print::Region} class PrintRegion { // owned by Print, no constructor/destructor Ref config() %code%{ RETVAL = &THIS->config; %}; Ref print(); Clone flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, PrintObject* object) %code%{ RETVAL = THIS->flow(role, layer_height, bridge, first_layer, width, *object); %}; }; %name{Slic3r::Print::Object} class PrintObject { // owned by Print, no constructor/destructor void add_region_volume(int region_id, int volume_id); std::vector get_region_volumes(int region_id) %code%{ if (0 <= region_id && region_id < THIS->region_volumes.size()) RETVAL = THIS->region_volumes[region_id]; %}; int region_count() %code%{ RETVAL = THIS->print()->regions.size(); %}; Ref print(); Ref model_object(); Ref config() %code%{ RETVAL = &THIS->config; %}; Points copies(); t_layer_height_ranges layer_height_ranges() %code%{ RETVAL = THIS->layer_height_ranges; %}; std::vector layer_height_profile() %code%{ RETVAL = THIS->layer_height_profile; %}; Ref size() %code%{ RETVAL = &THIS->size; %}; Clone bounding_box(); Points _shifted_copies() %code%{ RETVAL = THIS->_shifted_copies; %}; void set_shifted_copies(Points value) %code%{ THIS->_shifted_copies = value; %}; bool add_copy(Pointf* point) %code%{ RETVAL = THIS->add_copy(*point); %}; bool delete_last_copy(); bool delete_all_copies(); bool set_copies(Points copies); bool reload_model_instances(); void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges) %code%{ THIS->layer_height_ranges = layer_height_ranges; %}; void set_layer_height_profile(std::vector profile) %code%{ THIS->layer_height_profile = profile; %}; size_t total_layer_count(); size_t layer_count(); void clear_layers(); Ref get_layer(int idx); Ref add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z); size_t support_layer_count(); void clear_support_layers(); Ref get_support_layer(int idx); bool step_done(PrintObjectStep step) %code%{ RETVAL = THIS->state.is_done(step); %}; void set_step_done(PrintObjectStep step) %code%{ THIS->state.set_done(step); %}; void set_step_started(PrintObjectStep step) %code%{ THIS->state.set_started(step); %}; void _slice(); std::string _fix_slicing_errors(); void _simplify_slices(double distance); void _prepare_infill(); void detect_surfaces_type(); void process_external_surfaces(); void _make_perimeters(); void _infill(); void _generate_support_material(); std::vector get_layer_height_min_max() %code%{ SlicingParameters slicing_params = THIS->slicing_parameters(); RETVAL.push_back(slicing_params.min_layer_height); RETVAL.push_back(slicing_params.max_layer_height); RETVAL.push_back(slicing_params.first_print_layer_height); RETVAL.push_back(slicing_params.first_object_layer_height); RETVAL.push_back(slicing_params.layer_height); %}; void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action) %code%{ THIS->update_layer_height_profile(THIS->model_object()->layer_height_profile); adjust_layer_height_profile( THIS->slicing_parameters(), THIS->model_object()->layer_height_profile, z, layer_thickness_delta, band_width, LayerHeightEditActionType(action)); THIS->model_object()->layer_height_profile_valid = true; THIS->layer_height_profile_valid = false; %}; void reset_layer_height_profile(); int ptr() %code%{ RETVAL = (int)(intptr_t)THIS; %}; }; %name{Slic3r::Print} class Print { Print(); ~Print(); Ref config() %code%{ RETVAL = &THIS->config; %}; Ref default_object_config() %code%{ RETVAL = &THIS->default_object_config; %}; Ref default_region_config() %code%{ RETVAL = &THIS->default_region_config; %}; Ref placeholder_parser() %code%{ RETVAL = &THIS->placeholder_parser; %}; // TODO: status_cb Ref skirt() %code%{ RETVAL = &THIS->skirt; %}; Ref brim() %code%{ RETVAL = &THIS->brim; %}; std::string estimated_print_time() %code%{ RETVAL = THIS->estimated_print_time; %}; PrintObjectPtrs* objects() %code%{ RETVAL = &THIS->objects; %}; void clear_objects(); Ref get_object(int idx); void delete_object(int idx); void reload_object(int idx); bool reload_model_instances(); size_t object_count() %code%{ RETVAL = THIS->objects.size(); %}; PrintRegionPtrs* regions() %code%{ RETVAL = &THIS->regions; %}; Ref get_region(int idx); Ref add_region(); size_t region_count() %code%{ RETVAL = THIS->regions.size(); %}; bool step_done(PrintStep step) %code%{ RETVAL = THIS->state.is_done(step); %}; bool object_step_done(PrintObjectStep step) %code%{ RETVAL = THIS->step_done(step); %}; void set_step_done(PrintStep step) %code%{ THIS->state.set_done(step); %}; void set_step_started(PrintStep step) %code%{ THIS->state.set_started(step); %}; void clear_filament_stats() %code%{ THIS->filament_stats.clear(); %}; void set_filament_stats(int extruder_id, float length) %code%{ THIS->filament_stats.insert(std::pair(extruder_id, 0)); THIS->filament_stats[extruder_id] += length; %}; SV* filament_stats() %code%{ HV* hv = newHV(); for (std::map::const_iterator it = THIS->filament_stats.begin(); it != THIS->filament_stats.end(); ++it) { // stringify extruder_id std::ostringstream ss; ss << it->first; std::string str = ss.str(); (void)hv_store( hv, str.c_str(), str.length(), newSViv(it->second), 0 ); RETVAL = newRV_noinc((SV*)hv); } %}; void _simplify_slices(double distance); double max_allowed_layer_height() const; bool has_support_material() const; void auto_assign_extruders(ModelObject* model_object); std::string output_filepath(std::string path = "") %code%{ try { RETVAL = THIS->output_filepath(path); } catch (std::exception& e) { croak(e.what()); } %}; void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig* config) %code%{ RETVAL = THIS->apply_config(*config); %}; bool has_infinite_skirt(); bool has_skirt(); std::vector extruders() const; int validate() %code%{ std::string err = THIS->validate(); if (! err.empty()) croak("Configuration is not valid: %s\n", err.c_str()); RETVAL = 1; %}; Clone bounding_box(); Clone total_bounding_box(); double skirt_first_layer_height(); Clone brim_flow(); Clone skirt_flow(); void _make_skirt(); void _make_brim(); bool has_wipe_tower(); void _clear_wipe_tower(); void _make_wipe_tower(); %{ double Print::total_used_filament(...) CODE: if (items > 1) { THIS->total_used_filament = (double)SvNV(ST(1)); } RETVAL = THIS->total_used_filament; OUTPUT: RETVAL double Print::total_extruded_volume(...) CODE: if (items > 1) { THIS->total_extruded_volume = (double)SvNV(ST(1)); } RETVAL = THIS->total_extruded_volume; OUTPUT: RETVAL double Print::total_weight(...) CODE: if (items > 1) { THIS->total_weight = (double)SvNV(ST(1)); } RETVAL = THIS->total_weight; OUTPUT: RETVAL double Print::total_cost(...) CODE: if (items > 1) { THIS->total_cost = (double)SvNV(ST(1)); } RETVAL = THIS->total_cost; OUTPUT: RETVAL %} }; Slic3r-version_1.39.1/xs/xsp/Surface.xsp000066400000000000000000000057531324354444700201250ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/Surface.hpp" #include "libslic3r/ClipperUtils.hpp" %} %name{Slic3r::Surface} class Surface { ~Surface(); Ref expolygon() %code{% RETVAL = &(THIS->expolygon); %}; double thickness() %code{% RETVAL = THIS->thickness; %}; unsigned short thickness_layers() %code{% RETVAL = THIS->thickness_layers; %}; double area(); bool is_solid() const; bool is_external() const; bool is_internal() const; bool is_bottom() const; bool is_bridge() const; %{ Surface* _new(CLASS, expolygon, surface_type, thickness, thickness_layers, bridge_angle, extra_perimeters) char* CLASS; ExPolygon* expolygon; SurfaceType surface_type; double thickness; unsigned short thickness_layers; double bridge_angle; unsigned short extra_perimeters; CODE: RETVAL = new Surface (surface_type, *expolygon); RETVAL->thickness = thickness; RETVAL->thickness_layers = thickness_layers; RETVAL->bridge_angle = bridge_angle; RETVAL->extra_perimeters = extra_perimeters; // we don't delete expolygon here because it's referenced by a Perl SV // whose DESTROY will take care of destruction OUTPUT: RETVAL SurfaceType Surface::surface_type(...) CODE: if (items > 1) { THIS->surface_type = (SurfaceType)SvUV(ST(1)); } RETVAL = THIS->surface_type; OUTPUT: RETVAL double Surface::bridge_angle(...) CODE: if (items > 1) { THIS->bridge_angle = (double)SvNV(ST(1)); } RETVAL = THIS->bridge_angle; OUTPUT: RETVAL unsigned short Surface::extra_perimeters(...) CODE: if (items > 1) { THIS->extra_perimeters = (double)SvUV(ST(1)); } RETVAL = THIS->extra_perimeters; OUTPUT: RETVAL Polygons Surface::polygons() CODE: RETVAL.push_back(THIS->expolygon.contour); for (Polygons::iterator it = THIS->expolygon.holes.begin(); it != THIS->expolygon.holes.end(); ++it) { RETVAL.push_back((*it)); } OUTPUT: RETVAL Surfaces Surface::offset(delta, joinType = ClipperLib::jtMiter, miterLimit = 3) const float delta ClipperLib::JoinType joinType double miterLimit CODE: surfaces_append(RETVAL, offset_ex(THIS->expolygon, delta, joinType, miterLimit), *THIS); OUTPUT: RETVAL %} }; %package{Slic3r::Surface}; %{ IV _constant() ALIAS: S_TYPE_TOP = stTop S_TYPE_BOTTOM = stBottom S_TYPE_BOTTOMBRIDGE = stBottomBridge S_TYPE_INTERNAL = stInternal S_TYPE_INTERNALSOLID = stInternalSolid S_TYPE_INTERNALBRIDGE = stInternalBridge S_TYPE_INTERNALVOID = stInternalVoid S_TYPW_PERIMETER = stPerimeter PROTOTYPE: CODE: RETVAL = ix; OUTPUT: RETVAL %} Slic3r-version_1.39.1/xs/xsp/SurfaceCollection.xsp000066400000000000000000000044451324354444700221360ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/SurfaceCollection.hpp" %} %name{Slic3r::Surface::Collection} class SurfaceCollection { %name{_new} SurfaceCollection(); ~SurfaceCollection(); void clear() %code{% THIS->surfaces.clear(); %}; void append(Surface* surface) %code{% THIS->surfaces.push_back(*surface); %}; int count() %code{% RETVAL = THIS->surfaces.size(); %}; void simplify(double tolerance); %{ SV* SurfaceCollection::arrayref() CODE: AV* av = newAV(); av_fill(av, THIS->surfaces.size()-1); int i = 0; for (Surfaces::iterator it = THIS->surfaces.begin(); it != THIS->surfaces.end(); ++it) { av_store(av, i++, perl_to_SV_ref(*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL SV* SurfaceCollection::filter_by_type(surface_type) SurfaceType surface_type; CODE: AV* av = newAV(); for (Surfaces::iterator it = THIS->surfaces.begin(); it != THIS->surfaces.end(); ++it) { if ((*it).surface_type == surface_type) av_push(av, perl_to_SV_ref(*it)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL void SurfaceCollection::replace(index, surface) int index Surface* surface CODE: THIS->surfaces[index] = *surface; void SurfaceCollection::set_surface_type(index, surface_type) int index SurfaceType surface_type; CODE: THIS->surfaces[index].surface_type = surface_type; SV* SurfaceCollection::group() CODE: // perform grouping std::vector groups; THIS->group(&groups); // build return arrayref AV* av = newAV(); av_fill(av, groups.size()-1); size_t i = 0; for (std::vector::iterator it = groups.begin(); it != groups.end(); ++it) { AV* innerav = newAV(); av_fill(innerav, it->size()-1); size_t j = 0; for (SurfacesPtr::iterator it_s = it->begin(); it_s != it->end(); ++it_s) { av_store(innerav, j++, perl_to_SV_clone_ref(**it_s)); } av_store(av, i++, newRV_noinc((SV*)innerav)); } RETVAL = newRV_noinc((SV*)av); OUTPUT: RETVAL %} }; Slic3r-version_1.39.1/xs/xsp/TriangleMesh.xsp000066400000000000000000000202701324354444700211060ustar00rootroot00000000000000%module{Slic3r::XS}; %{ #include #include "libslic3r/TriangleMesh.hpp" %} %name{Slic3r::TriangleMesh} class TriangleMesh { TriangleMesh(); ~TriangleMesh(); Clone clone() %code{% RETVAL = THIS; %}; void ReadSTLFile(char* input_file); void write_ascii(char* output_file); void write_binary(char* output_file); void repair(); void WriteOBJFile(char* output_file); void scale(float factor); void scale_xyz(Pointf3* versor) %code{% THIS->scale(*versor); %}; void translate(float x, float y, float z); void rotate_x(float angle); void rotate_y(float angle); void rotate_z(float angle); void mirror_x(); void mirror_y(); void mirror_z(); void align_to_origin(); void rotate(double angle, Point* center); TriangleMeshPtrs split(); void merge(TriangleMesh* mesh) %code{% THIS->merge(*mesh); %}; ExPolygons horizontal_projection(); Clone convex_hull(); Clone bounding_box(); Clone center() %code{% RETVAL = THIS->bounding_box().center(); %}; int facets_count(); void reset_repair_stats(); %{ void TriangleMesh::ReadFromPerl(vertices, facets) SV* vertices SV* facets CODE: stl_file &stl = THIS->stl; stl.error = 0; stl.stats.type = inmemory; // count facets and allocate memory AV* facets_av = (AV*)SvRV(facets); stl.stats.number_of_facets = av_len(facets_av)+1; stl.stats.original_num_facets = stl.stats.number_of_facets; stl_allocate(&stl); // read geometry AV* vertices_av = (AV*)SvRV(vertices); for (int i = 0; i < stl.stats.number_of_facets; i++) { AV* facet_av = (AV*)SvRV(*av_fetch(facets_av, i, 0)); stl_facet facet; facet.normal.x = 0; facet.normal.y = 0; facet.normal.z = 0; for (unsigned int v = 0; v <= 2; v++) { AV* vertex_av = (AV*)SvRV(*av_fetch(vertices_av, SvIV(*av_fetch(facet_av, v, 0)), 0)); facet.vertex[v].x = SvNV(*av_fetch(vertex_av, 0, 0)); facet.vertex[v].y = SvNV(*av_fetch(vertex_av, 1, 0)); facet.vertex[v].z = SvNV(*av_fetch(vertex_av, 2, 0)); } facet.extra[0] = 0; facet.extra[1] = 0; stl.facet_start[i] = facet; } stl_get_size(&stl); SV* TriangleMesh::stats() CODE: HV* hv = newHV(); (void)hv_stores( hv, "number_of_facets", newSViv(THIS->stl.stats.number_of_facets) ); (void)hv_stores( hv, "number_of_parts", newSViv(THIS->stl.stats.number_of_parts) ); (void)hv_stores( hv, "volume", newSVnv(THIS->stl.stats.volume) ); (void)hv_stores( hv, "degenerate_facets", newSViv(THIS->stl.stats.degenerate_facets) ); (void)hv_stores( hv, "edges_fixed", newSViv(THIS->stl.stats.edges_fixed) ); (void)hv_stores( hv, "facets_removed", newSViv(THIS->stl.stats.facets_removed) ); (void)hv_stores( hv, "facets_added", newSViv(THIS->stl.stats.facets_added) ); (void)hv_stores( hv, "facets_reversed", newSViv(THIS->stl.stats.facets_reversed) ); (void)hv_stores( hv, "backwards_edges", newSViv(THIS->stl.stats.backwards_edges) ); (void)hv_stores( hv, "normals_fixed", newSViv(THIS->stl.stats.normals_fixed) ); RETVAL = (SV*)newRV_noinc((SV*)hv); OUTPUT: RETVAL SV* TriangleMesh::vertices() CODE: if (!THIS->repaired) CONFESS("vertices() requires repair()"); if (THIS->stl.v_shared == NULL) stl_generate_shared_vertices(&(THIS->stl)); // vertices AV* vertices = newAV(); av_extend(vertices, THIS->stl.stats.shared_vertices); for (int i = 0; i < THIS->stl.stats.shared_vertices; i++) { AV* vertex = newAV(); av_store(vertices, i, newRV_noinc((SV*)vertex)); av_extend(vertex, 2); av_store(vertex, 0, newSVnv(THIS->stl.v_shared[i].x)); av_store(vertex, 1, newSVnv(THIS->stl.v_shared[i].y)); av_store(vertex, 2, newSVnv(THIS->stl.v_shared[i].z)); } RETVAL = newRV_noinc((SV*)vertices); OUTPUT: RETVAL SV* TriangleMesh::facets() CODE: if (!THIS->repaired) CONFESS("facets() requires repair()"); if (THIS->stl.v_shared == NULL) stl_generate_shared_vertices(&(THIS->stl)); // facets AV* facets = newAV(); av_extend(facets, THIS->stl.stats.number_of_facets); for (int i = 0; i < THIS->stl.stats.number_of_facets; i++) { AV* facet = newAV(); av_store(facets, i, newRV_noinc((SV*)facet)); av_extend(facet, 2); av_store(facet, 0, newSVnv(THIS->stl.v_indices[i].vertex[0])); av_store(facet, 1, newSVnv(THIS->stl.v_indices[i].vertex[1])); av_store(facet, 2, newSVnv(THIS->stl.v_indices[i].vertex[2])); } RETVAL = newRV_noinc((SV*)facets); OUTPUT: RETVAL SV* TriangleMesh::normals() CODE: if (!THIS->repaired) CONFESS("normals() requires repair()"); // normals AV* normals = newAV(); av_extend(normals, THIS->stl.stats.number_of_facets); for (int i = 0; i < THIS->stl.stats.number_of_facets; i++) { AV* facet = newAV(); av_store(normals, i, newRV_noinc((SV*)facet)); av_extend(facet, 2); av_store(facet, 0, newSVnv(THIS->stl.facet_start[i].normal.x)); av_store(facet, 1, newSVnv(THIS->stl.facet_start[i].normal.y)); av_store(facet, 2, newSVnv(THIS->stl.facet_start[i].normal.z)); } RETVAL = newRV_noinc((SV*)normals); OUTPUT: RETVAL SV* TriangleMesh::size() CODE: AV* size = newAV(); av_extend(size, 2); av_store(size, 0, newSVnv(THIS->stl.stats.size.x)); av_store(size, 1, newSVnv(THIS->stl.stats.size.y)); av_store(size, 2, newSVnv(THIS->stl.stats.size.z)); RETVAL = newRV_noinc((SV*)size); OUTPUT: RETVAL SV* TriangleMesh::slice(z) std::vector z CODE: // convert doubles to floats std::vector z_f(z.begin(), z.end()); std::vector layers; TriangleMeshSlicer mslicer(THIS); mslicer.slice(z_f, &layers); AV* layers_av = newAV(); size_t len = layers.size(); if (len > 0) av_extend(layers_av, len-1); for (unsigned int i = 0; i < layers.size(); i++) { AV* expolygons_av = newAV(); len = layers[i].size(); if (len > 0) av_extend(expolygons_av, len-1); unsigned int j = 0; for (ExPolygons::iterator it = layers[i].begin(); it != layers[i].end(); ++it) { av_store(expolygons_av, j++, perl_to_SV_clone_ref(*it)); } av_store(layers_av, i, newRV_noinc((SV*)expolygons_av)); } RETVAL = (SV*)newRV_noinc((SV*)layers_av); OUTPUT: RETVAL void TriangleMesh::cut(z, upper, lower) float z; TriangleMesh* upper; TriangleMesh* lower; CODE: TriangleMeshSlicer mslicer(THIS); mslicer.cut(z, upper, lower); std::vector TriangleMesh::bb3() CODE: RETVAL.push_back(THIS->stl.stats.min.x); RETVAL.push_back(THIS->stl.stats.min.y); RETVAL.push_back(THIS->stl.stats.max.x); RETVAL.push_back(THIS->stl.stats.max.y); RETVAL.push_back(THIS->stl.stats.min.z); RETVAL.push_back(THIS->stl.stats.max.z); OUTPUT: RETVAL Clone cube(double x, double y, double z) CODE: RETVAL = make_cube(x,y,z); OUTPUT: RETVAL Clone cylinder(double r, double h) CODE: RETVAL = make_cylinder(r, h); OUTPUT: RETVAL Clone sphere(double rho) CODE: RETVAL = make_sphere(rho); OUTPUT: RETVAL %} }; %package{Slic3r::TriangleMesh}; %{ PROTOTYPES: DISABLE std::string hello_world() CODE: RETVAL = "Hello world!"; OUTPUT: RETVAL %} Slic3r-version_1.39.1/xs/xsp/XS.xsp000066400000000000000000000053221324354444700170570ustar00rootroot00000000000000%module{Slic3r::XS}; %package{Slic3r::XS}; #include #include "Utils.hpp" %{ %} %package{Slic3r}; %{ SV* VERSION() CODE: RETVAL = newSVpv(SLIC3R_VERSION, 0); OUTPUT: RETVAL SV* BUILD() CODE: RETVAL = newSVpv(SLIC3R_BUILD, 0); OUTPUT: RETVAL SV* DEBUG_OUT_PATH_PREFIX() CODE: RETVAL = newSVpv(SLIC3R_DEBUG_OUT_PATH_PREFIX, 0); OUTPUT: RETVAL SV* FORK_NAME() CODE: RETVAL = newSVpv(SLIC3R_FORK_NAME, 0); OUTPUT: RETVAL void set_logging_level(level) unsigned int level; CODE: Slic3r::set_logging_level(level); void trace(level, message) unsigned int level; char *message; CODE: Slic3r::trace(level, message); void set_var_dir(dir) char *dir; CODE: Slic3r::set_var_dir(dir); char* var_dir() CODE: RETVAL = const_cast(Slic3r::var_dir().c_str()); OUTPUT: RETVAL void set_resources_dir(dir) char *dir; CODE: Slic3r::set_resources_dir(dir); char* resources_dir() CODE: RETVAL = const_cast(Slic3r::resources_dir().c_str()); OUTPUT: RETVAL std::string var(file_name) const char *file_name; CODE: RETVAL = Slic3r::var(file_name); OUTPUT: RETVAL void set_data_dir(dir) char *dir; CODE: Slic3r::set_data_dir(dir); char* data_dir() CODE: RETVAL = const_cast(Slic3r::data_dir().c_str()); OUTPUT: RETVAL local_encoded_string encode_path(src) const char *src; CODE: RETVAL = Slic3r::encode_path(src); OUTPUT: RETVAL std::string decode_path(src) const char *src; CODE: RETVAL = Slic3r::decode_path(src); OUTPUT: RETVAL std::string normalize_utf8_nfc(src) const char *src; CODE: RETVAL = Slic3r::normalize_utf8_nfc(src); OUTPUT: RETVAL std::string path_to_filename(src) const char *src; CODE: RETVAL = Slic3r::PerlUtils::path_to_filename(src); OUTPUT: RETVAL local_encoded_string path_to_filename_raw(src) const char *src; CODE: RETVAL = Slic3r::PerlUtils::path_to_filename(src); OUTPUT: RETVAL std::string path_to_stem(src) const char *src; CODE: RETVAL = Slic3r::PerlUtils::path_to_stem(src); OUTPUT: RETVAL std::string path_to_extension(src) const char *src; CODE: RETVAL = Slic3r::PerlUtils::path_to_extension(src); OUTPUT: RETVAL std::string path_to_parent_path(src) const char *src; CODE: RETVAL = Slic3r::PerlUtils::path_to_parent_path(src); OUTPUT: RETVAL void xspp_test_croak_hangs_on_strawberry() CODE: try { throw 1; } catch (...) { croak("xspp_test_croak_hangs_on_strawberry: exception catched\n"); } %}Slic3r-version_1.39.1/xs/xsp/my.map000066400000000000000000000425361324354444700171250ustar00rootroot00000000000000coordf_t T_NV std::string T_STD_STRING local_encoded_string T_STD_STRING_LOCAL_ENCODING t_config_option_key T_STD_STRING t_model_material_id T_STD_STRING std::vector T_STD_VECTOR_STD_STRING std::vector T_STD_VECTOR_INT std::vector T_STD_VECTOR_INT std::vector T_STD_VECTOR_INT std::vector T_STD_VECTOR_UINT std::vector T_STD_VECTOR_DOUBLE t_layer_height_ranges T_LAYER_HEIGHT_RANGES BoundingBox* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T BoundingBoxf* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T BoundingBoxf3* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T DynamicPrintConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T StaticPrintConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PrintObjectConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PrintRegionConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T GCodeConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PrintConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T FullPrintConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T ZTable* O_OBJECT TriangleMesh* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Point* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Point3* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Pointf* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Pointf3* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Line* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Linef3* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Polyline* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T PolylineCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Polygon* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ExPolygon* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ExPolygonCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ExtrusionEntityCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ExtrusionMultiPath* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ExtrusionPath* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ExtrusionLoop* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ExtrusionSimulator* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Filler* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T Flow* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T PrintState* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Surface* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T SurfaceCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Model* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ModelMaterial* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ModelObject* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ModelVolume* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T ModelInstance* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T PrintRegion* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PrintObject* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Print* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T LayerRegion* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Layer* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T SupportLayer* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PlaceholderParser* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T CoolingBuffer* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T GCode* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T MotionPlanner* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T GCodeSender* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T BridgeDetector* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T PerimeterGenerator* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T PrintObjectSupportMaterial* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T AppConfig* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T GLShader* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T GLVolume* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T GLVolumeCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Preset* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PresetCollection* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PresetBundle* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T PresetHints* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Axis T_UV ExtrusionLoopRole T_UV ExtrusionRole T_UV ExtrusionSimulationType T_UV FlowRole T_UV PrintStep T_UV PrintObjectStep T_UV SurfaceType T_UV ClipperLib::JoinType T_UV ClipperLib::PolyFillType T_UV # we return these types whenever we want the items to be cloned Points T_ARRAYREF Pointfs T_ARRAYREF Lines T_ARRAYREF Polygons T_ARRAYREF Polylines T_ARRAYREF ExPolygons T_ARRAYREF ExtrusionPaths T_ARRAYREF Surfaces T_ARRAYREF # we return these types whenever we want the items to be returned # by reference and marked ::Ref because they're contained in another # Perl object Polygons* T_ARRAYREF_PTR ModelObjectPtrs* T_PTR_ARRAYREF_PTR ModelVolumePtrs* T_PTR_ARRAYREF_PTR ModelInstancePtrs* T_PTR_ARRAYREF_PTR PrintRegionPtrs* T_PTR_ARRAYREF_PTR PrintObjectPtrs* T_PTR_ARRAYREF_PTR LayerPtrs* T_PTR_ARRAYREF_PTR SupportLayerPtrs* T_PTR_ARRAYREF_PTR # we return these types whenever we want the items to be returned # by reference and not marked ::Ref because they're newly allocated # and not referenced by any Perl object TriangleMeshPtrs T_PTR_ARRAYREF INPUT T_STD_STRING { size_t len; // const char * c = SvPV($arg, len); // Always convert strings to UTF-8 before passing them to XS const char * c = SvPVutf8($arg, len); $var = std::string(c, len); } INPUT T_STD_STRING_LOCAL_ENCODING { size_t len; const char * c = SvPV($arg, len); $var = std::string(c, len); } T_STD_VECTOR_STD_STRING if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { AV* av = (AV*)SvRV($arg); const unsigned int alen = av_len(av)+1; $var = std::vector(alen); STRLEN len; char* tmp; SV** elem; for (unsigned int i = 0; i < alen; i++) { elem = av_fetch(av, i, 0); if (elem != NULL) { tmp = SvPV(*elem, len); ${var}[i] = std::string(tmp, len); } else ${var}[i] = std::string(\"\"); } } else Perl_croak(aTHX_ \"%s: %s is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); T_STD_VECTOR_INT if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { AV* av = (AV*)SvRV($arg); const unsigned int len = av_len(av)+1; $var = std::vector(len); SV** elem; for (unsigned int i = 0; i < len; i++) { elem = av_fetch(av, i, 0); if (elem != NULL) ${var}[i] = SvIV(*elem); else ${var}[i] = 0; } } else Perl_croak(aTHX_ \"%s: %s is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); T_STD_VECTOR_UINT if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { AV* av = (AV*)SvRV($arg); const unsigned int len = av_len(av)+1; $var = std::vector(len); SV** elem; for (unsigned int i = 0; i < len; i++) { elem = av_fetch(av, i, 0); if (elem != NULL) ${var}[i] = SvUV(*elem); else ${var}[i] = 0; } } else Perl_croak(aTHX_ \"%s: %s is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); T_STD_VECTOR_DOUBLE if (SvROK($arg) && SvTYPE(SvRV($arg))==SVt_PVAV) { AV* av = (AV*)SvRV($arg); const unsigned int len = av_len(av)+1; $var = std::vector(len); SV** elem; for (unsigned int i = 0; i < len; i++) { elem = av_fetch(av, i, 0); if (elem != NULL) ${var}[i] = SvNV(*elem); else ${var}[i] = 0.; } } else Perl_croak(aTHX_ \"%s: %s is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); O_OBJECT_SLIC3R if( sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG) ) { if ( sv_isa($arg, Slic3r::perl_class_name($var) ) || sv_isa($arg, Slic3r::perl_class_name_ref($var) )) { $var = ($type)SvIV((SV*)SvRV( $arg )); } else { croak(\"$var is not of type %s (got %s)\", Slic3r::perl_class_name($var), HvNAME(SvSTASH(SvRV($arg)))); XSRETURN_UNDEF; } } else { warn( \"${Package}::$func_name() -- $var is not a blessed SV reference\" ); XSRETURN_UNDEF; } T_ARRAYREF if (SvROK($arg) && SvTYPE(SvRV($arg)) == SVt_PVAV) { AV* av = (AV*)SvRV($arg); const unsigned int len = av_len(av)+1; $var.resize(len); for (unsigned int i = 0; i < len; i++) { SV** elem = av_fetch(av, i, 0); from_SV_check(*elem, &$var\[i]); } } else Perl_croak(aTHX_ \"%s: %s is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); T_LAYER_HEIGHT_RANGES { if (!SvROK($arg) || SvTYPE(SvRV($arg)) != SVt_PVAV) { Perl_croak(aTHX_ \"%s: %s is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } AV* av = (AV*)SvRV($arg); const unsigned int len = av_len(av)+1; t_layer_height_ranges tmp_ranges; for (unsigned int i = 0; i < len; i++) { SV* elem = *av_fetch(av, i, 0); if (!SvROK(elem) || SvTYPE(SvRV(elem)) != SVt_PVAV) { Perl_croak( aTHX_ \"%s: %s contains something that is not an array reference\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } AV* elemAV = (AV*)SvRV(elem); if (av_len(elemAV) + 1 != 3) { Perl_croak( aTHX_ \"%s: %s contains an array that isn't 3 elements long\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}, \"$var\"); } coordf_t vals[3]; for (unsigned int j = 0; j < 3; ++j) { SV *elem_elem = *av_fetch(elemAV, j, 0); if (!looks_like_number(elem_elem)) { Perl_croak( aTHX_ \"%s: layer ranges and heights must be numbers\", ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq[\"$pname\"]}); } vals[j] = SvNV(elem_elem); } tmp_ranges[t_layer_height_range(vals[0], vals[1])] = vals[2]; } $var = tmp_ranges; } OUTPUT T_STD_STRING $arg = newSVpvn_utf8( $var.c_str(), $var.length(), true ); T_STD_STRING_LOCAL_ENCODING $arg = newSVpvn( $var.c_str(), $var.length() ); T_STD_VECTOR_STD_STRING AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len) av_extend(av, len-1); for (unsigned int i = 0; i < len; i++) { const std::string& str = ${var}[i]; STRLEN len = str.length(); av_store(av, i, newSVpvn_utf8(str.c_str(), len, true)); } T_STD_VECTOR_INT AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len) av_extend(av, len-1); for (unsigned int i = 0; i < len; i++) { av_store(av, i, newSViv(${var}[i])); } T_STD_VECTOR_UINT AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len) av_extend(av, len-1); for (unsigned int i = 0; i < len; i++) { av_store(av, i, newSVuv(${var}[i])); } T_STD_VECTOR_DOUBLE AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len) av_extend(av, len-1); for (unsigned int i = 0; i < len; i++) { av_store(av, i, newSVnv(${var}[i])); } # return object from pointer O_OBJECT_SLIC3R if ($var == NULL) XSRETURN_UNDEF; sv_setref_pv( $arg, Slic3r::perl_class_name($var), (void*)$var ); # return value handled by template class O_OBJECT_SLIC3R_T if ($var == NULL) XSRETURN_UNDEF; sv_setref_pv( $arg, $type\::CLASS(), (void*)$var ); T_ARRAYREF AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len > 0) av_extend(av, len-1); int i = 0; for (${type}::const_iterator it = $var.begin(); it != $var.end(); ++it) { av_store(av, i++, perl_to_SV_clone_ref(*it)); } T_ARRAYREF_PTR AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var->size(); if (len > 0) av_extend(av, len-1); int i = 0; for (${ my $t = $type; $t =~ s/\*$//; \$t }::iterator it = $var->begin(); it != $var->end(); ++it) { av_store(av, i++, perl_to_SV_ref(*it)); } T_PTR_ARRAYREF_PTR AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var->size(); if (len > 0) av_extend(av, len-1); int i = 0; for (${ my $t = $type; $t =~ s/\*$//; \$t }::iterator it = $var->begin(); it != $var->end(); ++it) { av_store(av, i++, perl_to_SV_ref(**it)); } T_PTR_ARRAYREF AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len > 0) av_extend(av, len-1); int i = 0; for (${type}::iterator it = $var.begin(); it != $var.end(); ++it) { av_store(av, i++, to_SV(*it)); } T_LAYER_HEIGHT_RANGES AV* av = newAV(); $arg = newRV_noinc((SV*)av); sv_2mortal($arg); const unsigned int len = $var.size(); if (len > 0) av_extend(av, len-1); // map is sorted, so we can just copy it in order int i = 0; for (${type}::iterator it = $var.begin(); it != $var.end(); ++it) { const coordf_t range_values[] = { it->first.first, // key's first = minz it->first.second, // key's second = maxz it->second, // value = height }; AV *rangeAV = newAV(); av_extend(rangeAV, 2); for (int j = 0; j < 3; ++j) { av_store(rangeAV, j, newSVnv(range_values[j])); } av_store(av, i++, (SV*)newRV_noinc((SV*)rangeAV)); } Slic3r-version_1.39.1/xs/xsp/mytype.map000066400000000000000000000000001324354444700200030ustar00rootroot00000000000000Slic3r-version_1.39.1/xs/xsp/typemap.xspt000066400000000000000000000163311324354444700203720ustar00rootroot00000000000000%typemap{bool}{simple}; %typemap{size_t}{simple}; %typemap{coordf_t}{simple}; %typemap{std::string}; %typemap{t_config_option_key}; %typemap{t_model_material_id}; %typemap{std::vector}; %typemap{std::vector}; %typemap{std::vector*}; %typemap{std::vector}; %typemap{std::vector*}; %typemap{std::vector}; %typemap{std::vector*}; %typemap{std::vector}; %typemap{t_layer_height_ranges}; %typemap{void*}; %typemap{SV*}; %typemap{AV*}; %typemap{Point*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Point3*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Pointf*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Pointf3*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{BoundingBox*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{BoundingBoxf*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{BoundingBoxf3*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{DynamicPrintConfig*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{StaticPrintConfig*}; %typemap{Ref}{simple}; %typemap{PrintObjectConfig*}; %typemap{Ref}{simple}; %typemap{PrintRegionConfig*}; %typemap{Ref}{simple}; %typemap{GCodeConfig*}; %typemap{Ref}{simple}; %typemap{PrintConfig*}; %typemap{Ref}{simple}; %typemap{FullPrintConfig*}; %typemap{Ref}{simple}; %typemap{ExPolygon*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ExPolygonCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Filler*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Flow*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Line*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Linef3*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Polyline*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Polygon*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ExtrusionEntityCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ExtrusionMultiPath*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ExtrusionPath*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ExtrusionLoop*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ExtrusionSimulator*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{TriangleMesh*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{PolylineCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{MotionPlanner*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{GCodeSender*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{BridgeDetector*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{SurfaceCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{PerimeterGenerator*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Surface*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{PrintState*}; %typemap{Ref}{simple}; %typemap{PrintRegion*}; %typemap{Ref}{simple}; %typemap{PrintObject*}; %typemap{Ref}{simple}; %typemap{Print*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{LayerRegion*}; %typemap{Ref}{simple}; %typemap{Layer*}; %typemap{Ref}{simple}; %typemap{SupportLayer*}; %typemap{Ref}{simple}; %typemap{PrintObjectSupportMaterial*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{PlaceholderParser*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{CoolingBuffer*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{GCode*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{Points}; %typemap{Pointfs}; %typemap{Lines}; %typemap{Polygons}; %typemap{Polylines}; %typemap{ExPolygons}; %typemap{ExtrusionPaths}; %typemap{Surfaces}; %typemap{Polygons*}; %typemap{TriangleMesh*}; %typemap{TriangleMeshPtrs}; %typemap{Model*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelMaterial*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelObject*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelObjectPtrs*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelVolume*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelVolumePtrs*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelInstance*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{ModelInstancePtrs*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; %typemap{AppConfig*}; %typemap{Ref}{simple}; %typemap{GLShader*}; %typemap{Ref}{simple}; %typemap{GLVolume*}; %typemap{Ref}{simple}; %typemap{GLVolumeCollection*}; %typemap{Ref}{simple}; %typemap{Preset*}; %typemap{Ref}{simple}; %typemap{PresetCollection*}; %typemap{Ref}{simple}; %typemap{PresetBundle*}; %typemap{Ref}{simple}; %typemap{PresetHints*}; %typemap{Ref}{simple}; %typemap{PrintRegionPtrs*}; %typemap{PrintObjectPtrs*}; %typemap{LayerPtrs*}; %typemap{SupportLayerPtrs*}; %typemap{Axis}{parsed}{ %cpp_type{Axis}; %precall_code{% $CVar = (Axis)SvUV($PerlVar); %}; }; %typemap{SurfaceType}{parsed}{ %cpp_type{SurfaceType}; %precall_code{% $CVar = (SurfaceType)SvUV($PerlVar); %}; }; %typemap{ExtrusionLoopRole}{parsed}{ %cpp_type{ExtrusionLoopRole}; %precall_code{% $CVar = (ExtrusionLoopRole)SvUV($PerlVar); %}; }; %typemap{ExtrusionRole}{parsed}{ %cpp_type{ExtrusionRole}; %precall_code{% $CVar = (ExtrusionRole)SvUV($PerlVar); %}; }; %typemap{ExtrusionSimulationType}{parsed}{ %cpp_type{ExtrusionSimulationType}; %precall_code{% $CVar = (ExtrusionSimulationType)SvUV($PerlVar); %}; }; %typemap{FlowRole}{parsed}{ %cpp_type{FlowRole}; %precall_code{% $CVar = (FlowRole)SvUV($PerlVar); %}; }; %typemap{PrintStep}{parsed}{ %cpp_type{PrintStep}; %precall_code{% $CVar = (PrintStep)SvUV($PerlVar); %}; }; %typemap{PrintObjectStep}{parsed}{ %cpp_type{PrintObjectStep}; %precall_code{% $CVar = (PrintObjectStep)SvUV($PerlVar); %}; };