diff --git a/CMakeLists.txt b/CMakeLists.txt index 21d45174c..01be581dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,8 @@ option(SLIC3R_PERL_XS "Compile XS Perl module and enable Perl unit and option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0) option(SLIC3R_SYNTAXONLY "Only perform source code correctness checking, no binary output (UNIX only)" 0) +set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux") + # Proposal for C++ unit tests and sandboxes option(SLIC3R_BUILD_SANDBOXES "Build development sandboxes" OFF) option(SLIC3R_BUILD_TESTS "Build unit tests" OFF) diff --git a/doc/How to build - Linux et al.md b/doc/How to build - Linux et al.md index a0dfec089..14ec23b2d 100644 --- a/doc/How to build - Linux et al.md +++ b/doc/How to build - Linux et al.md @@ -7,7 +7,7 @@ as the versions listed - generally versions available on conservative Linux dist Perl is not required any more. -In a typical situaction, one would open a command line, go to the Slic3r sources, create a directory called `build` or similar, +In a typical situation, one would open a command line, go to the Slic3r sources, create a directory called `build` or similar, `cd` into it and call: cmake .. @@ -19,7 +19,7 @@ Additional CMake flags may be applicable as explained below. ### Dependency resolution -By default Slic3r looks for dependencies the default way CMake looks for them, ie. in default system locations. +By default Slic3r looks for dependencies the default way CMake looks for them, i.e. in default system locations. On Linux this will typically make Slic3r depend on dynamically loaded libraries from the system, however, Slic3r can be told to specifically look for static libraries with the `SLIC3R_STATIC` flag passed to cmake: @@ -58,16 +58,25 @@ Note that Slic3r PE is tested with wxWidgets 3.0 somewhat sporadically and so th ### Build variant -By default Scli3r builds the release variant. +By default Slic3r builds the release variant. To create a debug build, use the following CMake flag: -DCMAKE_BUILD_TYPE=Debug +### Enabling address sanitizer + +If you're using GCC/Clang compiler, it is possible to build Slic3r with the built-in address sanitizer enabled to help detect memory-corruption issues. +To enable it, simply use the following CMake flag: + + -DSLIC3R_ASAN=1 + +This requires GCC>4.8 or Clang>3.1. + ### Installation -In runtime, Slic3r needs a way to access its resource files. By default, it looks for a `resources` directory relative to its binary. +At runtime, Slic3r needs a way to access its resource files. By default, it looks for a `resources` directory relative to its binary. -If you instead wnat Slic3r installed in a structure according to the Filesystem Hierarchy Standard, use the `SLIC3R_FHS` flag +If you instead want Slic3r installed in a structure according to the File System Hierarchy Standard, use the `SLIC3R_FHS` flag cmake .. -DSLIC3R_FHS=1 diff --git a/doc/How to build - Mac OS.md b/doc/How to build - Mac OS.md index a1c8d9049..670e9fd87 100644 --- a/doc/How to build - Mac OS.md +++ b/doc/How to build - Mac OS.md @@ -18,12 +18,12 @@ This will create a dependencies bundle inside the `build/destdir` directory. You can also customize the bundle output path using the `-DDESTDIR=` option passed to `cmake`. **Warning**: Once the dependency bundle is installed in a destdir, the destdir cannot be moved elsewhere. -(This is because wxWidgets hardcode the installation path.) +(This is because wxWidgets hardcodes the installation path.) ### Building Slic3r -If dependencies built without an error, you can proceed to build Slic3r itself. +If dependencies are built without errors, you can proceed to build Slic3r itself. Go back to top level Slic3r sources directory and use these commands: mkdir build diff --git a/doc/How to build - Windows.md b/doc/How to build - Windows.md index 90fcfde7d..020b28958 100644 --- a/doc/How to build - Windows.md +++ b/doc/How to build - Windows.md @@ -7,7 +7,7 @@ CMake installer can be downloaded from [the official website](https://cmake.org/ Building with newer versions of MSVS (2015, 2017) may work too as reported by some of our users. -_Note:_ Thanks to [**@supermerill**](https://github.com/supermerill) for testing and inspiration on this guide. +_Note:_ Thanks to [**@supermerill**](https://github.com/supermerill) for testing and inspiration for this guide. ### Dependencies @@ -20,7 +20,7 @@ The package comes in a several variants: - [32 bit, Release mode only](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=destdir-32.7z) (38 MB, 520 MB unpacked) - [32 bit, Release and Debug mode](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=destdir-32-dev.7z) (74 MB, 1.1 GB unpacked) -When unsure, use the _Release mode only_ variant, the _Release and Debug_ variant is only needed for debugging & developement. +When unsure, use the _Release mode only_ variant, the _Release and Debug_ variant is only needed for debugging & development. If you're unsure where to unpack the package, unpack it into `C:\local\` (but it can really be anywhere). @@ -46,13 +46,13 @@ Conversely, if you're using Visual Studio version other than 2013, the version n If `cmake` has finished without errors, go to the build directory and open the `Slic3r.sln` solution file in Visual Studio. Before building, make sure you're building the right project (use one of those starting with `slic3r_app_...`) and that you're building -with the right configuration, ie. _Release_ vs. _Debug_. When unsure, choose _Release_. +with the right configuration, i.e. _Release_ vs. _Debug_. When unsure, choose _Release_. Note that you won't be able to build a _Debug_ variant against a _Release_-only dependencies package. #### Installing using the `INSTALL` project Slic3r PE can be run from the Visual Studio or from Visual Studio's build directory (`src\Release` or `src\Debug`), -but for longer-term usage you migth want to install somewhere using the `INSTALL` project. +but for longer-term usage you might want to install somewhere using the `INSTALL` project. By default, this installs into `C:\Program Files\Slic3r`. To customize the install path, use the `-DCMAKE_INSTALL_PREFIX=` when invoking `cmake`. @@ -91,7 +91,11 @@ You can also use the Visual Studio GUI or other generators as mentioned above. The `DESTDIR` option is the location where the bundle will be installed. This may be customized. If you leave it empty, the `DESTDIR` will be places inside the same `build` directory. -Note that the build variant that you may choose using Visual Studio (ie. _Release_ or _Debug_ etc.) when building the dependency package is **not relevant**. +Warning: If the `build` directory is nested too deep inside other folders, various file paths during the build +become too long and the build might fail due to file writing errors. For this reason, it is recommended to +place the `build` directory relatively close to the drive root. + +Note that the build variant that you may choose using Visual Studio (i.e. _Release_ or _Debug_ etc.) when building the dependency package is **not relevant**. The dependency build will by default build _both_ the _Release_ and _Debug_ variants regardless of what you choose in Visual Studio. You can disable building of the debug variant by passing the `-DDEP_DEBUG=OFF` option to CMake, this will only produce a _Release_ build. diff --git a/doc/Localization_guide.md b/doc/Localization_guide.md index 33fcc1c08..656f225ef 100644 --- a/doc/Localization_guide.md +++ b/doc/Localization_guide.md @@ -2,20 +2,20 @@ The purpose of this guide is to describe how to contribute to the Slic3rPE translations. We use GNUgettext for extracting string resources from the project and PoEdit for editing translations. -Those are possible to download here: +Those can be downloaded here: - https://sourceforge.net/directory/os:windows/?q=gnu+gettext GNUgettext package contains a set of tools to extract strings from the source code and to create the translation Catalog. - https://poedit.net PoEdit provides good interface for the translators. -After GNUgettext is installed it is recommended to add the path to gettext/bin to PATH variable. +After GNUgettext is installed, it is recommended to add the path to gettext/bin to PATH variable. -Full manual for GNUgettext you can see here: http://www.gnu.org/software/gettext/manual/gettext.html +Full manual for GNUgettext can be seen here: http://www.gnu.org/software/gettext/manual/gettext.html -### Scenario 1. How do I add a translation or fix the existing translation +### Scenario 1. How do I add a translation or fix an existing translation 1. Get PO-file from corresponding folder here: https://github.com/prusa3d/Slic3r/tree/master/resources/localization 2. Open this file in PoEdit as "Edit a translation" -3. Apply your corrections to translation +3. Apply your corrections to the translation 4. Push changed Slic3rPE.po and Slic3rPE.mo (will create automatically after saving of Slic3r.po in PoEdit) back to to the enter folder. ### Scenario 2. How do I add a new language support @@ -23,10 +23,10 @@ https://github.com/prusa3d/Slic3r/tree/master/resources/localization https://github.com/prusa3d/Slic3r/tree/master/resources/localization 2. Open it in PoEdit for "Create new translation" 3. Select Translation Language (for example French). -4. As a result you will have fr.po - the file contaning translation to French. -Notice. When the transtation is complete you need to: +4. As a result you will have fr.po - the file containing translation to French. +Notice. When the translation is complete you need to: - Rename the file to Slic3rPE.po - - Click "Save file" button. Slic3rPE.mo will be created immediatly + - Click "Save file" button. Slic3rPE.mo will be created immediately - Both Slic3rPE.po and Slic3rPE.mo have to be saved here: https://github.com/prusa3d/Slic3r/tree/master/resources/localization/fr ( name of folder "fr" means "French" - the translation language). @@ -37,11 +37,11 @@ Each string resource in Slic3rPE available for translation needs to be explicitl auto msg = L("This message to be localized") ``` To get translated text use one of needed macro/function (`_(s)`, `_CHB(s)` or `L_str(s)` ). -If you add new file resourse, add it to list of files contaned macro `L()` +If you add new file resource, add it to the list of files containing macro `L()` ### Scenario 4. How do I use GNUgettext to localize my own application taking Slic3rPE as an example -1. For conviniance create list of files with this macro `L(s)`. We have +1. For convenience create a list of files with this macro `L(s)`. We have https://github.com/prusa3d/Slic3r/tree/master/resources/localization/list.txt. 2. Create template file(*.POT) with GNUgettext command: @@ -50,11 +50,11 @@ https://github.com/prusa3d/Slic3r/tree/master/resources/localization/list.txt. ``` Use flag `--from-code=UTF-8` to specify that the source strings are in UTF-8 encoding - Use flag `--debug` to correctly extract formated strings(used %d, %s etc.) + Use flag `--debug` to correctly extract formatted strings(used %d, %s etc.) 3. Create PO- and MO-files for your project as described above. -4. To merge old PO-file with strings from creaded new POT-file use command: +4. To merge old PO-file with strings from created new POT-file use command: ``` msgmerge -N -o new.po old.po new.pot ``` @@ -71,6 +71,6 @@ https://github.com/prusa3d/Slic3r/tree/master/resources/localization/list.txt. ``` Notice, in this Catalog it will be totally same strings for initial text and translated. -When you have Catalog to translation open POT or PO file in PoEdit and start to translation. -It's very important to keep attention to every gaps and punctuation. Especially with -formated strings. (used %d, %s etc.) \ No newline at end of file +When you have Catalog to translation open POT or PO file in PoEdit and start to translation, +it's very important to keep attention to every gaps and punctuation. Especially with +formatted strings. (using %d, %s, etc.) \ No newline at end of file diff --git a/doc/updating/Updatig.md b/doc/updating/Updating.md similarity index 79% rename from doc/updating/Updatig.md rename to doc/updating/Updating.md index 13a2de144..12727fb15 100644 --- a/doc/updating/Updatig.md +++ b/doc/updating/Updating.md @@ -12,7 +12,7 @@ There are three new features: - _System preset_: These are the presets that come with Slic3r PE installation. They come from a vendor configuration bundle (not individual files like before). They are **read-only** – a user cannot modify them, but may instead create a derived User preset based on a System preset - _User preset_: These are regular presets stored in files just like before. Additionally, they may be derived (inherited) from one of the System presets -A derived User preset keeps track of wich settings are inherited from the parent System preset and which are modified by the user. When a system preset is updated (either via installation of a new Slic3r or automatically from the internet), in a User preset the settings that are modified by the user will stay that way, while the ones that are inherited reflect the updated System preset. +A derived User preset keeps track of which settings are inherited from the parent System preset and which are modified by the user. When a system preset is updated (either via installation of a new Slic3r or automatically from the internet), in a User preset the settings that are modified by the user will stay that way, while the ones that are inherited reflect the updated System preset. This system ensures that we don't overwrite user's settings when there is an update to the built in presets. @@ -25,9 +25,9 @@ A settings modified in a User preset has an open lock icon: ![a user setting](setting_user.png) -Clickign the open lock icon restored the system setting. +Clicking the open lock icon restores the system setting. -Additionaly, any setting that is modified but not yet saved onto disk is represented by orange label and a back-arrow: +Additionally, any setting that is modified but not yet saved onto disk is represented by orange label and a back-arrow: ![a modified setting](setting_mod.png) diff --git a/resources/icons/add.svg b/resources/icons/add.svg new file mode 100644 index 000000000..8a9b253de --- /dev/null +++ b/resources/icons/add.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/resources/icons/arrange.svg b/resources/icons/arrange.svg new file mode 100644 index 000000000..4f30e979e --- /dev/null +++ b/resources/icons/arrange.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/bed/mk2_4096_bottom.png b/resources/icons/bed/mk2_4096_bottom.png deleted file mode 100644 index dcded1f7e..000000000 Binary files a/resources/icons/bed/mk2_4096_bottom.png and /dev/null differ diff --git a/resources/icons/bed/mk2_4096_top.png b/resources/icons/bed/mk2_4096_top.png deleted file mode 100644 index 1fe8c6706..000000000 Binary files a/resources/icons/bed/mk2_4096_top.png and /dev/null differ diff --git a/resources/icons/bed/mk2_8192_bottom.png b/resources/icons/bed/mk2_8192_bottom.png deleted file mode 100644 index ccc83d311..000000000 Binary files a/resources/icons/bed/mk2_8192_bottom.png and /dev/null differ diff --git a/resources/icons/bed/mk2_8192_top.png b/resources/icons/bed/mk2_8192_top.png deleted file mode 100644 index 5e2ab6a50..000000000 Binary files a/resources/icons/bed/mk2_8192_top.png and /dev/null differ diff --git a/resources/icons/bed/mk2_bottom.png b/resources/icons/bed/mk2_bottom.png deleted file mode 100644 index ed94b0d62..000000000 Binary files a/resources/icons/bed/mk2_bottom.png and /dev/null differ diff --git a/resources/icons/bed/mk2_top.png b/resources/icons/bed/mk2_top.png deleted file mode 100644 index 63b034a99..000000000 Binary files a/resources/icons/bed/mk2_top.png and /dev/null differ diff --git a/resources/icons/bed/mk3_4096_bottom.png b/resources/icons/bed/mk3_4096_bottom.png deleted file mode 100644 index 4a02aaeff..000000000 Binary files a/resources/icons/bed/mk3_4096_bottom.png and /dev/null differ diff --git a/resources/icons/bed/mk3_4096_top.png b/resources/icons/bed/mk3_4096_top.png deleted file mode 100644 index a2a1824dd..000000000 Binary files a/resources/icons/bed/mk3_4096_top.png and /dev/null differ diff --git a/resources/icons/bed/mk3_8192_bottom.png b/resources/icons/bed/mk3_8192_bottom.png deleted file mode 100644 index aaa624373..000000000 Binary files a/resources/icons/bed/mk3_8192_bottom.png and /dev/null differ diff --git a/resources/icons/bed/mk3_8192_top.png b/resources/icons/bed/mk3_8192_top.png deleted file mode 100644 index 6b1e41e5c..000000000 Binary files a/resources/icons/bed/mk3_8192_top.png and /dev/null differ diff --git a/resources/icons/bed/mk3_bottom.png b/resources/icons/bed/mk3_bottom.png deleted file mode 100644 index f72cd93da..000000000 Binary files a/resources/icons/bed/mk3_bottom.png and /dev/null differ diff --git a/resources/icons/bed/mk3_top.png b/resources/icons/bed/mk3_top.png deleted file mode 100644 index e6f3261b5..000000000 Binary files a/resources/icons/bed/mk3_top.png and /dev/null differ diff --git a/resources/icons/bed/sl1_4096_bottom.png b/resources/icons/bed/sl1_4096_bottom.png deleted file mode 100644 index 0998c0e3b..000000000 Binary files a/resources/icons/bed/sl1_4096_bottom.png and /dev/null differ diff --git a/resources/icons/bed/sl1_4096_top.png b/resources/icons/bed/sl1_4096_top.png deleted file mode 100644 index 7abd8b049..000000000 Binary files a/resources/icons/bed/sl1_4096_top.png and /dev/null differ diff --git a/resources/icons/bed/sl1_8192_bottom.png b/resources/icons/bed/sl1_8192_bottom.png deleted file mode 100644 index 4e38fc920..000000000 Binary files a/resources/icons/bed/sl1_8192_bottom.png and /dev/null differ diff --git a/resources/icons/bed/sl1_8192_top.png b/resources/icons/bed/sl1_8192_top.png deleted file mode 100644 index c01d59c7b..000000000 Binary files a/resources/icons/bed/sl1_8192_top.png and /dev/null differ diff --git a/resources/icons/bed/sl1_bottom.png b/resources/icons/bed/sl1_bottom.png deleted file mode 100644 index 04272ac22..000000000 Binary files a/resources/icons/bed/sl1_bottom.png and /dev/null differ diff --git a/resources/icons/bed/sl1_top.png b/resources/icons/bed/sl1_top.png deleted file mode 100644 index 49305f531..000000000 Binary files a/resources/icons/bed/sl1_top.png and /dev/null differ diff --git a/resources/icons/cut.svg b/resources/icons/cut.svg new file mode 100644 index 000000000..c5c952742 --- /dev/null +++ b/resources/icons/cut.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/resources/icons/delete_all.svg b/resources/icons/delete_all.svg new file mode 100644 index 000000000..80e2e503c --- /dev/null +++ b/resources/icons/delete_all.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/editor.svg b/resources/icons/editor.svg new file mode 100644 index 000000000..5866ce055 --- /dev/null +++ b/resources/icons/editor.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/resources/icons/empty_icon.png b/resources/icons/empty_icon.png new file mode 100644 index 000000000..2dd8f0afe Binary files /dev/null and b/resources/icons/empty_icon.png differ diff --git a/resources/icons/gizmos.png b/resources/icons/gizmos.png new file mode 100644 index 000000000..448e826b8 Binary files /dev/null and b/resources/icons/gizmos.png differ diff --git a/resources/icons/instance_add.svg b/resources/icons/instance_add.svg new file mode 100644 index 000000000..5ef492cfa --- /dev/null +++ b/resources/icons/instance_add.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + diff --git a/resources/icons/instance_remove.svg b/resources/icons/instance_remove.svg new file mode 100644 index 000000000..466752ea8 --- /dev/null +++ b/resources/icons/instance_remove.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + diff --git a/resources/icons/layers.svg b/resources/icons/layers.svg new file mode 100644 index 000000000..7718a8cbd --- /dev/null +++ b/resources/icons/layers.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/move.svg b/resources/icons/move.svg new file mode 100644 index 000000000..019ea8ca7 --- /dev/null +++ b/resources/icons/move.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/place.svg b/resources/icons/place.svg new file mode 100644 index 000000000..6acc2451e --- /dev/null +++ b/resources/icons/place.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/preview.svg b/resources/icons/preview.svg new file mode 100644 index 000000000..658bba2c6 --- /dev/null +++ b/resources/icons/preview.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/remove.svg b/resources/icons/remove.svg new file mode 100644 index 000000000..acd21256c --- /dev/null +++ b/resources/icons/remove.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + diff --git a/resources/icons/rotate.svg b/resources/icons/rotate.svg new file mode 100644 index 000000000..508c4f39d --- /dev/null +++ b/resources/icons/rotate.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/scale.svg b/resources/icons/scale.svg new file mode 100644 index 000000000..ee9cef95a --- /dev/null +++ b/resources/icons/scale.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/sla_supports.svg b/resources/icons/sla_supports.svg new file mode 100644 index 000000000..119fb6afc --- /dev/null +++ b/resources/icons/sla_supports.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/split_objects.svg b/resources/icons/split_objects.svg new file mode 100644 index 000000000..a7ccc5df8 --- /dev/null +++ b/resources/icons/split_objects.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/resources/icons/split_parts.svg b/resources/icons/split_parts.svg new file mode 100644 index 000000000..82a292770 --- /dev/null +++ b/resources/icons/split_parts.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 407883544..a6f9deb28 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,14 +1,22 @@ min_slic3r_version = 1.42.0-alpha6 -0.8.0-alpha7 -0.8.0-alpha6 +0.8.0-beta Updated SLA profiles +0.8.0-alpha9 Updated SLA and FFF profiles +0.8.0-alpha8 Updated SLA profiles +0.8.0-alpha7 Updated SLA profiles +0.8.0-alpha6 Updated SLA profiles min_slic3r_version = 1.42.0-alpha -0.8.0-alpha +0.8.0-alpha Updated SLA profiles 0.4.0-alpha4 Updated SLA profiles 0.4.0-alpha3 Update of SLA profiles 0.4.0-alpha2 First SLA profiles min_slic3r_version = 1.41.3-alpha +0.4.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +0.4.3 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +0.4.2 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt +0.4.1 New MK2.5S and MK3S FW versions 0.4.0 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt min_slic3r_version = 1.41.1 +0.3.5 New MK2.5 and MK3 FW versions 0.3.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt 0.3.3 Prusament PETG released 0.3.2 New MK2.5 and MK3 FW versions @@ -41,6 +49,7 @@ min_slic3r_version = 1.41.0-alpha 0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters min_slic3r_version = 1.40.0 +0.1.13 New MK2.5 and MK3 FW versions 0.1.12 New MK2.5 and MK3 FW versions 0.1.11 fw version changed to 3.3.1 0.1.10 MK3 jerk and acceleration update diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index e11e00312..2d455ffb6 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -8,7 +8,7 @@ full_name = Prusa {technology} Technology Printers # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.8.0-alpha7 +config_version = 0.8.0-beta # Where to get the updates from? config_update_url = https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/PrusaResearch/ @@ -198,6 +198,14 @@ travel_speed = 180 wipe_tower_x = 170 wipe_tower_y = 125 +[print:*MK306*] +fill_pattern = gyroid +fill_density = 15% +single_extruder_multi_material_priming = 0 +travel_speed = 180 +wipe_tower_x = 170 +wipe_tower_y = 125 + # Print parameters common to a 0.25mm diameter nozzle. [print:*0.25nozzle*] external_perimeter_extrusion_width = 0.25 @@ -213,6 +221,38 @@ support_material_interface_spacing = 0.15 support_material_spacing = 1 support_material_xy_spacing = 150% +[print:*0.25nozzleMK3*] +external_perimeter_extrusion_width = 0.25 +extrusion_width = 0.25 +first_layer_extrusion_width = 0.35 +infill_extrusion_width = 0.25 +perimeter_extrusion_width = 0.25 +solid_infill_extrusion_width = 0.25 +top_infill_extrusion_width = 0.25 +support_material_extrusion_width = 0.2 +support_material_interface_layers = 0 +support_material_interface_spacing = 0.15 +support_material_spacing = 1 +support_material_xy_spacing = 150% +perimeter_speed = 30 +external_perimeter_speed = 20 +small_perimeter_speed = 20 +infill_speed = 45 +solid_infill_speed = 45 +top_solid_infill_speed = 30 +support_material_speed = 40 +bridge_speed = 20 +gap_fill_speed = 30 +perimeter_acceleration = 500 +infill_acceleration = 1000 +bridge_acceleration = 500 +first_layer_acceleration = 500 +default_acceleration = 1000 +max_print_speed = 80 +perimeters = 3 +fill_pattern = grid +fill_density = 20% + # Print parameters common to a 0.6mm diameter nozzle. [print:*0.6nozzle*] external_perimeter_extrusion_width = 0.61 @@ -224,6 +264,18 @@ solid_infill_extrusion_width = 0.65 top_infill_extrusion_width = 0.6 support_material_extrusion_width = 0.55 +[print:*0.6nozzleMK3*] +external_perimeter_extrusion_width = 0.65 +extrusion_width = 0.65 +first_layer_extrusion_width = 0.65 +infill_extrusion_width = 0.7 +perimeter_extrusion_width = 0.65 +solid_infill_extrusion_width = 0.7 +top_infill_extrusion_width = 0.6 +support_material_extrusion_width = 0.55 +bridge_flow_ratio = 0.95 +bridge_speed = 25 + [print:*soluble_support*] overhangs = 1 skirts = 0 @@ -298,6 +350,9 @@ support_material_speed = 20 [print:0.05mm ULTRADETAIL 0.25 nozzle MK3] inherits = *0.05mm*; *0.25nozzle*; *MK3* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 +fill_pattern = grid +fill_density = 20% +first_layer_extrusion_width = 0.35 # XXXXXXXXXXXXXXXXXXXX # XXX--- 0.07mm ---XXX @@ -336,6 +391,17 @@ fill_pattern = gyroid compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 and ! single_extruder_multi_material top_infill_extrusion_width = 0.4 +[print:0.07mm ULTRADETAIL 0.25 nozzle MK3] +inherits = *0.07mm*; *0.25nozzle*; *MK3* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 and num_extruders==1 +infill_speed = 30 +solid_infill_speed = 30 +support_material_speed = 30 +top_solid_infill_speed = 20 +fill_pattern = grid +fill_density = 20% +first_layer_extrusion_width = 0.35 + # XXXXXXXXXXXXXXXXXXXX # XXX--- 0.10mm ---XXX # XXXXXXXXXXXXXXXXXXXX @@ -392,29 +458,10 @@ top_solid_infill_speed = 30 # MK3 # [print:0.10mm DETAIL 0.25 nozzle MK3] -inherits = *0.10mm*; *0.25nozzle*; *MK3* -bridge_speed = 30 +inherits = *0.10mm*; *0.25nozzleMK3*; *MK3* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 -external_perimeter_speed = 35 -infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 200 -top_solid_infill_speed = 50 - -# MK3 # -[print:0.10mm DETAIL 0.6 nozzle MK3] -inherits = *0.10mm*; *0.6nozzle*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 200 -top_solid_infill_speed = 50 +fill_pattern = grid +fill_density = 20% # XXXXXXXXXXXXXXXXXXXX # XXX--- 0.15mm ---XXX @@ -541,17 +588,27 @@ support_material_with_sheath = 0 support_material_xy_spacing = 80% # MK3 # -[print:0.15mm OPTIMAL 0.25 nozzle MK3] -inherits = *0.15mm*; *0.25nozzle*; *MK3* -bridge_speed = 30 +[print:0.15mm QUALITY 0.25 nozzle MK3] +inherits = *0.15mm*; *0.25nozzleMK3*; *MK3* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.25 +fill_pattern = grid +fill_density = 20% + +# MK3 # +[print:0.15mm DETAIL 0.6 nozzle MK3] +inherits = *0.15mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 +infill_speed = 70 +max_print_speed = 100 perimeter_speed = 45 -solid_infill_speed = 200 -top_solid_infill_speed = 50 +solid_infill_speed = 70 +top_solid_infill_speed = 45 + +# XXXXXXXXXXXXXXXXXXXX +# XXX--- 0.20mm ---XXX +# XXXXXXXXXXXXXXXXXXXX [print:*0.20mm*] inherits = *common* @@ -567,23 +624,6 @@ solid_infill_speed = 50 top_infill_extrusion_width = 0.4 top_solid_layers = 5 -# MK3 # -[print:0.15mm OPTIMAL 0.6 nozzle MK3] -inherits = *0.15mm*; *0.6nozzle*; *MK3* -bridge_speed = 30 -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 -external_perimeter_speed = 35 -infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 -perimeter_speed = 45 -solid_infill_speed = 200 -top_solid_infill_speed = 50 - -# XXXXXXXXXXXXXXXXXXXX -# XXX--- 0.20mm ---XXX -# XXXXXXXXXXXXXXXXXXXX - # MK2 # [print:0.20mm 100mms Linear Advance] inherits = *0.20mm* @@ -674,17 +714,68 @@ support_material_with_sheath = 0 support_material_xy_spacing = 80% # MK3 # -[print:0.20mm FAST 0.6 nozzle MK3] -inherits = *0.20mm*; *0.6nozzle*; *MK3* -bridge_speed = 30 +[print:0.20mm DETAIL 0.6 nozzle MK3] +inherits = *0.20mm*; *0.6nozzleMK3*; *MK306* compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 external_perimeter_speed = 35 infill_acceleration = 1250 -infill_speed = 200 -max_print_speed = 200 +infill_speed = 70 +max_print_speed = 100 perimeter_speed = 45 -solid_infill_speed = 200 -top_solid_infill_speed = 50 +solid_infill_speed = 70 +top_solid_infill_speed = 45 + +# XXXXXXXXXXXXXXXXXXXX +# XXX--- 0.30mm ---XXX +# XXXXXXXXXXXXXXXXXXXX + +[print:*0.30mm*] +inherits = *common* +bottom_solid_layers = 4 +bridge_flow_ratio = 0.95 +external_perimeter_speed = 40 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.3 +perimeter_acceleration = 800 +perimeter_speed = 50 +solid_infill_speed = 50 +top_infill_extrusion_width = 0.4 +top_solid_layers = 4 + +[print:0.30mm QUALITY 0.6 nozzle MK3] +inherits = *0.30mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1250 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 + +[print:0.30mm DRAFT MK3] +inherits = *0.30mm*; *MK3* +bottom_solid_layers = 3 +bridge_speed = 30 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.4 +external_perimeter_speed = 35 +infill_acceleration = 1250 +infill_speed = 85 +max_print_speed = 200 +perimeter_speed = 50 +small_perimeter_speed = 30 +solid_infill_speed = 80 +top_solid_infill_speed = 40 +support_material_speed = 45 +external_perimeter_extrusion_width = 0.6 +extrusion_width = 0.5 +first_layer_extrusion_width = 0.42 +infill_extrusion_width = 0.5 +perimeter_extrusion_width = 0.5 +solid_infill_extrusion_width = 0.5 +top_infill_extrusion_width = 0.45 +support_material_extrusion_width = 0.35 # XXXXXXXXXXXXXXXXXXXX # XXX--- 0.35mm ---XXX @@ -742,6 +833,57 @@ support_material_interface_layers = 2 support_material_with_sheath = 0 support_material_xy_spacing = 150% +# MK3 # +[print:0.35mm SPEED 0.6 nozzle MK3] +inherits = *0.35mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1250 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 +external_perimeter_extrusion_width = 0.68 +perimeter_extrusion_width = 0.68 + +# XXXXXXXXXXXXXXXXXXXX +# XXX--- 0.40mm ---XXX +# XXXXXXXXXXXXXXXXXXXX + +[print:*0.40mm*] +inherits = *common* +bottom_solid_layers = 3 +external_perimeter_extrusion_width = 0.6 +external_perimeter_speed = 40 +first_layer_extrusion_width = 0.65 +infill_acceleration = 2000 +infill_speed = 60 +layer_height = 0.4 +perimeter_acceleration = 800 +perimeter_extrusion_width = 0.65 +perimeter_speed = 50 +solid_infill_extrusion_width = 0.65 +solid_infill_speed = 60 +top_solid_infill_speed = 40 +top_solid_layers = 4 + +# MK3 # +[print:0.40mm DRAFT 0.6 nozzle MK3] +inherits = *0.40mm*; *0.6nozzleMK3*; *MK306* +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and nozzle_diameter[0]==0.6 +external_perimeter_speed = 35 +infill_acceleration = 1250 +infill_speed = 70 +max_print_speed = 100 +perimeter_speed = 45 +solid_infill_speed = 70 +top_solid_infill_speed = 45 +external_perimeter_extrusion_width = 0.7 +perimeter_extrusion_width = 0.7 +infill_extrusion_width = 0.72 +solid_infill_extrusion_width = 0.72 + # XXXXXXXXXXXXXXXXXXXXXX # XXX----- MK2.5 ----XXX # XXXXXXXXXXXXXXXXXXXXXX @@ -837,7 +979,7 @@ filament_settings_id = "" filament_soluble = 0 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" +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif} ; Filament gcode" [filament:*PLA*] inherits = *common* @@ -854,6 +996,7 @@ first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 temperature = 210 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}15{else}30{endif} ; Filament gcode" [filament:*PET*] inherits = *common* @@ -869,9 +1012,14 @@ first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}22{else}45{endif} ; Filament gcode" temperature = 240 +[filament:*PET06*] +inherits = *PET* +compatible_printers_condition = nozzle_diameter[0]==0.6 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +filament_max_volumetric_speed = 15 + [filament:*ABS*] inherits = *common* bed_temperature = 110 @@ -889,6 +1037,7 @@ first_layer_temperature = 255 max_fan_speed = 30 min_fan_speed = 20 temperature = 255 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}15{else}30{endif} ; Filament gcode" [filament:*FLEX*] inherits = *common* @@ -916,10 +1065,11 @@ inherits = *PLA* # For now, all but selected filaments are disabled for the MMU 2.0 compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) extrusion_multiplier = 1.2 -filament_cost = 80.65 -filament_density = 4 +filament_cost = 56.64 +filament_density = 3.9 filament_colour = #804040 -filament_max_volumetric_speed = 10 +filament_max_volumetric_speed = 9 +filament_notes = "List of materials tested with standard print settings:\n\nColorFabb bronzeFill\nColorFabb brassFill\nColorFabb steelFill\nColorFabb copperFill" [filament:ColorFabb HT] inherits = *PET* @@ -943,19 +1093,31 @@ inherits = *PLA* filament_cost = 55.5 filament_density = 1.24 -[filament:ColorFabb Woodfil] +[filament:ColorFabb woodFill] inherits = *PLA* # For now, all but selected filaments are disabled for the MMU 2.0 compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) extrusion_multiplier = 1.2 filament_cost = 62.9 filament_density = 1.15 -filament_colour = #804040 +filament_colour = #dfc287 filament_max_volumetric_speed = 10 first_layer_temperature = 200 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 200 +[filament:ColorFabb corkFill] +inherits = *PLA* +compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +extrusion_multiplier = 1.2 +filament_cost = 45.45 +filament_density = 1.18 +filament_colour = #634d33 +filament_max_volumetric_speed = 6 +first_layer_temperature = 220 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +temperature = 220 + [filament:ColorFabb XT] inherits = *PET* filament_type = PET @@ -1011,7 +1173,7 @@ temperature = 260 inherits = *PET* filament_cost = 56.9 filament_density = 1.26 -filament_notes = "List of manufacturers tested with standart PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" +filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" [filament:E3D PC-ABS] inherits = *ABS* @@ -1046,7 +1208,7 @@ max_fan_speed = 50 min_fan_speed = 50 temperature = 275 -[filament:Fillamentum Timberfil] +[filament:Fillamentum Timberfill] inherits = *PLA* # For now, all but selected filaments are disabled for the MMU 2.0 compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) @@ -1063,19 +1225,19 @@ temperature = 190 inherits = *ABS* filament_cost = 27.82 filament_density = 1.04 -filament_notes = "List of materials tested with standart ABS print settings:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladec ABS" +filament_notes = "List of materials tested with standard ABS print settings:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladec ABS" [filament:Generic PET] inherits = *PET* filament_cost = 27.82 filament_density = 1.27 -filament_notes = "List of manufacturers tested with standart PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" +filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" [filament:Generic PLA] inherits = *PLA* filament_cost = 25.4 filament_density = 1.24 -filament_notes = "List of materials tested with standart PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_notes = "List of materials tested with standard PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" [filament:Polymaker PC-Max] inherits = *ABS* @@ -1094,11 +1256,11 @@ filament_density = 1.23 cooling = 0 fan_always_on = 0 filament_colour = #FFFFD7 -filament_max_volumetric_speed = 10 -filament_notes = "List of materials tested with standart PVA print settings:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'\nVerbatim BVOH" +filament_max_volumetric_speed = 4 +filament_notes = "List of materials tested with standard PVA print settings:\n\nPrimaSelect PVA+\nICE FILAMENTS PVA 'NAUGHTY NATURAL'" filament_ramming_parameters = "120 100 8.3871 8.6129 8.93548 9.22581 9.48387 9.70968 9.87097 10.0323 10.2258 10.4194 10.6452 10.8065| 0.05 8.34193 0.45 8.73548 0.95 9.34836 1.45 9.78385 1.95 10.0871 2.45 10.5161 2.95 10.8903 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" filament_soluble = 1 -filament_type = PVA +filament_type = PLA first_layer_temperature = 195 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 195 @@ -1107,7 +1269,7 @@ temperature = 195 inherits = *ABS* filament_cost = 27.82 filament_density = 1.08 -filament_notes = "List of materials tested with standart ABS print settings:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladec ABS" +filament_notes = "List of materials tested with standard ABS print settings:\n\nEsun ABS\nFil-A-Gehr ABS\nHatchboxABS\nPlasty Mladec ABS" [filament:*ABS MMU2*] inherits = Prusa ABS @@ -1150,7 +1312,8 @@ temperature = 220 inherits = *PET* filament_cost = 27.82 filament_density = 1.27 -filament_notes = "List of manufacturers tested with standart PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" +filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" +compatible_printers_condition = nozzle_diameter[0]!=0.6 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) [filament:Prusament PETG] inherits = *PET* @@ -1158,6 +1321,20 @@ first_layer_temperature = 240 temperature = 250 filament_cost = 24.99 filament_density = 1.27 +compatible_printers_condition = nozzle_diameter[0]!=0.6 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) + +[filament:Prusa PET 0.6 nozzle] +inherits = *PET06* +filament_cost = 27.82 +filament_density = 1.27 +filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" + +[filament:Prusament PETG 0.6 nozzle] +inherits = *PET06* +first_layer_temperature = 240 +temperature = 250 +filament_cost = 24.99 +filament_density = 1.27 [filament:*PET MMU2*] inherits = Prusa PET @@ -1189,7 +1366,7 @@ inherits = *PET MMU2* inherits = *PLA* filament_cost = 25.4 filament_density = 1.24 -filament_notes = "List of materials tested with standart PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_notes = "List of materials tested with standard PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" [filament:Prusament PLA] inherits = *PLA* @@ -1275,7 +1452,7 @@ fan_always_on = 0 fan_below_layer_time = 100 filament_colour = #FFFFD7 filament_max_volumetric_speed = 4 -filament_notes = "List of materials tested with standart PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_notes = "List of materials tested with standard PVA print settings:\n\nVerbatim BVOH" filament_soluble = 1 filament_type = PLA first_layer_bed_temperature = 60 @@ -1289,7 +1466,6 @@ temperature = 210 inherits = Verbatim BVOH compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material temperature = 195 -filament_notes = BVOH fan_always_on = 1 first_layer_temperature = 200 filament_cooling_final_speed = 1 @@ -1326,7 +1502,7 @@ filament_loading_speed = 14 filament_loading_speed_start = 19 filament_max_volumetric_speed = 4 filament_minimal_purge_on_wipe_tower = 5 -filament_notes = PVA +filament_notes = "List of materials tested with standard PVA print settings:\n\nPrimaSelect PVA+" filament_ramming_parameters = "120 110 3.83871 3.90323 3.96774 4.03226 4.09677 4.19355 4.3871 4.83871 5.67742 6.93548 8.54839 10.3226 11.9677 13.2581 14.129 14.5806| 0.05 3.8258 0.45 3.89676 0.95 4.05807 1.45 4.23548 1.95 5.18386 2.45 7.80651 2.95 11.5356 3.45 13.9872 3.95 14.7613 4.45 7.6 4.95 7.6" filament_soluble = 1 filament_toolchange_delay = 0 @@ -1356,7 +1532,7 @@ fan_always_on = 1 fan_below_layer_time = 100 filament_colour = #DEE0E6 filament_max_volumetric_speed = 5 -filament_notes = "List of materials tested with standart PLA print settings:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" +filament_notes = "List of materials tested with standard PLA print settings:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" filament_type = PLA first_layer_bed_temperature = 100 first_layer_temperature = 220 @@ -1368,14 +1544,15 @@ temperature = 220 [sla_print:*common*] compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_SL1.*/ layer_height = 0.05 -output_filename_format = [input_filename_base].dwz +output_filename_format = [input_filename_base].sl1 pad_edge_radius = 0.5 pad_enable = 1 pad_max_merge_distance = 50 -pad_wall_height = 3 +pad_wall_height = 0 pad_wall_thickness = 1 +pad_wall_slope = 90 support_base_diameter = 3 -support_base_height = 0.5 +support_base_height = 1 support_critical_angle = 45 support_density_at_45 = 250 support_density_at_horizontal = 500 @@ -1386,6 +1563,7 @@ support_max_bridge_length = 10 support_minimal_z = 0 support_object_elevation = 5 support_pillar_diameter = 1 +support_pillar_connection_mode = zigzag support_pillar_widening_factor = 0 supports_enable = 1 @@ -1451,6 +1629,11 @@ inherits = *common 0.025* exposure_time = 5 initial_exposure_time = 35 +[sla_material:SL1 Orange solid 0.025] +inherits = *common 0.025* +exposure_time = 5 +initial_exposure_time = 35 + ########### Materials 0.05 [sla_material:3DM-HTR140 (high temperature) 0.05] @@ -1468,11 +1651,31 @@ inherits = *common 0.05* exposure_time = 8 initial_exposure_time = 45 +[sla_material:Bluecast Keramaster Dental 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 45 + +[sla_material:Bluecast LCD-DLP Original 0.05] +inherits = *common 0.05* +exposure_time = 10 +initial_exposure_time = 60 + [sla_material:Bluecast Phrozen Wax 0.05] inherits = *common 0.05* exposure_time = 10 initial_exposure_time = 55 +[sla_material:Bluecast S+ 0.05] +inherits = *common 0.05* +exposure_time = 9 +initial_exposure_time = 45 + +[sla_material:Bluecast X2 0.05] +inherits = *common 0.05* +exposure_time = 10 +initial_exposure_time = 60 + [sla_material:Jamg He PJHC-00 Yellow 0.05] inherits = *common 0.05* exposure_time = 7 @@ -1485,7 +1688,7 @@ initial_exposure_time = 45 [sla_material:Jamg He PJHC-30 Orange 0.05] inherits = *common 0.05* -exposure_time = 7 +exposure_time = 7.5 initial_exposure_time = 45 [sla_material:Jamg He PJHC-60 Gray 0.05] @@ -1523,8 +1726,6 @@ inherits = *common 0.05* exposure_time = 7 initial_exposure_time = 40 -# v2 - [sla_material:3DM-ABS 0.05] inherits = *common 0.05* exposure_time = 9 @@ -1570,11 +1771,101 @@ inherits = *common 0.05* exposure_time = 6.5 initial_exposure_time = 40 +[sla_material:Harz Labs Model Resin Cherry 0.05] +inherits = *common 0.05* +exposure_time = 8 +initial_exposure_time = 45 + +[sla_material:Jamg He CRX-70C High Tenacity Black 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 40 + +[sla_material:Jamg He MC-2000 Casting Green 0.05] +inherits = *common 0.05* +exposure_time = 13 +initial_exposure_time = 40 + +[sla_material:Jamg He PJHC-00 Solid Yellow 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 40 + +[sla_material:Jamg He PJHC-20 White 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 45 + +[sla_material:Jamg He PJHC-80 Transparent Green 0.05] +inherits = *common 0.05* +exposure_time = 8 +initial_exposure_time = 45 + +[sla_material:Jamg He PJHC-80 Transparent Red 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 45 + +[sla_material:Jamg He PJHC-81 Solid Maroon 0.05] +inherits = *common 0.05* +exposure_time = 9 +initial_exposure_time = 45 + +[sla_material:Jamg He PJHC-90 Solid Pink 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 40 + +[sla_material:Jamg He RJHC-00 Yellow Flexible 0.05] +inherits = *common 0.05* +exposure_time = 9 +initial_exposure_time = 40 + +[sla_material:Jamg He RJHC-10 Clear Flexible 0.05] +inherits = *common 0.05* +exposure_time = 9 +initial_exposure_time = 40 + +[sla_material:Jamg He RJHC-20 White Flexible 0.05] +inherits = *common 0.05* +exposure_time = 9 +initial_exposure_time = 40 + +[sla_material:Jamg He RJHC-50 Blue Flexible 0.05] +inherits = *common 0.05* +exposure_time = 9 +initial_exposure_time = 40 + +[sla_material:Jamg He RJHC-70 Black Flexible 0.05] +inherits = *common 0.05* +exposure_time = 9 +initial_exposure_time = 40 + +[sla_material:Jamg He RJHC-81 Red Flexible 0.05] +inherits = *common 0.05* +exposure_time = 9 +initial_exposure_time = 40 + +[sla_material:SL1 Orange solid 0.05] +inherits = *common 0.05* +exposure_time = 7.5 +initial_exposure_time = 45 + +[sla_material:SL1 Red transparent 0.05] +inherits = *common 0.05* +exposure_time = 7.5 +initial_exposure_time = 45 + ########### Materials 0.035 [sla_material:Jamg He PJHC-30 Orange 0.035] inherits = *common 0.035* -exposure_time = 9 +exposure_time = 6 +initial_exposure_time = 35 + +[sla_material:SL1 Orange solid 0.035] +inherits = *common 0.035* +exposure_time = 6 initial_exposure_time = 35 ########### Materials 0.1 @@ -1584,6 +1875,11 @@ inherits = *common 0.1* exposure_time = 10 initial_exposure_time = 45 +[sla_material:SL1 Orange solid 0.1] +inherits = *common 0.1* +exposure_time = 10 +initial_exposure_time = 45 + [printer:*common*] printer_technology = FFF bed_shape = 0x0,250x0,250x210,0x210 @@ -1750,19 +2046,19 @@ min_layer_height = 0.1 inherits = Original Prusa i3 MK2S printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.5.1 ; tell printer latest fw version\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 +start_gcode = M115 U3.6.0 ; tell printer latest fw version\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 [printer:Original Prusa i3 MK2.5 0.25 nozzle] inherits = Original Prusa i3 MK2S 0.25 nozzle printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.5.1 ; tell printer latest fw version\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 +start_gcode = M115 U3.6.0 ; tell printer latest fw version\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 [printer:Original Prusa i3 MK2.5 0.6 nozzle] inherits = Original Prusa i3 MK2S 0.6 nozzle printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.5.1 ; tell printer latest fw version\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 +start_gcode = M115 U3.6.0 ; tell printer latest fw version\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 [printer:Original Prusa i3 MK2.5 MMU2 Single] inherits = Original Prusa i3 MK2.5; *mm2* @@ -1791,7 +2087,7 @@ machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL MK2.5 default_filament_profile = Prusament PLA 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.5\n -start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.6.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK2.5 MMU2] @@ -1825,20 +2121,23 @@ single_extruder_multi_material = 1 # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.6.0 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n [printer:Original Prusa i3 MK2.5S] inherits = Original Prusa i3 MK2.5 printer_model = MK2.5S +start_gcode = M115 U3.6.0 ; tell printer latest fw version\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 [printer:Original Prusa i3 MK2.5S 0.25 nozzle] inherits = Original Prusa i3 MK2.5 0.25 nozzle printer_model = MK2.5S +start_gcode = M115 U3.6.0 ; tell printer latest fw version\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 [printer:Original Prusa i3 MK2.5S 0.6 nozzle] inherits = Original Prusa i3 MK2.5 0.6 nozzle printer_model = MK2.5S +start_gcode = M115 U3.6.0 ; tell printer latest fw version\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 [printer:Original Prusa i3 MK2.5S MMU2S Single] inherits = Original Prusa i3 MK2.5; *mm2s* @@ -1867,7 +2166,7 @@ machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL MK2.5 default_filament_profile = Prusament PLA 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.5\n -start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.6.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK2.5S MMU2S] @@ -1901,7 +2200,7 @@ single_extruder_multi_material = 1 # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.6.0 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n @@ -1933,7 +2232,7 @@ remaining_times = 1 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_lift_below = 209 max_print_height = 210 -start_gcode = M115 U3.5.1 ; tell printer latest fw version\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.075}100{else}95{endif} +start_gcode = M115 U3.6.0 ; tell printer latest fw version\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.075}100{else}95{endif} printer_model = MK3 default_print_profile = 0.15mm QUALITY MK3 @@ -1943,27 +2242,31 @@ nozzle_diameter = 0.25 max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 +start_gcode = M115 U3.6.0 ; tell printer latest fw version\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 E8.0 F700.0 ; intro line\nG1 X100.0 E12.5 F700.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} default_print_profile = 0.10mm DETAIL 0.25 nozzle MK3 [printer:Original Prusa i3 MK3 0.6 nozzle] inherits = Original Prusa i3 MK3 nozzle_diameter = 0.6 -max_layer_height = 0.35 -min_layer_height = 0.1 +max_layer_height = 0.40 +min_layer_height = 0.15 printer_variant = 0.6 -default_print_profile = 0.15mm OPTIMAL 0.6 nozzle MK3 +default_print_profile = 0.30mm QUALITY 0.6 nozzle MK3 [printer:Original Prusa i3 MK3S] inherits = Original Prusa i3 MK3 printer_model = MK3S +start_gcode = M115 U3.6.0 ; tell printer latest fw version\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.075}100{else}95{endif} [printer:Original Prusa i3 MK3S 0.25 nozzle] inherits = Original Prusa i3 MK3 0.25 nozzle printer_model = MK3S +start_gcode = M115 U3.6.0 ; tell printer latest fw version\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 E8.0 F700.0 ; intro line\nG1 X100.0 E12.5 F700.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} [printer:Original Prusa i3 MK3S 0.6 nozzle] inherits = Original Prusa i3 MK3 0.6 nozzle printer_model = MK3S +start_gcode = M115 U3.6.0 ; tell printer latest fw version\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.075}100{else}95{endif} [printer:*mm2*] inherits = Original Prusa i3 MK3 @@ -1993,7 +2296,7 @@ default_filament_profile = Prusament PLA MMU2 inherits = *mm2* single_extruder_multi_material = 0 default_filament_profile = Prusament PLA -start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.6.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK3 MMU2] @@ -2004,14 +2307,14 @@ inherits = *mm2* machine_max_acceleration_e = 8000,8000 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.6.0 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n [printer:Original Prusa i3 MK3S MMU2S Single] inherits = *mm2s* single_extruder_multi_material = 0 default_filament_profile = Prusament PLA -start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.6.0 ; tell printer latest fw version\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\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\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK3S MMU2S] @@ -2019,14 +2322,14 @@ inherits = *mm2s* machine_max_acceleration_e = 8000,8000 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M107\nM115 U3.5.1 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n +start_gcode = M107\nM115 U3.6.0 ; tell printer latest fw version\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\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200; home X axis\nM84 ; disable motors\n [printer:Original Prusa SL1] printer_technology = SLA printer_model = SL1 printer_variant = default -default_sla_material_profile = Jamg He Transparent Green 0.05 +default_sla_material_profile = Jamg He PJHC-30 Orange 0.05 default_sla_print_profile = 0.05 Normal bed_shape = 0.98x1.02,119.98x1.02,119.98x68.02,0.98x68.02 display_height = 68.04 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b38ae3665..d15fee470 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectory(qhull) add_subdirectory(Shiny) add_subdirectory(semver) add_subdirectory(imgui) +#add_subdirectory(test) # Adding libnest2d project for bin packing... set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d") @@ -60,6 +61,7 @@ if (SLIC3R_GUI) endif() if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + set (wxWidgets_CONFIG_OPTIONS "--toolkit=gtk${SLIC3R_GTK}") if (SLIC3R_WX_STABLE) find_package(wxWidgets 3.0 REQUIRED COMPONENTS base core adv html gl) else () @@ -88,9 +90,9 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/slic3r.rc.in ${CMAKE_CUR configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/slic3r.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/slic3r.manifest @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY) if (MSVC) - add_library(slic3r SHARED slic3r.cpp) + add_library(slic3r SHARED slic3r.cpp slic3r.hpp) else () - add_executable(slic3r slic3r.cpp) + add_executable(slic3r slic3r.cpp slic3r.hpp) endif () if (NOT MSVC) if(SLIC3R_GUI) @@ -153,19 +155,19 @@ endif () # Also the shim may load the Mesa software OpenGL renderer if the default renderer does not support OpenGL 2.0 and higher. if (MSVC) add_executable(slic3r_app_gui WIN32 slic3r_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/slic3r.rc) - target_compile_definitions(slic3r_app_gui PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE -DSLIC3R_WRAPPER_GUI) + target_compile_definitions(slic3r_app_gui PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE) add_dependencies(slic3r_app_gui slic3r) set_target_properties(slic3r_app_gui PROPERTIES OUTPUT_NAME "slic3r") add_executable(slic3r_app_console slic3r_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/slic3r.rc) - target_compile_definitions(slic3r_app_console PRIVATE -DSLIC3R_WRAPPER_CONSOLE -DSLIC3R_WRAPPER_NOGUI) + target_compile_definitions(slic3r_app_console PRIVATE -DSLIC3R_WRAPPER_CONSOLE) add_dependencies(slic3r_app_console slic3r) set_target_properties(slic3r_app_console PROPERTIES OUTPUT_NAME "slic3r-console") - add_executable(slic3r_app_noconsole WIN32 slic3r_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/slic3r.rc) - target_compile_definitions(slic3r_app_noconsole PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE -DSLIC3R_WRAPPER_NOGUI) - add_dependencies(slic3r_app_noconsole slic3r) - set_target_properties(slic3r_app_noconsole PROPERTIES OUTPUT_NAME "slic3r-noconsole") +# add_executable(slic3r_test slic3r.cpp ${CMAKE_CURRENT_BINARY_DIR}/slic3r.rc) +# target_compile_definitions(slic3r_test PRIVATE -DSLIC3R_WRAPPER_CONSOLE) +# add_dependencies(slic3r_test slic3r) +# set_target_properties(slic3r_test PROPERTIES OUTPUT_NAME "slic3r-test") endif () # Link the resources dir to where Slic3r GUI expects it @@ -213,7 +215,7 @@ if (WIN32) if (MSVC) install(TARGETS slic3r_app_gui RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}") install(TARGETS slic3r_app_console RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}") - install(TARGETS slic3r_app_noconsole RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}") +# install(TARGETS slic3r_test RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}") endif () else () install(TARGETS slic3r RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index 166eec330..5fc992b63 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -293,8 +293,8 @@ static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, stl_vertex * { // Index of a grid cell spaced by tolerance. typedef Eigen::Matrix Vec3i; - Vec3i vertex1 = (*a / tolerance).cast(); - Vec3i vertex2 = (*b / tolerance).cast(); + Vec3i vertex1 = ((*a - stl->stats.min) / tolerance).cast(); + Vec3i vertex2 = ((*b - stl->stats.min) / tolerance).cast(); static_assert(sizeof(Vec3i) == 12, "size of Vec3i incorrect"); if (vertex1 == vertex2) diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index 91bb82e00..c8c17ccd5 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -143,7 +143,7 @@ stl_generate_shared_vertices(stl_file *stl) { } void -stl_write_off(stl_file *stl, char *file) { +stl_write_off(stl_file *stl, const char *file) { int i; FILE *fp; char *error_msg; @@ -179,7 +179,7 @@ stl_write_off(stl_file *stl, char *file) { } void -stl_write_vrml(stl_file *stl, char *file) { +stl_write_vrml(stl_file *stl, const char *file) { int i; FILE *fp; char *error_msg; @@ -236,7 +236,7 @@ stl_write_vrml(stl_file *stl, char *file) { fclose(fp); } -void stl_write_obj (stl_file *stl, char *file) { +void stl_write_obj (stl_file *stl, const char *file) { int i; FILE* fp; diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 9c71f00f6..afff3deac 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -177,10 +177,10 @@ extern void stl_transform(stl_file *stl, const Eigen::Transformvertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]); } diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp index 81e29d286..71e434cbc 100644 --- a/src/admesh/stl_io.cpp +++ b/src/admesh/stl_io.cpp @@ -365,7 +365,7 @@ stl_write_quad_object(stl_file *stl, char *file) { } void -stl_write_dxf(stl_file *stl, char *file, char *label) { +stl_write_dxf(stl_file *stl, const char *file, char *label) { int i; FILE *fp; char *error_msg; diff --git a/src/avrdude/avrdude-slic3r.conf.h b/src/avrdude/avrdude-slic3r.conf.h deleted file mode 100644 index 7cc901336..000000000 --- a/src/avrdude/avrdude-slic3r.conf.h +++ /dev/null @@ -1,1188 +0,0 @@ -/* WARN: This file is auto-generated from `avrdude-slic3r.conf` */ -unsigned char avrdude_slic3r_conf[] = { - 0x0a, 0x23, 0x0a, 0x23, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, - 0x20, 0x61, 0x20, 0x62, 0x61, 0x73, 0x69, 0x63, 0x20, 0x6d, 0x69, 0x6e, - 0x69, 0x6d, 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, - 0x66, 0x69, 0x6c, 0x65, 0x20, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, - 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, - 0x76, 0x72, 0x64, 0x75, 0x64, 0x65, 0x2d, 0x73, 0x6c, 0x69, 0x63, 0x33, - 0x72, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x0a, 0x23, 0x20, 0x73, - 0x6f, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x69, 0x74, 0x20, 0x63, 0x61, - 0x6e, 0x20, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, - 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x6c, 0x6f, 0x6e, 0x65, 0x20, 0x6d, - 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x0a, 0x23, 0x0a, 0x23, 0x20, 0x4f, - 0x6e, 0x6c, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x69, 0x74, 0x73, - 0x20, 0x75, 0x73, 0x65, 0x66, 0x75, 0x6c, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x50, 0x72, 0x75, 0x73, 0x61, 0x33, 0x44, 0x20, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x20, 0x77, 0x65, 0x72, 0x65, 0x20, 0x63, 0x6f, 0x70, - 0x69, 0x65, 0x64, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x66, 0x72, 0x6f, - 0x6d, 0x20, 0x61, 0x76, 0x72, 0x64, 0x75, 0x64, 0x65, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x0a, 0x23, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x65, 0x65, 0x64, - 0x65, 0x64, 0x2c, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, - 0x61, 0x6e, 0x20, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, - 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, - 0x61, 0x76, 0x72, 0x64, 0x75, 0x64, 0x65, 0x2d, 0x73, 0x6c, 0x69, 0x63, - 0x33, 0x72, 0x0a, 0x23, 0x20, 0x76, 0x69, 0x61, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x2d, 0x43, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2d, - 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x0a, 0x23, 0x0a, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, - 0x6d, 0x65, 0x72, 0x0a, 0x20, 0x20, 0x69, 0x64, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x77, 0x69, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x3b, 0x0a, - 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x57, - 0x69, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x74, 0x79, - 0x70, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x77, 0x69, 0x72, 0x69, 0x6e, - 0x67, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x20, - 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3b, 0x0a, 0x3b, 0x0a, 0x0a, 0x70, - 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x0a, 0x20, 0x20, - 0x69, 0x64, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x61, 0x72, 0x64, - 0x75, 0x69, 0x6e, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x65, 0x73, - 0x63, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x41, 0x72, 0x64, 0x75, 0x69, 0x6e, - 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x22, 0x3b, - 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x72, - 0x69, 0x61, 0x6c, 0x3b, 0x0a, 0x3b, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x0a, 0x20, 0x20, 0x69, 0x64, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x61, 0x76, 0x72, 0x31, 0x30, 0x39, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x20, 0x20, 0x3d, - 0x20, 0x22, 0x41, 0x74, 0x6d, 0x65, 0x6c, 0x20, 0x41, 0x70, 0x70, 0x4e, - 0x6f, 0x74, 0x65, 0x20, 0x41, 0x56, 0x52, 0x31, 0x30, 0x39, 0x20, 0x42, - 0x6f, 0x6f, 0x74, 0x20, 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x22, 0x3b, - 0x0a, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x22, - 0x62, 0x75, 0x74, 0x74, 0x65, 0x72, 0x66, 0x6c, 0x79, 0x22, 0x3b, 0x0a, - 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x72, 0x69, - 0x61, 0x6c, 0x3b, 0x0a, 0x3b, 0x0a, 0x0a, 0x0a, 0x23, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x23, 0x20, - 0x41, 0x54, 0x6d, 0x65, 0x67, 0x61, 0x32, 0x35, 0x36, 0x30, 0x0a, 0x23, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x6d, 0x32, 0x35, 0x36, 0x30, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x41, 0x54, 0x6d, 0x65, 0x67, 0x61, 0x32, 0x35, 0x36, - 0x30, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x30, 0x78, 0x31, 0x65, 0x20, 0x30, 0x78, 0x39, 0x38, - 0x20, 0x30, 0x78, 0x30, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, - 0x61, 0x73, 0x5f, 0x6a, 0x74, 0x61, 0x67, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x79, 0x65, 0x73, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x73, 0x74, 0x6b, 0x35, 0x30, 0x30, 0x5f, 0x64, 0x65, - 0x76, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, - 0x42, 0x32, 0x3b, 0x0a, 0x23, 0x20, 0x20, 0x20, 0x20, 0x61, 0x76, 0x72, - 0x39, 0x31, 0x30, 0x5f, 0x64, 0x65, 0x76, 0x63, 0x6f, 0x64, 0x65, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x34, 0x33, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, 0x5f, 0x65, 0x72, 0x61, 0x73, 0x65, - 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x6c, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x30, 0x78, 0x44, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x62, 0x73, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x41, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x65, 0x74, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x67, 0x6d, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, - 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, - 0x20, 0x31, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x31, 0x20, 0x31, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, - 0x69, 0x70, 0x5f, 0x65, 0x72, 0x61, 0x73, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x73, 0x74, 0x61, 0x62, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x63, 0x6d, 0x64, 0x65, 0x78, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x73, 0x79, 0x6e, 0x63, 0x68, 0x6c, 0x6f, 0x6f, 0x70, 0x73, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x62, 0x79, 0x74, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, - 0x6c, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x6c, 0x6c, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x35, - 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x65, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x73, 0x74, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x70, 0x6f, 0x6c, 0x6c, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x78, 0x30, 0x45, 0x2c, - 0x20, 0x30, 0x78, 0x31, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x46, 0x2c, - 0x20, 0x30, 0x78, 0x31, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x32, 0x45, 0x2c, - 0x20, 0x30, 0x78, 0x33, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x32, 0x46, 0x2c, - 0x20, 0x30, 0x78, 0x33, 0x46, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x78, 0x34, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x35, - 0x45, 0x2c, 0x20, 0x30, 0x78, 0x34, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x35, - 0x46, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x37, - 0x45, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x37, - 0x46, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, - 0x78, 0x36, 0x36, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x36, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x37, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x37, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x41, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x41, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x42, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x42, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x78, 0x42, 0x45, 0x2c, - 0x20, 0x30, 0x78, 0x46, 0x44, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, - 0x20, 0x30, 0x78, 0x30, 0x31, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, - 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, - 0x20, 0x30, 0x78, 0x30, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, - 0x76, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x74, 0x61, 0x62, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x30, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x6d, 0x6f, - 0x64, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, - 0x61, 0x74, 0x63, 0x68, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x76, 0x74, - 0x67, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x77, - 0x65, 0x72, 0x6f, 0x66, 0x66, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x35, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x65, 0x74, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x6d, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x65, - 0x74, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x75, 0x73, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x68, 0x76, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x74, 0x61, 0x62, - 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, - 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, 0x65, - 0x72, 0x61, 0x73, 0x65, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, 0x69, 0x64, - 0x74, 0x68, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x63, 0x68, 0x69, 0x70, 0x65, 0x72, 0x61, 0x73, 0x65, 0x70, 0x6f, 0x6c, - 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x31, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, - 0x61, 0x6d, 0x66, 0x75, 0x73, 0x65, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, - 0x69, 0x64, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x66, 0x75, 0x73, - 0x65, 0x70, 0x6f, 0x6c, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, - 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6c, 0x6f, 0x63, 0x6b, 0x70, 0x75, 0x6c, - 0x73, 0x65, 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, - 0x6c, 0x6f, 0x63, 0x6b, 0x70, 0x6f, 0x6c, 0x6c, 0x74, 0x69, 0x6d, 0x65, - 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x69, 0x64, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x78, 0x33, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x70, - 0x6d, 0x63, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x35, 0x37, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x61, 0x6d, 0x70, 0x7a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x33, 0x62, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x66, 0x75, 0x6c, 0x6c, 0x70, - 0x61, 0x67, 0x65, 0x62, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x20, 0x3d, 0x20, 0x6e, 0x6f, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x6f, 0x63, 0x64, 0x72, 0x65, 0x76, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x34, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, - 0x20, 0x22, 0x65, 0x65, 0x70, 0x72, 0x6f, 0x6d, 0x22, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x64, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x6e, 0x6f, 0x3b, 0x20, 0x2f, 0x2a, 0x20, 0x6c, 0x65, 0x61, 0x76, 0x65, - 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x22, 0x6e, 0x6f, 0x22, 0x20, 0x2a, - 0x2f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x38, 0x3b, 0x20, 0x20, 0x2f, 0x2a, 0x20, 0x66, - 0x6f, 0x72, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x20, - 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x20, - 0x2a, 0x2f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x34, 0x30, 0x39, 0x36, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, - 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, - 0x65, 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x31, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, - 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, - 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, - 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, - 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, - 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, - 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, - 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, - 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x20, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x61, 0x64, - 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x6f, 0x20, 0x3d, 0x20, 0x22, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, - 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, - 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x77, 0x72, 0x69, - 0x74, 0x65, 0x70, 0x61, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, - 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, - 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x6f, 0x64, 0x65, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x34, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x72, - 0x65, 0x61, 0x64, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x32, - 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, - 0x22, 0x66, 0x6c, 0x61, 0x73, 0x68, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x64, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x79, 0x65, - 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x36, 0x32, 0x31, 0x34, 0x34, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x67, 0x65, - 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x30, - 0x32, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x34, 0x35, 0x30, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, - 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, - 0x3d, 0x20, 0x34, 0x35, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, - 0x5f, 0x70, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, - 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x72, 0x65, 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x32, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, - 0x5f, 0x6c, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x31, 0x35, 0x20, 0x61, - 0x31, 0x34, 0x20, 0x61, 0x31, 0x33, 0x20, 0x61, 0x31, 0x32, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, - 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, - 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, - 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, - 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, - 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x68, 0x69, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x61, 0x31, 0x35, 0x20, 0x61, 0x31, 0x34, 0x20, 0x61, 0x31, 0x33, 0x20, - 0x61, 0x31, 0x32, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, - 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, - 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, - 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x6f, 0x20, - 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, - 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x6c, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, - 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, - 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, - 0x6f, 0x61, 0x64, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x68, 0x69, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, - 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, - 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, - 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, - 0x20, 0x20, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x70, 0x61, 0x67, - 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x61, 0x31, 0x35, 0x20, 0x61, 0x31, 0x34, 0x20, 0x61, - 0x31, 0x33, 0x20, 0x61, 0x31, 0x32, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, - 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, - 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x61, - 0x64, 0x5f, 0x65, 0x78, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x61, 0x31, 0x36, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x6f, 0x64, 0x65, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x34, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, - 0x20, 0x72, 0x65, 0x61, 0x64, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x3d, - 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, - 0x79, 0x20, 0x22, 0x6c, 0x66, 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, - 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, - 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x68, 0x66, 0x75, - 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, - 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x69, 0x20, - 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, - 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, - 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, - 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, - 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, - 0x79, 0x20, 0x22, 0x65, 0x66, 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, - 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x6c, 0x6f, 0x63, - 0x6b, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x30, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, - 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x31, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, 0x31, 0x20, - 0x31, 0x20, 0x69, 0x20, 0x69, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, - 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, - 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, - 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, - 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, - 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x63, 0x61, 0x6c, 0x69, 0x62, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x30, 0x20, 0x30, 0x20, 0x31, 0x20, 0x31, 0x20, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, - 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x73, 0x69, 0x67, - 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x61, 0x31, 0x20, 0x61, 0x30, 0x20, 0x20, - 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, - 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x20, - 0x20, 0x3b, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x23, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x23, 0x20, - 0x41, 0x54, 0x6d, 0x65, 0x67, 0x61, 0x33, 0x32, 0x75, 0x34, 0x0a, 0x23, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x6d, 0x33, 0x32, 0x75, 0x34, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x41, 0x54, 0x6d, 0x65, 0x67, 0x61, 0x33, 0x32, 0x55, - 0x34, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x30, 0x78, 0x31, 0x65, 0x20, 0x30, 0x78, 0x39, 0x35, - 0x20, 0x30, 0x78, 0x38, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x75, - 0x73, 0x62, 0x70, 0x69, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x32, 0x66, 0x66, 0x34, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x61, 0x73, 0x5f, 0x6a, 0x74, - 0x61, 0x67, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x79, 0x65, 0x73, 0x3b, 0x0a, 0x23, 0x20, 0x20, 0x20, 0x20, 0x73, - 0x74, 0x6b, 0x35, 0x30, 0x30, 0x5f, 0x64, 0x65, 0x76, 0x63, 0x6f, 0x64, - 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x42, 0x32, 0x3b, 0x0a, - 0x23, 0x20, 0x20, 0x20, 0x20, 0x61, 0x76, 0x72, 0x39, 0x31, 0x30, 0x5f, - 0x64, 0x65, 0x76, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x78, 0x34, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, - 0x69, 0x70, 0x5f, 0x65, 0x72, 0x61, 0x73, 0x65, 0x5f, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x6c, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, - 0x44, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x73, 0x32, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x30, 0x78, 0x41, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x72, 0x65, 0x73, 0x65, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x64, 0x65, 0x64, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x64, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, - 0x67, 0x6d, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x31, 0x20, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, 0x5f, 0x65, - 0x72, 0x61, 0x73, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x32, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, - 0x61, 0x62, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6d, 0x64, - 0x65, 0x78, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x32, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x79, 0x6e, - 0x63, 0x68, 0x6c, 0x6f, 0x6f, 0x70, 0x73, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x33, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x79, 0x74, - 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x6f, 0x6c, 0x6c, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x35, 0x33, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x72, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x70, 0x6f, 0x73, 0x74, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x6c, - 0x6c, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x70, 0x5f, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x78, 0x30, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x31, - 0x45, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x31, - 0x46, 0x2c, 0x20, 0x30, 0x78, 0x32, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x33, - 0x45, 0x2c, 0x20, 0x30, 0x78, 0x32, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x33, - 0x46, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, - 0x78, 0x34, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x35, 0x45, 0x2c, 0x20, 0x30, - 0x78, 0x34, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x35, 0x46, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x45, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x46, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x78, 0x36, 0x36, 0x2c, - 0x20, 0x30, 0x78, 0x37, 0x36, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x37, 0x2c, - 0x20, 0x30, 0x78, 0x37, 0x37, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x41, 0x2c, - 0x20, 0x30, 0x78, 0x37, 0x41, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x42, 0x2c, - 0x20, 0x30, 0x78, 0x37, 0x42, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x78, 0x42, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x46, - 0x44, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, - 0x31, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, - 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x76, 0x65, 0x6e, 0x74, - 0x65, 0x72, 0x73, 0x74, 0x61, 0x62, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x6d, 0x6f, 0x64, 0x65, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x61, 0x74, 0x63, 0x68, - 0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x76, 0x74, 0x67, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x6f, 0x66, - 0x66, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x31, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, - 0x65, 0x73, 0x65, 0x74, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x6d, 0x73, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x65, 0x74, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x75, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x76, 0x6c, - 0x65, 0x61, 0x76, 0x65, 0x73, 0x74, 0x61, 0x62, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x35, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, 0x65, 0x72, 0x61, 0x73, 0x65, - 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x3d, - 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, - 0x65, 0x72, 0x61, 0x73, 0x65, 0x70, 0x6f, 0x6c, 0x6c, 0x74, 0x69, 0x6d, - 0x65, 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x66, 0x75, - 0x73, 0x65, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, 0x69, 0x64, 0x74, 0x68, - 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, - 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x66, 0x75, 0x73, 0x65, 0x70, 0x6f, 0x6c, - 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x35, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, - 0x6d, 0x6c, 0x6f, 0x63, 0x6b, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, 0x69, - 0x64, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6c, 0x6f, 0x63, 0x6b, - 0x70, 0x6f, 0x6c, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x20, - 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, - 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x33, 0x31, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x70, 0x6d, 0x63, 0x72, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x35, 0x37, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x72, 0x61, 0x6d, 0x70, 0x7a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x78, 0x33, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61, 0x6c, - 0x6c, 0x6f, 0x77, 0x66, 0x75, 0x6c, 0x6c, 0x70, 0x61, 0x67, 0x65, 0x62, - 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x3d, 0x20, 0x6e, - 0x6f, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x63, 0x64, 0x72, - 0x65, 0x76, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x65, 0x65, - 0x70, 0x72, 0x6f, 0x6d, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x6e, 0x6f, 0x3b, 0x20, - 0x2f, 0x2a, 0x20, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x20, 0x74, 0x68, 0x69, - 0x73, 0x20, 0x22, 0x6e, 0x6f, 0x22, 0x20, 0x2a, 0x2f, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x34, 0x3b, 0x20, 0x20, 0x2f, 0x2a, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x70, - 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x20, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x20, 0x2a, 0x2f, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x30, 0x32, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, - 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, - 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x5f, - 0x70, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, - 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x61, 0x31, 0x30, - 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, - 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, - 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, - 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, - 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, - 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x20, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x6c, 0x6f, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x31, 0x20, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, - 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x70, 0x61, - 0x67, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, - 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, - 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, - 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x30, 0x78, 0x34, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x3d, 0x20, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x66, 0x6c, 0x61, - 0x73, 0x68, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x70, 0x61, 0x67, 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x79, 0x65, 0x73, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x33, 0x32, 0x37, 0x36, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x32, - 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, - 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x73, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, - 0x34, 0x35, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, - 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x34, 0x35, 0x30, 0x30, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, - 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x31, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, 0x61, - 0x63, 0x6b, 0x5f, 0x70, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6c, 0x6f, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x20, 0x20, 0x30, 0x20, 0x61, 0x31, 0x34, 0x20, 0x61, 0x31, 0x33, - 0x20, 0x61, 0x31, 0x32, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, - 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, - 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, - 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, - 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x5f, - 0x68, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x61, 0x31, - 0x34, 0x20, 0x61, 0x31, 0x33, 0x20, 0x61, 0x31, 0x32, 0x20, 0x20, 0x20, - 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, - 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, - 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, - 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, - 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, - 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6c, 0x6f, 0x61, 0x64, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x6f, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, - 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, - 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x68, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, - 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x70, 0x61, 0x67, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x31, - 0x35, 0x20, 0x61, 0x31, 0x34, 0x20, 0x61, 0x31, 0x33, 0x20, 0x61, 0x31, - 0x32, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, - 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x20, 0x78, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x6f, - 0x64, 0x65, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x34, 0x31, - 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x73, 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x31, 0x32, 0x38, 0x3b, 0x0a, - 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, - 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, - 0x72, 0x79, 0x20, 0x22, 0x6c, 0x66, 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, - 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, - 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, - 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, - 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, - 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x68, 0x66, - 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x69, - 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, - 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, - 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, - 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, - 0x72, 0x79, 0x20, 0x22, 0x65, 0x66, 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, - 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, - 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, - 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, - 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x6c, 0x6f, - 0x63, 0x6b, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, - 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x31, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, - 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, - 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x31, 0x20, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, 0x31, - 0x20, 0x31, 0x20, 0x69, 0x20, 0x69, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, - 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, - 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, - 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, - 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x63, 0x61, 0x6c, 0x69, 0x62, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x30, 0x20, 0x31, 0x20, 0x31, 0x20, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, - 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, 0x31, - 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x61, 0x31, 0x20, 0x61, 0x30, 0x20, - 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, - 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, - 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x0a, - 0, 0 -}; -size_t avrdude_slic3r_conf_size = 14178; -size_t avrdude_slic3r_conf_size_yy = 14180; diff --git a/src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp b/src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp index 731ead554..b92251ebd 100644 --- a/src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp +++ b/src/libnest2d/include/libnest2d/optimizers/nlopt/genetic.hpp @@ -14,6 +14,8 @@ public: localmethod_ = m; return *this; } + + inline void seed(unsigned long val) { nlopt::srand(val); } }; template<> diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index cad059616..741b0e9d2 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include // std::runtime_error #include #include @@ -14,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -190,6 +192,123 @@ bool unescape_strings_cstyle(const std::string &str, std::vector &o } } +std::vector ConfigOptionDef::cli_args(const std::string &key) const +{ + std::vector args; + if (this->cli != ConfigOptionDef::nocli) { + std::string cli = this->cli.substr(0, this->cli.find("=")); + boost::trim_right_if(cli, boost::is_any_of("!")); + if (cli.empty()) { + // Add the key + std::string opt = key; + boost::replace_all(opt, "_", "-"); + args.emplace_back(std::move(opt)); + } else + boost::split(args, cli, boost::is_any_of("|")); + } + return args; +} + +std::string ConfigOptionDef::nocli = "~~~noCLI"; + +std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function filter) const +{ + // prepare a function for wrapping text + auto wrap = [](std::string text, size_t line_length) -> std::string { + std::istringstream words(text); + std::ostringstream wrapped; + std::string word; + + if (words >> word) { + wrapped << word; + size_t space_left = line_length - word.length(); + while (words >> word) { + if (space_left < word.length() + 1) { + wrapped << '\n' << word; + space_left = line_length - word.length(); + } else { + wrapped << ' ' << word; + space_left -= word.length() + 1; + } + } + } + return wrapped.str(); + }; + + // get the unique categories + std::set categories; + for (const auto& opt : this->options) { + const ConfigOptionDef& def = opt.second; + if (filter(def)) + categories.insert(def.category); + } + + for (auto category : categories) { + if (category != "") { + out << category << ":" << std::endl; + } else if (categories.size() > 1) { + out << "Misc options:" << std::endl; + } + + for (const auto& opt : this->options) { + const ConfigOptionDef& def = opt.second; + if (def.category != category || def.cli == ConfigOptionDef::nocli || !filter(def)) + continue; + + // get all possible variations: --foo, --foobar, -f... + std::vector cli_args = def.cli_args(opt.first); + if (cli_args.empty()) + continue; + + for (auto& arg : cli_args) { + arg.insert(0, (arg.size() == 1) ? "-" : "--"); + if (def.type == coFloat || def.type == coInt || def.type == coFloatOrPercent + || def.type == coFloats || def.type == coInts) { + arg += " N"; + } else if (def.type == coPoint) { + arg += " X,Y"; + } else if (def.type == coPoint3) { + arg += " X,Y,Z"; + } else if (def.type == coString || def.type == coStrings) { + arg += " ABCD"; + } + } + + // left: command line options + const std::string cli = boost::algorithm::join(cli_args, ", "); + out << " " << std::left << std::setw(20) << cli; + + // right: option description + std::string descr = def.tooltip; + if (show_defaults && def.default_value != nullptr && def.type != coBool + && (def.type != coString || !def.default_value->serialize().empty())) { + descr += " ("; + if (!def.sidetext.empty()) { + descr += def.sidetext + ", "; + } else if (!def.enum_values.empty()) { + descr += boost::algorithm::join(def.enum_values, ", ") + "; "; + } + descr += "default: " + def.default_value->serialize() + ")"; + } + + // wrap lines of description + descr = wrap(descr, 80); + std::vector lines; + boost::split(lines, descr, boost::is_any_of("\n")); + + // if command line options are too long, print description in new line + for (size_t i = 0; i < lines.size(); ++i) { + if (i == 0 && cli.size() > 19) + out << std::endl; + if (i > 0 || cli.size() > 19) + out << std::string(21, ' '); + out << lines[i] << std::endl; + } + } + } + return out; +} + void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent) { // loop through options and apply them @@ -522,50 +641,53 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre // 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); + if (optdef->default_value != nullptr) { + opt = (optdef->default_value->type() == coEnum) ? + // Special case: For a DynamicConfig, convert a templated enum to a generic enum. + new ConfigOptionEnumGeneric(optdef->enum_keys_map, optdef->default_value->getInt()) : + optdef->default_value->clone(); + } else { + 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 coPoint3: opt = new ConfigOptionPoint3(); break; + // case coPoint3s: opt = new ConfigOptionPoint3s(); 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; } -void DynamicConfig::read_cli(const std::vector &tokens, t_config_option_keys* extra) +void DynamicConfig::read_cli(const std::vector &tokens, t_config_option_keys* extra, t_config_option_keys* keys) { std::vector args; // push a bogus executable name (argv[0]) args.emplace_back(const_cast("")); for (size_t i = 0; i < tokens.size(); ++ i) args.emplace_back(const_cast(tokens[i].c_str())); - this->read_cli(int(args.size()), &args[0], extra); + this->read_cli(int(args.size()), &args[0], extra, keys); } -bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra) +bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra, t_config_option_keys* keys) { // cache the CLI option => opt_key mapping std::map opts; - for (const auto &oit : this->def()->options) { - std::string cli = oit.second.cli; - cli = cli.substr(0, cli.find("=")); - boost::trim_right_if(cli, boost::is_any_of("!")); - std::vector tokens; - boost::split(tokens, cli, boost::is_any_of("|")); - for (const std::string &t : tokens) + for (const auto &oit : this->def()->options) + for (auto t : oit.second.cli_args(oit.first)) opts[t] = oit.first; - } bool parse_options = true; for (int i = 1; i < argc; ++ i) { @@ -606,11 +728,8 @@ bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra) // Look for the cli -> option mapping. const auto it = opts.find(token); if (it == opts.end()) { - printf("Warning: unknown option --%s\n", token.c_str()); - // instead of continuing, return false to caller - // to stop execution and print usage - return false; - //continue; + boost::nowide::cerr << "Unknown option --" << token.c_str() << std::endl; + return false; } const t_config_option_key opt_key = it->second; const ConfigOptionDef &optdef = this->def()->options.at(opt_key); @@ -618,13 +737,17 @@ bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra) // look for it in the next token. if (optdef.type != coBool && optdef.type != coBools && value.empty()) { if (i == (argc-1)) { - printf("No value supplied for --%s\n", token.c_str()); - continue; + boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl; + return false; } value = argv[++ i]; } // Store the option value. const bool existing = this->has(opt_key); + if (keys != nullptr && !existing) { + // Save the order of detected keys. + keys->push_back(opt_key); + } ConfigOption *opt_base = this->option(opt_key, true); ConfigOptionVectorBase *opt_vector = opt_base->is_vector() ? static_cast(opt_base) : nullptr; if (opt_vector) { @@ -648,7 +771,10 @@ bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra) static_cast(opt_base)->value = value; } else { // Any scalar value of a type different from Bool and String. - this->set_deserialize(opt_key, value, false); + if (! this->set_deserialize(opt_key, value, false)) { + boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl; + return false; + } } } return true; diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index c521f1cbb..e6f2ca6cb 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,24 @@ 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); +/// Specialization of std::exception to indicate that an unknown config option has been encountered. +class UnknownOptionException : public std::runtime_error { +public: + UnknownOptionException() : + std::runtime_error("Unknown option exception") {} + UnknownOptionException(const std::string &opt_key) : + std::runtime_error(std::string("Unknown option exception: ") + opt_key) {} +}; + +/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). +class NoDefinitionException : public std::runtime_error +{ +public: + NoDefinitionException() : + std::runtime_error("No definition exception") {} + NoDefinitionException(const std::string &opt_key) : + std::runtime_error(std::string("No definition exception: ") + opt_key) {} +}; // Type of a configuration value. enum ConfigOptionType { @@ -54,12 +73,14 @@ enum ConfigOptionType { coPoint = 6, // vector of 2d points (Point2f). Currently used for the definition of the print bed and for the extruder offsets. coPoints = coPoint + coVectorType, + coPoint3 = 7, +// coPoint3s = coPoint3 + coVectorType, // single boolean value - coBool = 7, + coBool = 8, // vector of boolean values coBools = coBool + coVectorType, // a generic enum - coEnum = 8, + coEnum = 9, }; enum ConfigOptionMode { @@ -68,6 +89,30 @@ enum ConfigOptionMode { comExpert }; +enum PrinterTechnology : uint8_t +{ + // Fused Filament Fabrication + ptFFF = 1 << 0, + // Stereolitography + ptSLA = 1 << 1, + // Selective Laser-Sintering + ptSLS = 1 << 2, + // Unknown, useful for command line processing + ptUnknown = 1 << 7 +}; +inline PrinterTechnology operator|(PrinterTechnology a, PrinterTechnology b) { + return static_cast(static_cast(a) | static_cast(b)); +} +inline PrinterTechnology operator&(PrinterTechnology a, PrinterTechnology b) { + return static_cast(static_cast(a)& static_cast(b)); +} +inline PrinterTechnology operator|=(PrinterTechnology& a, PrinterTechnology b) { + a = a | b; return a; +} +inline PrinterTechnology operator&=(PrinterTechnology& a, PrinterTechnology b) { + a = a & b; return a; +} + // A generic value of a configuration option. class ConfigOption { public: @@ -718,6 +763,39 @@ public: } }; + +class ConfigOptionPoint3 : public ConfigOptionSingle +{ +public: + ConfigOptionPoint3() : ConfigOptionSingle(Vec3d(0,0,0)) {} + explicit ConfigOptionPoint3(const Vec3d &value) : ConfigOptionSingle(value) {} + + static ConfigOptionType static_type() { return coPoint3; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionPoint3(*this); } + ConfigOptionPoint3& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionPoint3 &rhs) const { return this->value == rhs.value; } + + std::string serialize() const override + { + std::ostringstream ss; + ss << this->value(0); + ss << ","; + ss << this->value(1); + ss << ","; + ss << this->value(2); + return ss.str(); + } + + bool deserialize(const std::string &str, bool append = false) override + { + UNUSED(append); + char dummy; + return sscanf(str.data(), " %lf , %lf , %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 2 || + sscanf(str.data(), " %lf x %lf x %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 2; + } +}; + class ConfigOptionBool : public ConfigOptionSingle { public: @@ -893,6 +971,7 @@ class ConfigOptionEnumGeneric : public ConfigOptionInt { public: ConfigOptionEnumGeneric(const t_config_enum_values* keys_map = nullptr) : keys_map(keys_map) {} + explicit ConfigOptionEnumGeneric(const t_config_enum_values* keys_map, int value) : ConfigOptionInt(value), keys_map(keys_map) {} const t_config_enum_values* keys_map; @@ -960,6 +1039,8 @@ public: // The full label is shown, when adding an override parameter for an object or a modified object. std::string label; std::string full_label; + // With which printer technology is this configuration valid? + PrinterTechnology printer_technology = ptUnknown; // 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; @@ -1010,6 +1091,13 @@ public: return true; return false; } + + // Returns the alternative CLI arguments for the given option. + // If there are no cli arguments defined, use the key and replace underscores with dashes. + std::vector cli_args(const std::string &key) const; + + // Assign this key to cli to disable CLI for this option. + static std::string nocli; }; // Map from a config option name to its definition. @@ -1044,6 +1132,11 @@ public: return out; } + /// Iterate through all of the CLI options and write them to a stream. + std::ostream& print_cli_help( + std::ostream& out, bool show_defaults, + std::function filter = [](const ConfigOptionDef &){ return true; }) const; + protected: ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type) { ConfigOptionDef* opt = &this->options[opt_key]; @@ -1089,12 +1182,24 @@ public: 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); } + template + TYPE* option_throw(const t_config_option_key &opt_key, bool create = false) + { + ConfigOption *opt = this->optptr(opt_key, create); + if (opt == nullptr) + throw UnknownOptionException(opt_key); + if (opt->type() != TYPE::static_type()) + throw std::runtime_error("Conversion to a wrong type"); + return static_cast(opt); + } + template + const TYPE* option_throw(const t_config_option_key &opt_key) const + { return const_cast(this)->option_throw(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. @@ -1276,8 +1381,8 @@ public: bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } // Command line processing - void read_cli(const std::vector &tokens, t_config_option_keys* extra); - bool read_cli(int argc, char** argv, t_config_option_keys* extra); + void read_cli(const std::vector &tokens, t_config_option_keys* extra, t_config_option_keys* keys = nullptr); + bool read_cli(int argc, char** argv, t_config_option_keys* extra, t_config_option_keys* keys = nullptr); typedef std::map t_options_map; t_options_map::const_iterator cbegin() const { return options.cbegin(); } @@ -1303,25 +1408,6 @@ protected: void set_defaults(); }; -/// Specialization of std::exception to indicate that an unknown config option has been encountered. -class UnknownOptionException : public std::runtime_error { -public: - UnknownOptionException() : - std::runtime_error("Unknown option exception") {} - UnknownOptionException(const std::string &opt_key) : - std::runtime_error(std::string("Unknown option exception: ") + opt_key) {} -}; - -/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). -class NoDefinitionException : public std::runtime_error -{ -public: - NoDefinitionException() : - std::runtime_error("No definition exception") {} - NoDefinitionException(const std::string &opt_key) : - std::runtime_error(std::string("No definition exception: ") + opt_key) {} -}; - } #endif diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index 615cc6efe..c52353b02 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -15,7 +15,7 @@ void FillPlanePath::_fill_surface_single( { expolygon.rotate(- direction.first); - coord_t distance_between_lines = scale_(this->spacing) / params.density; + coord_t distance_between_lines = coord_t(scale_(this->spacing) / params.density); // align infill across layers using the object's bounding box // Rotated bounding box of the whole object. @@ -89,7 +89,8 @@ Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t m out.push_back(Vec2d(0, 0)); out.push_back(Vec2d(1, 0)); while (r < rmax) { - theta += 1. / r; + // Discretization angle to achieve a discretization error lower than RESOLUTION. + theta += 2. * acos(1. - RESOLUTION / r); r = a + b * theta; out.push_back(Vec2d(r * cos(theta), r * sin(theta))); } diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 516490b35..4b0a846ad 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -42,14 +42,20 @@ public: bool bridge; Flow(float _w, float _h, float _nd, bool _bridge = false) : - width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {}; + 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))); }; + 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))); } + + // Elephant foot compensation spacing to be used to detect narrow parts, where the elephant foot compensation cannot be applied. + // To be used on frExternalPerimeter only. + // Enable some perimeter squish (see INSET_OVERLAP_TOLERANCE). + // Here an overlap of 0.2x external perimeter spacing is allowed for by the elephant foot compensation. + coord_t scaled_elephant_foot_spacing() const { return coord_t(0.5f * float(this->scaled_width() + 0.6f * this->scaled_spacing())); } 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. diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index d2b9b08f3..5a16a6e8f 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -587,8 +587,10 @@ namespace Slic3r { object.second->layer_height_profile = obj_layer_heights_profile->second; IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.first); - if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) + if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { object.second->sla_support_points = obj_sla_support_points->second; + object.second->sla_points_status = sla::PointsStatus::UserModified; + } IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first); if (obj_metadata != m_objects_metadata.end()) diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 35c90a00c..ff1da37cc 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -175,6 +175,11 @@ struct AMFParserContext bool mirrory_set; float mirrorz; bool mirrorz_set; + + bool anything_set() const { return deltax_set || deltay_set || deltaz_set || + rx_set || ry_set || rz_set || + scalex_set || scaley_set || scalez_set || + mirrorx_set || mirrory_set || mirrorz_set; } }; struct Object { @@ -600,6 +605,7 @@ void AMFParserContext::endElement(const char * /* name */) break; p = end + 1; } + m_object->sla_points_status = sla::PointsStatus::UserModified; } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) { if (strcmp(opt_key, "modifier") == 0) { @@ -643,11 +649,7 @@ void AMFParserContext::endDocument() continue; } for (const Instance &instance : object.second.instances) -#if ENABLE_VOLUMES_CENTERING_FIXES - { -#else - if (instance.deltax_set && instance.deltay_set) { -#endif // ENABLE_VOLUMES_CENTERING_FIXES + if (instance.anything_set()) { ModelInstance *mi = m_model.objects[object.second.idx]->add_instance(); mi->set_offset(Vec3d(instance.deltax_set ? (double)instance.deltax : 0.0, instance.deltay_set ? (double)instance.deltay : 0.0, instance.deltaz_set ? (double)instance.deltaz : 0.0)); mi->set_rotation(Vec3d(instance.rx_set ? (double)instance.rx : 0.0, instance.ry_set ? (double)instance.ry : 0.0, instance.rz_set ? (double)instance.rz : 0.0)); diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp index ee5756083..85ca73551 100644 --- a/src/libslic3r/Format/OBJ.cpp +++ b/src/libslic3r/Format/OBJ.cpp @@ -115,4 +115,23 @@ bool load_obj(const char *path, Model *model, const char *object_name_in) return true; } +bool store_obj(const char *path, TriangleMesh *mesh) +{ + //FIXME returning false even if write failed. + mesh->WriteOBJFile(path); + return true; +} + +bool store_obj(const char *path, ModelObject *model_object) +{ + TriangleMesh mesh = model_object->mesh(); + return store_obj(path, &mesh); +} + +bool store_obj(const char *path, Model *model) +{ + TriangleMesh mesh = model->mesh(); + return store_obj(path, &mesh); +} + }; // namespace Slic3r diff --git a/src/libslic3r/Format/OBJ.hpp b/src/libslic3r/Format/OBJ.hpp index 9a8790bff..36aa17951 100644 --- a/src/libslic3r/Format/OBJ.hpp +++ b/src/libslic3r/Format/OBJ.hpp @@ -9,6 +9,10 @@ class Model; // Load an OBJ file into a provided model. extern bool load_obj(const char *path, Model *model, const char *object_name = nullptr); +extern bool store_obj(const char *path, TriangleMesh *mesh); +extern bool store_obj(const char *path, ModelObject *model); +extern bool store_obj(const char *path, Model *model); + }; // namespace Slic3r #endif /* slic3r_Format_OBJ_hpp_ */ diff --git a/src/libslic3r/Format/STL.cpp b/src/libslic3r/Format/STL.cpp index 99e2ff193..b00623d1d 100644 --- a/src/libslic3r/Format/STL.cpp +++ b/src/libslic3r/Format/STL.cpp @@ -55,4 +55,10 @@ bool store_stl(const char *path, ModelObject *model_object, bool binary) return store_stl(path, &mesh, binary); } +bool store_stl(const char *path, Model *model, bool binary) +{ + TriangleMesh mesh = model->mesh(); + return store_stl(path, &mesh, binary); +} + }; // namespace Slic3r diff --git a/src/libslic3r/Format/STL.hpp b/src/libslic3r/Format/STL.hpp index 2fd32324c..cff7dc086 100644 --- a/src/libslic3r/Format/STL.hpp +++ b/src/libslic3r/Format/STL.hpp @@ -11,6 +11,7 @@ extern bool load_stl(const char *path, Model *model, const char *object_name = n extern bool store_stl(const char *path, TriangleMesh *mesh, bool binary); extern bool store_stl(const char *path, ModelObject *model_object, bool binary); +extern bool store_stl(const char *path, Model *model, bool binary); }; // namespace Slic3r diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 6c60dba24..fe13c1a59 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1,3 +1,4 @@ +#include "libslic3r.h" #include "GCode.hpp" #include "ExtrusionEntity.hpp" #include "EdgeGrid.hpp" @@ -486,7 +487,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ // starts analyzer calculations if (m_enable_analyzer) { BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data"; - m_analyzer.calc_gcode_preview_data(*preview_data); + m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); }); m_analyzer.reset(); } @@ -574,6 +575,16 @@ void GCode::_do_export(Print &print, FILE *file) // resets analyzer m_analyzer.reset(); + // send extruder offset data to analyzer + GCodeAnalyzer::ExtruderOffsetsMap extruder_offsets; + for (unsigned int extruder_id : print.extruders()) + { + Vec2d offset = print.config().extruder_offset.get_at(extruder_id); + if (!offset.isApprox(Vec2d::Zero())) + extruder_offsets[extruder_id] = offset; + } + m_analyzer.set_extruder_offsets(extruder_offsets); + // resets analyzer's tracking data m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; m_last_width = GCodeAnalyzer::Default_Width; @@ -860,7 +871,7 @@ void GCode::_do_export(Print &print, FILE *file) for (unsigned int extruder_id : print.extruders()) { const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id); Polygon s(outer_skirt); - s.translate(Point::new_scale(- extruder_offset(0), - extruder_offset(1))); + s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1))); skirts.emplace_back(std::move(s)); } m_ooze_prevention.enable = true; diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 641fc64ba..1be26c722 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -101,6 +101,11 @@ GCodeAnalyzer::GCodeAnalyzer() reset(); } +void GCodeAnalyzer::set_extruder_offsets(const GCodeAnalyzer::ExtruderOffsetsMap& extruder_offsets) +{ + m_extruder_offsets = extruder_offsets; +} + void GCodeAnalyzer::reset() { _set_units(Millimeters); @@ -118,6 +123,7 @@ void GCodeAnalyzer::reset() _reset_axes_position(); m_moves_map.clear(); + m_extruder_offsets.clear(); } const std::string& GCodeAnalyzer::process_gcode(const std::string& gcode) @@ -131,22 +137,22 @@ const std::string& GCodeAnalyzer::process_gcode(const std::string& gcode) return m_process_output; } -void GCodeAnalyzer::calc_gcode_preview_data(GCodePreviewData& preview_data) +void GCodeAnalyzer::calc_gcode_preview_data(GCodePreviewData& preview_data, std::function cancel_callback) { // resets preview data preview_data.reset(); // calculates extrusion layers - _calc_gcode_preview_extrusion_layers(preview_data); + _calc_gcode_preview_extrusion_layers(preview_data, cancel_callback); // calculates travel - _calc_gcode_preview_travel(preview_data); + _calc_gcode_preview_travel(preview_data, cancel_callback); // calculates retractions - _calc_gcode_preview_retractions(preview_data); + _calc_gcode_preview_retractions(preview_data, cancel_callback); // calculates unretractions - _calc_gcode_preview_unretractions(preview_data); + _calc_gcode_preview_unretractions(preview_data, cancel_callback); } bool GCodeAnalyzer::is_valid_extrusion_role(ExtrusionRole role) @@ -654,7 +660,15 @@ void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type) it = m_moves_map.insert(TypeToMovesMap::value_type(type, GCodeMovesList())).first; // store move - it->second.emplace_back(type, _get_extrusion_role(), _get_extruder_id(), _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), _get_start_position(), _get_end_position(), _get_delta_extrusion(), _get_cp_color_id()); + Vec3d extruder_offset = Vec3d::Zero(); + unsigned int extruder_id = _get_extruder_id(); + ExtruderOffsetsMap::iterator extr_it = m_extruder_offsets.find(extruder_id); + if (extr_it != m_extruder_offsets.end()) + extruder_offset = Vec3d(extr_it->second(0), extr_it->second(1), 0.0); + + Vec3d start_position = _get_start_position() + extruder_offset; + Vec3d end_position = _get_end_position() + extruder_offset; + it->second.emplace_back(type, _get_extrusion_role(), extruder_id, _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), start_position, end_position, _get_delta_extrusion(), _get_cp_color_id()); } bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const @@ -662,7 +676,7 @@ bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const return ((int)erNone <= value) && (value <= (int)erMixed); } -void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data) +void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data, std::function cancel_callback) { struct Helper { @@ -711,9 +725,18 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ GCodePreviewData::Range feedrate_range; GCodePreviewData::Range volumetric_rate_range; + // to avoid to call the callback too often + unsigned int cancel_callback_threshold = (unsigned int)std::max((int)extrude_moves->second.size() / 25, 1); + unsigned int cancel_callback_curr = 0; + // constructs the polylines while traversing the moves for (const GCodeMove& move : extrude_moves->second) { + // to avoid to call the callback too often + cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold; + if (cancel_callback_curr == 0) + cancel_callback(); + if ((data != move.data) || (z != move.start_position.z()) || (position != move.start_position) || (volumetric_rate != move.data.feedrate * (float)move.data.mm3_per_mm)) { // store current polyline @@ -755,7 +778,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range); } -void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) +void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data, std::function cancel_callback) { struct Helper { @@ -783,9 +806,17 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) GCodePreviewData::Range width_range; GCodePreviewData::Range feedrate_range; + // to avoid to call the callback too often + unsigned int cancel_callback_threshold = (unsigned int)std::max((int)travel_moves->second.size() / 25, 1); + unsigned int cancel_callback_curr = 0; + // constructs the polylines while traversing the moves for (const GCodeMove& move : travel_moves->second) { + cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold; + if (cancel_callback_curr == 0) + cancel_callback(); + GCodePreviewData::Travel::EType move_type = (move.delta_extruder < 0.0f) ? GCodePreviewData::Travel::Retract : ((move.delta_extruder > 0.0f) ? GCodePreviewData::Travel::Extrude : GCodePreviewData::Travel::Move); GCodePreviewData::Travel::Polyline::EDirection move_direction = ((move.start_position.x() != move.end_position.x()) || (move.start_position.y() != move.end_position.y())) ? GCodePreviewData::Travel::Polyline::Generic : GCodePreviewData::Travel::Polyline::Vertical; @@ -826,28 +857,44 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) preview_data.ranges.feedrate.update_from(feedrate_range); } -void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data) +void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data, std::function cancel_callback) { TypeToMovesMap::iterator retraction_moves = m_moves_map.find(GCodeMove::Retract); if (retraction_moves == m_moves_map.end()) return; + // to avoid to call the callback too often + unsigned int cancel_callback_threshold = (unsigned int)std::max((int)retraction_moves->second.size() / 25, 1); + unsigned int cancel_callback_curr = 0; + for (const GCodeMove& move : retraction_moves->second) { + cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold; + if (cancel_callback_curr == 0) + cancel_callback(); + // store position Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); preview_data.retraction.positions.emplace_back(position, move.data.width, move.data.height); } } -void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_data) +void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_data, std::function cancel_callback) { TypeToMovesMap::iterator unretraction_moves = m_moves_map.find(GCodeMove::Unretract); if (unretraction_moves == m_moves_map.end()) return; + // to avoid to call the callback too often + unsigned int cancel_callback_threshold = (unsigned int)std::max((int)unretraction_moves->second.size() / 25, 1); + unsigned int cancel_callback_curr = 0; + for (const GCodeMove& move : unretraction_moves->second) { + cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold; + if (cancel_callback_curr == 0) + cancel_callback(); + // store position Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); preview_data.unretraction.positions.emplace_back(position, move.data.width, move.data.height); diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index 08373055a..d816f49e4 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -87,6 +87,7 @@ public: typedef std::vector GCodeMovesList; typedef std::map TypeToMovesMap; + typedef std::map ExtruderOffsetsMap; private: struct State @@ -105,6 +106,7 @@ private: State m_state; GCodeReader m_parser; TypeToMovesMap m_moves_map; + ExtruderOffsetsMap m_extruder_offsets; // The output of process_layer() std::string m_process_output; @@ -112,6 +114,8 @@ private: public: GCodeAnalyzer(); + void set_extruder_offsets(const ExtruderOffsetsMap& extruder_offsets); + // Reinitialize the analyzer void reset(); @@ -119,7 +123,8 @@ public: const std::string& process_gcode(const std::string& gcode); // Calculates all data needed for gcode visualization - void calc_gcode_preview_data(GCodePreviewData& preview_data); + // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). + void calc_gcode_preview_data(GCodePreviewData& preview_data, std::function cancel_callback = std::function()); // Return an estimate of the memory consumed by the time estimator. size_t memory_used() const; @@ -234,10 +239,11 @@ private: // Checks if the given int is a valid extrusion role (contained into enum ExtrusionRole) bool _is_valid_extrusion_role(int value) const; - void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data); - void _calc_gcode_preview_travel(GCodePreviewData& preview_data); - void _calc_gcode_preview_retractions(GCodePreviewData& preview_data); - void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data); + // All the following methods throw CanceledException through print->throw_if_canceled() (sent by the caller as callback). + void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data, std::function cancel_callback); + void _calc_gcode_preview_travel(GCodePreviewData& preview_data, std::function cancel_callback); + void _calc_gcode_preview_retractions(GCodePreviewData& preview_data, std::function cancel_callback); + void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data, std::function cancel_callback); }; class BufferData { diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp index e86463b84..df4acc1bf 100644 --- a/src/libslic3r/GCode/PostProcessor.cpp +++ b/src/libslic3r/GCode/PostProcessor.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #ifdef WIN32 @@ -88,7 +89,7 @@ static DWORD execute_process_winapi(const std::wstring &command_line) // Run the script. If it is a perl script, run it through the bundled perl interpreter. // If it is a batch file, run it through the cmd.exe. // Otherwise run it directly. -static int run_script_win32(const std::string &script, const std::string &gcode) +static int run_script(const std::string &script, const std::string &gcode, std::string &/*std_err*/) { // Unpack the argument list provided by the user. int nArgs; @@ -132,9 +133,46 @@ static int run_script_win32(const std::string &script, const std::string &gcode) } #else - #include //for getting filesystem UID/GID - #include //for getting current UID/GID - #include + // POSIX + +#include // getenv() +#include +#include + +namespace process = boost::process; + +static int run_script(const std::string &script, const std::string &gcode, std::string &std_err) +{ + // Try to obtain user's default shell + const char *shell = ::getenv("SHELL"); + if (shell == nullptr) { shell = "sh"; } + + // Quote and escape the gcode path argument + std::string command { script }; + command.append(" '"); + for (char c : gcode) { + if (c == '\'') { command.append("'\\''"); } + else { command.push_back(c); } + } + command.push_back('\''); + + BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script, shell: %1%, command: %2%") % shell % command; + + process::ipstream istd_err; + process::child child(shell, "-c", command, process::std_err > istd_err); + + std_err.clear(); + std::string line; + + while (child.running() && std::getline(istd_err, line)) { + std_err.append(line); + std_err.push_back('\n'); + } + + child.wait(); + return child.exit_code(); +} + #endif namespace Slic3r { @@ -158,27 +196,15 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config if (script.empty()) continue; BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path; -#ifdef WIN32 - int result = run_script_win32(script, gcode_file.string()); -#else - //FIXME testing existence of a script is risky, as the script line may contain the script and some additional command line parameters. - // We would have to process the script line into parameters before testing for the existence of the command, the command may be looked up - // in the PATH etc. - if (! boost::filesystem::exists(boost::filesystem::path(script))) - throw std::runtime_error(std::string("The configured post-processing script does not exist: ") + script); - struct stat info; - if (stat(script.c_str(), &info)) - throw std::runtime_error(std::string("Cannot read information for post-processing script: ") + script); - boost::filesystem::perms script_perms = boost::filesystem::status(script).permissions(); - //if UID matches, check UID perm. else if GID matches, check GID perm. Otherwise check other perm. - if (!(script_perms & ((info.st_uid == geteuid()) ? boost::filesystem::perms::owner_exe - : ((info.st_gid == getegid()) ? boost::filesystem::perms::group_exe - : boost::filesystem::perms::others_exe)))) - throw std::runtime_error(std::string("The configured post-processing script is not executable: check permissions. ") + script); - int result = boost::process::system(script, gcode_file); - if (result < 0) - BOOST_LOG_TRIVIAL(error) << "Script " << script << " on file " << path << " failed. Negative error code returned."; -#endif + + std::string std_err; + const int result = run_script(script, gcode_file.string(), std_err); + if (result != 0) { + const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str() + : (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str(); + BOOST_LOG_TRIVIAL(error) << msg; + throw std::runtime_error(msg); + } } } } diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 854001860..6f9b488a3 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -741,12 +741,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. // Extrude 4 rounds of a brim around the future wipe tower. box_coordinates box(wipeTower_box); - box.expand(m_brim_width); for (size_t i = 0; i < 4; ++ i) { + box.expand(m_perimeter_width - m_layer_height*(1.f-M_PI_4)); // the brim shall have 'normal' spacing with no extra void space writer.travel (box.ld, 7000) .extrude(box.lu, 2100).extrude(box.ru) .extrude(box.rd ).extrude(box.ld); - box.expand(m_brim_width); } writer.travel(wipeTower_box.ld, 7000); // Move to the front left corner. diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 163197e2e..c92b56893 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -65,6 +65,7 @@ void Layer::make_slices() this->slices.expolygons.push_back(std::move(slices[i])); } +// Merge typed slices into untyped slices. This method is used to revert the effects of detect_surfaces_type() called for posPrepareInfill. void Layer::merge_slices() { if (m_regions.size() == 1) { @@ -78,6 +79,24 @@ void Layer::merge_slices() } } +ExPolygons Layer::merged(float offset_scaled) const +{ + assert(offset_scaled >= 0.f); + // If no offset is set, apply EPSILON offset before union, and revert it afterwards. + float offset_scaled2 = 0; + if (offset_scaled == 0.f) { + offset_scaled = float( EPSILON); + offset_scaled2 = float(- EPSILON); + } + Polygons polygons; + for (LayerRegion *layerm : m_regions) + append(polygons, offset(to_expolygons(layerm->slices.surfaces), offset_scaled)); + ExPolygons out = union_ex(polygons); + if (offset_scaled2 != 0.f) + out = offset_ex(out, offset_scaled2); + return out; +} + // 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. @@ -86,13 +105,14 @@ 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; + std::vector done(m_regions.size(), false); for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm) { size_t region_id = layerm - m_regions.begin(); - if (done.find(region_id) != done.end()) continue; + if (done[region_id]) + continue; BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id; - done.insert(region_id); + done[region_id] = true; const PrintRegionConfig &config = (*layerm)->region()->config(); // find compatible regions @@ -116,7 +136,7 @@ void Layer::make_perimeters() && config.external_perimeters_first == other_config.external_perimeters_first && config.perimeter_loop == other_config.perimeter_loop) { layerms.push_back(other_layerm); - done.insert(it - m_regions.begin()); + done[it - m_regions.begin()] = true; } } @@ -128,15 +148,13 @@ void Layer::make_perimeters() 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); - } - } + std::map slices; // extra_perimeters => [ surface, surface... ] + for (LayerRegion *layerm : layerms) + for (Surface &surface : layerm->slices.surfaces) + slices[surface.extra_perimeters].emplace_back(surface); // 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()); + for (std::pair &surfaces_with_extra_perimeters : slices) + new_slices.append(union_ex(surfaces_with_extra_perimeters.second, true), surfaces_with_extra_perimeters.second.front()); } // make perimeters diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 609f680fe..c7a48f8b5 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -65,7 +65,12 @@ public: void prepare_fill_surfaces(); void make_perimeters(SurfaceCollection &slices, SurfaceCollection* fill_surfaces); void process_external_surfaces(const Layer* lower_layer); - double infill_area_threshold() const; + double infill_area_threshold() const; + // Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer. + void trim_surfaces(const Polygons &trimming_polygons); + // Single elephant foot compensation step, used by the elephant foor compensation at the 1st layer. + // Trim surfaces by trimming polygons (shrunk by an elephant foot compensation step), but don't shrink narrow parts so much that no perimeter would fit. + void elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons); void export_region_slices_to_svg(const char *path) const; void export_region_fill_surfaces_to_svg(const char *path) const; @@ -119,7 +124,10 @@ public: // Test whether whether there are any slices assigned to this layer. bool empty() const; void make_slices(); + // Merge typed slices into untyped slices. This method is used to revert the effects of detect_surfaces_type() called for posPrepareInfill. void merge_slices(); + // Slices merged into islands, to be used by the elephant foot compensation to trim the individual surfaces with the shrunk merged slices. + ExPolygons merged(float offset) const; template bool any_internal_region_slice_contains(const T &item) const { for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_internal_contains(item)) return true; return false; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 2f656aaf8..0e3ea0abd 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -79,7 +79,7 @@ void LayerRegion::make_perimeters(SurfaceCollection &slices, SurfaceCollection* if (this->layer()->upper_layer != NULL) g.upper_slices = &this->layer()->upper_layer->slices; - g.layer_id = this->layer()->id(); + g.layer_id = (int)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); @@ -413,6 +413,28 @@ double LayerRegion::infill_area_threshold() const return ss*ss; } +void LayerRegion::trim_surfaces(const Polygons &trimming_polygons) +{ +#ifndef NDEBUG + for (const Surface &surface : this->slices.surfaces) + assert(surface.surface_type == stInternal); +#endif /* NDEBUG */ + this->slices.set(intersection_ex(to_polygons(std::move(this->slices.surfaces)), trimming_polygons, false), stPosInternal | stDensSparse); +} + +void LayerRegion::elephant_foot_compensation_step(const float elephant_foot_compensation_perimeter_step, const Polygons &trimming_polygons) +{ +#ifndef NDEBUG + for (const Surface &surface : this->slices.surfaces) + assert(surface.surface_type == stInternal); +#endif /* NDEBUG */ + ExPolygons slices_expolygons = to_expolygons(std::move(this->slices.surfaces)); + Polygons slices_polygons = to_polygons(slices_expolygons); + Polygons tmp = intersection(slices_polygons, trimming_polygons, false); + append(tmp, diff(slices_polygons, offset(offset_ex(slices_expolygons, -elephant_foot_compensation_perimeter_step), elephant_foot_compensation_perimeter_step))); + this->slices.set(std::move(union_ex(tmp)), stPosInternal | stDensSparse); +} + void LayerRegion::export_region_slices_to_svg(const char *path) const { BoundingBox bbox; diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 1e0fb426a..1f6ca1a23 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -56,6 +56,113 @@ public: } }; +template +class IndexBasedIterator { + static const size_t NONE = size_t(-1); + + std::reference_wrapper m_index_ref; + size_t m_idx = NONE; +public: + + using value_type = Value; + using pointer = Value *; + using reference = Value &; + using difference_type = long; + using iterator_category = std::random_access_iterator_tag; + + inline explicit + IndexBasedIterator(Vector& index, size_t idx): + m_index_ref(index), m_idx(idx) {} + + // Post increment + inline IndexBasedIterator operator++(int) { + IndexBasedIterator cpy(*this); ++m_idx; return cpy; + } + + inline IndexBasedIterator operator--(int) { + IndexBasedIterator cpy(*this); --m_idx; return cpy; + } + + inline IndexBasedIterator& operator++() { + ++m_idx; return *this; + } + + inline IndexBasedIterator& operator--() { + --m_idx; return *this; + } + + inline IndexBasedIterator& operator+=(difference_type l) { + m_idx += size_t(l); return *this; + } + + inline IndexBasedIterator operator+(difference_type l) { + auto cpy = *this; cpy += l; return cpy; + } + + inline IndexBasedIterator& operator-=(difference_type l) { + m_idx -= size_t(l); return *this; + } + + inline IndexBasedIterator operator-(difference_type l) { + auto cpy = *this; cpy -= l; return cpy; + } + + operator difference_type() { return difference_type(m_idx); } + + inline bool is_end() const { return m_idx >= m_index_ref.get().size();} + + inline Value & operator*() const { + assert(m_idx < m_index_ref.get().size()); + return m_index_ref.get().operator[](m_idx); + } + + inline Value * operator->() const { + assert(m_idx < m_index_ref.get().size()); + return &m_index_ref.get().operator[](m_idx); + } + + inline bool operator ==(const IndexBasedIterator& other) { + size_t e = m_index_ref.get().size(); + return m_idx == other.m_idx || (m_idx >= e && other.m_idx >= e); + } + + inline bool operator !=(const IndexBasedIterator& other) { + return !(*this == other); + } + + inline bool operator <=(const IndexBasedIterator& other) { + return (m_idx < other.m_idx) || (*this == other); + } + + inline bool operator <(const IndexBasedIterator& other) { + return m_idx < other.m_idx && (*this != other); + } + + inline bool operator >=(const IndexBasedIterator& other) { + return m_idx > other.m_idx || *this == other; + } + + inline bool operator >(const IndexBasedIterator& other) { + return m_idx > other.m_idx && *this != other; + } +}; + +template class Range { + It from, to; +public: + It begin() const { return from; } + It end() const { return to; } + using Type = It; + + Range() = default; + Range(It &&b, It &&e): + from(std::forward(b)), to(std::forward(e)) {} + + inline size_t size() const { return end() - begin(); } + inline bool empty() const { return size() == 0; } +}; + } #endif // MTUTILS_HPP diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 9c5f8c826..f9db9fea0 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -577,6 +577,11 @@ end: return input_file; } +std::string Model::propose_export_file_name_and_path(const std::string &new_extension) const +{ + return boost::filesystem::path(this->propose_export_file_name_and_path()).replace_extension(new_extension).string(); +} + ModelObject::~ModelObject() { this->clear_volumes(); @@ -992,12 +997,16 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) return hull; } +#if ENABLE_VOLUMES_CENTERING_FIXES +void ModelObject::center_around_origin(bool include_modifiers) +#else void ModelObject::center_around_origin() +#endif // ENABLE_VOLUMES_CENTERING_FIXES { // calculate the displacements needed to // center this object around the origin #if ENABLE_VOLUMES_CENTERING_FIXES - BoundingBoxf3 bb = full_raw_mesh_bounding_box(); + BoundingBoxf3 bb = include_modifiers ? full_raw_mesh_bounding_box() : raw_mesh_bounding_box(); #else BoundingBoxf3 bb; for (ModelVolume *v : this->volumes) @@ -1456,6 +1465,15 @@ int ModelVolume::extruder_id() const return extruder_id; } +bool ModelVolume::is_splittable() const +{ + // the call mesh.has_multiple_patches() is expensive, so cache the value to calculate it only once + if (m_is_splittable == -1) + m_is_splittable = (int)mesh.has_multiple_patches(); + + return m_is_splittable == 1; +} + void ModelVolume::center_geometry() { #if ENABLE_VOLUMES_CENTERING_FIXES @@ -1568,6 +1586,22 @@ void ModelVolume::scale(const Vec3d& scaling_factors) set_scaling_factor(get_scaling_factor().cwiseProduct(scaling_factors)); } +void ModelObject::scale_to_fit(const Vec3d &size) +{ +/* + BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; + Vec3d orig_size = this->bounding_box().size(); + float factor = fminf( + size.x / orig_size.x, + fminf( + size.y / orig_size.y, + size.z / orig_size.z + ) + ); + this->scale(factor); +*/ +} + void ModelVolume::rotate(double angle, Axis axis) { switch (axis) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a4b32d93f..8a48f8ee9 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -236,7 +236,11 @@ public: // This method is used by the auto arrange function. Polygon convex_hull_2d(const Transform3d &trafo_instance); +#if ENABLE_VOLUMES_CENTERING_FIXES + void center_around_origin(bool include_modifiers = true); +#else void center_around_origin(); +#endif // ENABLE_VOLUMES_CENTERING_FIXES void ensure_on_bed(); void translate_instances(const Vec3d& vector); void translate_instance(size_t instance_idx, const Vec3d& vector); @@ -245,6 +249,10 @@ public: void scale(const Vec3d &versor); void scale(const double s) { this->scale(Vec3d(s, s, s)); } void scale(double x, double y, double z) { this->scale(Vec3d(x, y, z)); } + /// Scale the current ModelObject to fit by altering the scaling factor of ModelInstances. + /// It operates on the total size by duplicating the object according to all the instances. + /// \param size Sizef3 the size vector + void scale_to_fit(const Vec3d &size); void rotate(double angle, Axis axis); void rotate(double angle, const Vec3d& axis); void mirror(Axis axis); @@ -336,8 +344,7 @@ public: // Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes). int extruder_id() const; - void set_splittable(const int val) { m_is_splittable = val; } - int is_splittable() const { return m_is_splittable; } + bool is_splittable() const; // Split this volume, append the result to the object owning this volume. // Return the number of volumes created from this one. @@ -417,7 +424,7 @@ private: // -1 -> is unknown value (before first cheking) // 0 -> is not splittable // 1 -> is splittable - int m_is_splittable {-1}; + mutable int m_is_splittable{ -1 }; ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(ModelVolumeType::MODEL_PART), object(object) { @@ -619,6 +626,8 @@ public: // Propose an output file name & path based on the first printable object's name and source input file's path. std::string propose_export_file_name_and_path() const; + // Propose an output path, replace extension. The new_extension shall contain the initial dot. + std::string propose_export_file_name_and_path(const std::string &new_extension) const; private: MODELBASE_DERIVED_PRIVATE_COPY_MOVE(Model) diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index fd3cf8648..4942e35b6 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -556,29 +556,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { // TODO export the exact 2D projection. Cannot do it as libnest2d // does not support concave shapes (yet). ClipperLib::Path clpath; -//WIP Vojtech's optimization of the calculation of the convex hull is not working correctly yet. -#if 1 - { - TriangleMesh rmesh = objptr->raw_mesh(); - ModelInstance * finst = objptr->instances.front(); - - // Object instances should carry the same scaling and - // x, y rotation that is why we use the first instance. - // The next line will apply only the full mirroring and scaling - rmesh.transform(finst->get_matrix(true, true, false, false)); - rmesh.rotate_x(float(finst->get_rotation()(X))); - rmesh.rotate_y(float(finst->get_rotation()(Y))); - - // TODO export the exact 2D projection. Cannot do it as libnest2d - // does not support concave shapes (yet). - auto p = rmesh.convex_hull(); - - p.make_clockwise(); - p.append(p.first_point()); - clpath = Slic3rMultiPoint_to_ClipperPath(p); - } -#else // Object instances should carry the same scaling and // x, y rotation that is why we use the first instance. { @@ -593,7 +571,6 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { p.append(p.first_point()); clpath = Slic3rMultiPoint_to_ClipperPath(p); } -#endif for(ModelInstance* objinst : objptr->instances) { if(objinst) { diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 74972a115..0d4cea0e7 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -240,7 +240,7 @@ public: const ValueType &value = it->second; const Vec2crd *pt2 = m_point_accessor(value); if (pt2 != nullptr) { - const double d2 = (pt - *pt2).squaredNorm(); + const double d2 = (pt - *pt2).cast().squaredNorm(); if (d2 < dist_min) { dist_min = d2; value_min = &value; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 5e16aa96d..275490ec2 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -532,6 +532,9 @@ exit_for_rearrange_regions: invalidated = true; } + for (PrintObject *object : m_objects) + object->update_slicing_parameters(); + return invalidated; } @@ -667,6 +670,9 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co // Make a copy of the config, normalize it. DynamicPrintConfig config(config_in); + config.option("print_settings_id", true); + config.option("filament_settings_id", true); + config.option("printer_settings_id", true); config.normalize(); // Collect changes to print config. t_config_option_keys print_diff = m_config.diff(config); @@ -694,9 +700,9 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co PlaceholderParser &pp = this->placeholder_parser(); pp.apply_only(config, placeholder_parser_diff); // Set the profile aliases for the PrintBase::output_filename() - pp.set("print_preset", config_in.option("print_settings_id" )->clone()); - pp.set("filament_preset", config_in.option("filament_settings_id")->clone()); - pp.set("printer_preset", config_in.option("printer_settings_id" )->clone()); + pp.set("print_preset", config.option("print_settings_id")->clone()); + pp.set("filament_preset", config.option("filament_settings_id")->clone()); + pp.set("printer_preset", config.option("printer_settings_id")->clone()); } // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. @@ -1107,6 +1113,12 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co } } + // Update SlicingParameters for each object where the SlicingParameters is not valid. + // If it is not valid, then it is ensured that PrintObject.m_slicing_params is not in use + // (posSlicing and posSupportMaterial was invalidated). + for (PrintObject *object : m_objects) + object->update_slicing_parameters(); + #ifdef _DEBUG check_model_ids_equal(m_model, model); #endif /* _DEBUG */ @@ -1245,21 +1257,21 @@ std::string Print::validate() const break; } } - SlicingParameters slicing_params0 = m_objects.front()->slicing_parameters(); + const SlicingParameters &slicing_params0 = m_objects.front()->slicing_parameters(); size_t tallest_object_idx = 0; if (has_custom_layering) PrintObject::update_layer_height_profile(*m_objects.front()->model_object(), slicing_params0, layer_height_profiles.front()); for (size_t i = 1; i < m_objects.size(); ++ i) { - const PrintObject *object = m_objects[i]; - const SlicingParameters slicing_params = object->slicing_parameters(); + const PrintObject *object = m_objects[i]; + const 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 L("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 L("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_type != m_objects.front()->config().support_material_contact_distance_type - || object->config().support_material_contact_distance_top != m_objects.front()->config().support_material_contact_distance_top - || object->config().support_material_contact_distance_bottom != m_objects.front()->config().support_material_contact_distance_bottom) + || object->config().support_material_contact_distance_top != m_objects.front()->config().support_material_contact_distance_top + || object->config().support_material_contact_distance_bottom != m_objects.front()->config().support_material_contact_distance_bottom) return L("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 L("The Wipe Tower is only supported for multiple objects if they are sliced equally."); @@ -1574,7 +1586,7 @@ void Print::process() // 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. -void Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data) +std::string Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data) { // output everything to a G-code file // The following call may die if the output_filename_format template substitution fails. @@ -1590,6 +1602,7 @@ void Print::export_gcode(const std::string &path_template, GCodePreviewData *pre // The following line may die for multiple reasons. GCode gcode; gcode.do_export(this, path.c_str(), preview_data); + return path.c_str(); } void Print::_make_skirt(const PrintObjectPtrs &objects, ExtrusionEntityCollection &out) @@ -1763,6 +1776,8 @@ void Print::_make_brim(const PrintObjectPtrs &objects, ExtrusionEntityCollection } loops = union_pt_chained(loops, false); + // The function above produces ordering well suited for concentric infill (from outside to inside). + // For Brim, the ordering should be reversed (from inside to outside). std::reverse(loops.begin(), loops.end()); extrusion_entities_append_loops(out.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 60f6b68f7..a87568794 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -93,7 +93,7 @@ public: const LayerPtrs& layers() const { return m_layers; } const SupportLayerPtrs& support_layers() const { return m_support_layers; } const Transform3d& trafo() const { return m_trafo; } - const Points& copies() const { return m_copies; } + const Points& copies() const { return m_copies; } // since the object is aligned to origin, bounding box coincides with size BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } @@ -131,7 +131,7 @@ public: // 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; + const SlicingParameters& slicing_parameters() const { return m_slicing_params; } static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object); // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) @@ -165,6 +165,8 @@ protected: bool invalidate_all_steps(); // Invalidate steps based on a set of parameters changed. bool invalidate_state_by_config_options(const std::vector &opt_keys); + // If ! m_slicing_params.valid, recalculate. + void update_slicing_parameters(); static PrintObjectConfig object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders); static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_region_config, const ModelVolume &volume, size_t num_extruders); @@ -203,6 +205,7 @@ private: // for external callers) Point m_copies_shift; + SlicingParameters m_slicing_params; LayerPtrs m_layers; SupportLayerPtrs m_support_layers; @@ -214,6 +217,7 @@ private: std::vector _slice_region(size_t region_id, const std::vector &z, bool modifier); std::vector _slice_volumes(const std::vector &z, const std::vector &volumes) const; + std::vector _slice_volume(const std::vector &z, const ModelVolume &volume) const; }; struct WipeTowerData @@ -307,7 +311,9 @@ public: bool apply_config(DynamicPrintConfig config); void process() override; - void export_gcode(const std::string &path_template, GCodePreviewData *preview_data); + // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. + // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r). + std::string export_gcode(const std::string &path_template, GCodePreviewData *preview_data); // methods for handling state bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); } diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index e01b678a5..2d47d3567 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -272,6 +272,7 @@ public: NO_RELOAD_SCENE = 0, RELOAD_SCENE = 1 << 1, RELOAD_SLA_SUPPORT_POINTS = 1 << 2, + RELOAD_SLA_PREVIEW = 1 << 3, }; // Bitmap of FlagBits unsigned int flags; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 086e533a8..2decc53fe 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -16,11 +16,22 @@ namespace Slic3r { //! return same string #define L(s) Slic3r::I18N::translate(s) +static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology) +{ + for (std::pair &kvp : options) + if (kvp.second.printer_technology == ptUnknown) + kvp.second.printer_technology = printer_technology; +} + PrintConfigDef::PrintConfigDef() { this->init_common_params(); + //assign params that are not already allocated to FFF+SLA (default from slic3rPE) + assign_printer_technology_to_unknown(this->options, ptFFF | ptSLA); this->init_fff_params(); + assign_printer_technology_to_unknown(this->options, ptFFF); this->init_sla_params(); + assign_printer_technology_to_unknown(this->options, ptSLA); } void PrintConfigDef::init_common_params() @@ -31,7 +42,6 @@ void PrintConfigDef::init_common_params() def = this->add("printer_technology", coEnum); def->label = L("Printer technology"); def->tooltip = L("Printer technology"); - def->cli = "printer-technology=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("FFF"); def->enum_values.push_back("SLA"); @@ -48,7 +58,6 @@ void PrintConfigDef::init_common_params() def->tooltip = L("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 = L("mm"); - def->cli = "layer-height=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0.3); @@ -57,7 +66,6 @@ void PrintConfigDef::init_common_params() def->label = L("Max print height"); def->tooltip = L("Set this to the maximum height that can be reached by your extruder while printing."); def->sidetext = L("mm"); - def->cli = "max-print-height=f"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(200.0); @@ -67,10 +75,30 @@ void PrintConfigDef::init_common_params() def->tooltip = L("Cracks smaller than 2x gap closing radius are being filled during the triangle mesh slicing. " "The gap closing operation may reduce the final print resolution, therefore it is advisable to keep the value reasonably low."); def->sidetext = L("mm"); - def->cli = "slice-closing-radius=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0.049); + + def = this->add("print_host", coString); + def->label = L("Hostname, IP or URL"); + def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " + "the hostname, IP address or URL of the printer host instance."); + def->mode = comAdvanced; + def->default_value = new ConfigOptionString(""); + + def = this->add("printhost_apikey", coString); + def->label = L("API Key / Password"); + def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " + "the API Key or the password required for authentication."); + def->mode = comAdvanced; + def->default_value = new ConfigOptionString(""); + + def = this->add("printhost_cafile", coString); + def->label = "HTTPS CA File"; + def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " + "If left blank, the default OS CA certificate repository is used."; + def->mode = comAdvanced; + def->default_value = new ConfigOptionString(""); } void PrintConfigDef::init_fff_params() @@ -86,16 +114,14 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("bed_temperature", coInts); def->label = L("Other layers"); - def->full_label = L("Bed temperature"); def->tooltip = L("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 = L("Bed temperature"); def->sidetext = L("°C"); def->min = 0; def->max = 300; @@ -106,7 +132,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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; @@ -116,7 +141,6 @@ void PrintConfigDef::init_fff_params() def = this->add("between_objects_gcode", coString); def->label = L("Between objects G-code"); def->tooltip = L("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; @@ -125,11 +149,9 @@ void PrintConfigDef::init_fff_params() def = this->add("bottom_solid_layers", coInt); def->label = L("Bottom"); - def->full_label = L("Bottom solid layers"); def->category = L("Layers and Perimeters"); def->tooltip = L("Number of solid layers to generate on bottom surfaces."); - def->cli = "bottom-solid-layers=i"; - def->min = 0; + def->full_label = L("Bottom solid layers"); def->min = 0; def->default_value = new ConfigOptionInt(3); def = this->add("bridge_acceleration", coFloat); @@ -138,20 +160,18 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("This is the acceleration your printer will use for bridges. " "Set zero to disable acceleration control for bridges."); def->sidetext = L("mm/s²"); - def->cli = "bridge-acceleration=f"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); def = this->add("bridge_angle", coFloat); def->label = L("Bridging"); - def->full_label = L("Bridge angle"); + def->full_label = L("Bridging angle"); def->category = L("Infill"); def->tooltip = L("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 = L("°"); - def->cli = "bridge-angle=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0.); @@ -160,7 +180,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Bridges fan speed"); def->tooltip = L("This fan speed is enforced during all bridges and overhangs."); def->sidetext = L("%"); - def->cli = "bridge-fan-speed=i@"; def->min = 0; def->max = 100; def->mode = comAdvanced; @@ -170,7 +189,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Top fan speed"); def->tooltip = L("This fan speed is enforced during all top fills."); def->sidetext = L("%"); - def->cli = "top-fan-speed=i@"; def->min = 0; def->max = 100; def->mode = comAdvanced; @@ -184,7 +202,6 @@ void PrintConfigDef::init_fff_params() "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->max = 2; def->mode = comAdvanced; @@ -197,7 +214,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Flow ratio to compensate for the gaps in a bridged top surface. Used for ironing infill" "pattern to prevent regions where the low-flow pass does not provide a smooth surface due to a lack of plastic." " You can increase it slightly to pull the top layer at the correct height. Recommended maximum: 1.2."); - def->cli = "over-bridge-flow-ratio=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(1); @@ -208,7 +224,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Speed"); def->tooltip = L("Speed for printing bridges."); def->sidetext = L("mm/s"); - def->cli = "bridge-speed=f"; def->aliases = { "bridge_feed_rate" }; def->min = 0; def->mode = comAdvanced; @@ -218,22 +233,20 @@ void PrintConfigDef::init_fff_params() def->label = L("Brim width"); def->tooltip = L("Horizontal width of the brim that will be printed around each object on the first layer."); def->sidetext = L("mm"); - def->cli = "brim-width=f"; def->min = 0; + def->mode = comSimple; def->default_value = new ConfigOptionFloat(0); def = this->add("brim_ears", coBool); def->label = L(" "); def->full_label = L("Brim ears"); def->tooltip = L("Only draw brim over the sharp edges of the model."); - def->cli = "brim-ears!"; def->default_value = new ConfigOptionBool(false); def = this->add("brim_ears_max_angle", coFloat); def->label = L("max angle"); def->tooltip = L("Maximum angle to let a brim ear appear."); def->sidetext = L("°"); - def->cli = "brim-ears-max-angle=f"; def->min = 0; def->max = 180; def->default_value = new ConfigOptionFloat(125); @@ -242,7 +255,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Chamber"); def->full_label = L("Chamber temperature"); def->tooltip = L("Chamber temperature0. Note that this setting doesn't do anything, but you can access it in Start G-code, Tool change G-code and the other ones, like for other temperature settings."); - def->cli = "chamber-temperature=i@"; def->sidetext = L("°C"); def->min = 0; def->max = 300; @@ -254,20 +266,19 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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->mode = comExpert; def->default_value = new ConfigOptionBool(false); def = this->add("colorprint_heights", coFloats); def->label = L("Colorprint height"); def->tooltip = L("Heights at which a filament change is to occur. "); - def->cli = "colorprint-heights=f@"; def->default_value = new ConfigOptionFloats { }; def = this->add("compatible_printers", coStrings); def->label = L("Compatible printers"); def->mode = comAdvanced; def->default_value = new ConfigOptionStrings(); + def->cli = ConfigOptionDef::nocli; def = this->add("compatible_printers_condition", coString); def->label = L("Compatible printers condition"); @@ -276,11 +287,13 @@ void PrintConfigDef::init_fff_params() "with the active printer profile."); def->mode = comExpert; def->default_value = new ConfigOptionString(); + def->cli = ConfigOptionDef::nocli; def = this->add("compatible_prints", coStrings); def->label = L("Compatible print profiles"); def->mode = comAdvanced; def->default_value = new ConfigOptionStrings(); + def->cli = ConfigOptionDef::nocli; def = this->add("compatible_prints_condition", coString); def->label = L("Compatible print profiles condition"); @@ -289,13 +302,16 @@ void PrintConfigDef::init_fff_params() "with the active print profile."); def->mode = comExpert; def->default_value = new ConfigOptionString(); + def->cli = ConfigOptionDef::nocli; // The following value is to be stored into the project file (AMF, 3MF, Config ...) // and it contains a sum of "compatible_printers_condition" values over the print and filament profiles. def = this->add("compatible_printers_condition_cummulative", coStrings); def->default_value = new ConfigOptionStrings(); + def->cli = ConfigOptionDef::nocli; def = this->add("compatible_prints_condition_cummulative", coStrings); def->default_value = new ConfigOptionStrings(); + def->cli = ConfigOptionDef::nocli; def = this->add("complete_objects", coBool); def->label = L("Complete individual objects"); @@ -303,7 +319,6 @@ void PrintConfigDef::init_fff_params() "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->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); @@ -311,7 +326,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Enable auto cooling"); def->tooltip = L("This flag enables the automatic cooling logic that adjusts print speed " "and fan speed according to layer printing time."); - def->cli = "cooling!"; def->mode = comAdvanced; def->default_value = new ConfigOptionBools { true }; @@ -319,7 +333,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Cooling tube position"); def->tooltip = L("Distance of the center-point of the cooling tube from the extruder tip "); def->sidetext = L("mm"); - def->cli = "cooling_tube_retraction=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(91.5f); @@ -328,7 +341,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Cooling tube length"); def->tooltip = L("Length of the cooling tube to limit space for cooling moves inside it "); def->sidetext = L("mm"); - def->cli = "cooling_tube_length=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(5.f); @@ -340,7 +352,6 @@ void PrintConfigDef::init_fff_params() "the role-specific acceleration values are used (perimeter/infill). " "Set zero to prevent resetting acceleration at all."); def->sidetext = L("mm/s²"); - def->cli = "default-acceleration=f"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); @@ -350,19 +361,20 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Default filament profile associated with the current printer profile. " "On selection of the current printer profile, this filament profile will be activated."); def->default_value = new ConfigOptionStrings(); + def->cli = ConfigOptionDef::nocli; def = this->add("default_print_profile", coString); def->label = L("Default print profile"); def->tooltip = L("Default print profile associated with the current printer profile. " "On selection of the current printer profile, this print profile will be activated."); def->default_value = new ConfigOptionString(); + def->cli = ConfigOptionDef::nocli; def = this->add("disable_fan_first_layers", coInts); def->label = L("Disable fan for the first"); def->tooltip = L("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 = L("layers"); - def->cli = "disable-fan-first-layers=i@"; def->min = 0; def->max = 1000; def->mode = comExpert; @@ -373,7 +385,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("Experimental option for preventing support material from being generated " "under bridged areas."); - def->cli = "dont-support-bridges!"; def->mode = comAdvanced; def->default_value = new ConfigOptionBool(true); @@ -381,7 +392,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Distance between copies"); def->tooltip = L("Distance used for the auto-arrange feature of the plater."); def->sidetext = L("mm"); - def->cli = "duplicate-distance=f"; def->aliases = { "multiply_distance" }; def->min = 0; def->default_value = new ConfigOptionFloat(6); @@ -393,7 +403,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("The first layer will be grown / shrunk in the XY plane by the configured value " "to compensate for the 1st layer squish aka an Elephant Foot effect. (should be negative = inwards)"); def->sidetext = L("mm"); - def->cli = "elefant-foot-compensation=f"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0); @@ -401,7 +410,6 @@ void PrintConfigDef::init_fff_params() def->label = L("End G-code"); def->tooltip = L("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; @@ -414,7 +422,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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; @@ -426,7 +433,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Layers and Perimeters"); def->tooltip = L("Add solid infill near sloping surfaces to guarantee the vertical shell thickness " "(top+bottom solid layers)."); - def->cli = "ensure-vertical-shell-thickness!"; def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); @@ -459,7 +465,7 @@ void PrintConfigDef::init_fff_params() def = this->add("bottom_fill_pattern", coEnum); def->label = L("Bottom"); - def->full_label = L("Bottom Pattern"); + def->full_label = L("Bottom fill pattern"); def->category = L("Infill"); def->tooltip = L("Fill pattern for bottom infill. This only affects the bottom visible layer, and not its adjacent solid shells."); def->cli = "bottom-fill-pattern|external-fill-pattern=s"; @@ -485,7 +491,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Solid pattern"); def->category = L("Infill"); def->tooltip = L("Fill pattern for solid (internal) infill. This only affects the solid not-visible layers. You should use rectilinear is most cases. You can use ironing for transluscnet material."); - def->cli = "solid-fill-pattern=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("rectilinear"); def->enum_values.push_back("smooth"); @@ -511,7 +516,6 @@ void PrintConfigDef::init_fff_params() "(it generally changes the flow from -7% to +4%, depending on the size of the surface to fill and the overlap parameters, " "but it can go as high as +50% for infill in very small areas where rectilinear doesn't have good coverage). It has the advantage " "to remove the over-extrusion seen in thin infill areas, from the overlap ratio"); - def->cli = "enforce-full-fill-volume!"; def->mode = comExpert; def->default_value = new ConfigOptionBool(true); @@ -521,7 +525,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Infill"); def->tooltip = L("This parameter grows the top/bottom/solid layers by the specified MM to anchor them into the part. Put 0 to deactivate it. Can be a % of the width of the perimeters."); def->sidetext = L("mm"); - def->cli = "top-layer-anchor=s"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloatOrPercent(150, true); @@ -532,7 +535,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Infill"); def->tooltip = L("This parameter grows the bridged solid infill layers by the specified MM to anchor them into the part. Put 0 to deactivate it. Can be a % of the width of the external perimeter."); def->sidetext = L("mm"); - def->cli = "bridged-layer-anchor=s"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloatOrPercent(200, true); @@ -545,7 +547,6 @@ void PrintConfigDef::init_fff_params() "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 112.5%), it will be computed over nozzle diameter."); def->sidetext = L("mm or % (leave 0 for default)"); - def->cli = "external-perimeter-extrusion-width=s"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -557,7 +558,6 @@ void PrintConfigDef::init_fff_params() "If expressed as percentage (for example: 80%) it will be calculated " "on the perimeters speed setting above. Set to zero for auto."); def->sidetext = L("mm/s or %"); - def->cli = "external-perimeter-speed=s"; def->ratio_over = "perimeter_speed"; def->min = 0; def->mode = comAdvanced; @@ -568,7 +568,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Layers and Perimeters"); def->tooltip = L("Print contour perimeters from the outermost one to the innermost one " "instead of the default inverse order."); - def->cli = "external-perimeters-first!"; def->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -578,7 +577,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Layers and Perimeters"); def->tooltip = L("Join the perimeters to create only one continuous extrusion without any z-hop." " Long inside travel (from external to holes) are not extruded to give some space to the infill."); - def->cli = "loop-perimeter!"; def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); @@ -587,7 +585,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Perimeter loop"); def->category = L("Layers and Perimeters"); def->tooltip = L("Position of perimeters starting points."); - def->cli = "perimeter-seam-position=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("nearest"); def->enum_values.push_back("rear"); @@ -603,7 +600,6 @@ void PrintConfigDef::init_fff_params() "Slic3r keeps adding perimeters, until more than 70% of the loop immediately above " "is supported, and keep adding periemter until all overhangs are filled." "\n!! this is a very slow algorithm !!"); - def->cli = "extra-perimeters!"; def->mode = comAdvanced; def->default_value = new ConfigOptionBool(true); @@ -611,7 +607,6 @@ void PrintConfigDef::init_fff_params() def->label = "Only one perimeter on Top surfaces"; def->category = "Layers and Perimeters"; def->tooltip = "Use only one perimeter on flat top surface, to let more space to the top infill pattern."; - def->cli = "one-top-perimeter!"; def->default_value = new ConfigOptionBool(true); @@ -621,7 +616,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Extruders"); def->tooltip = L("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"); @@ -642,7 +636,6 @@ void PrintConfigDef::init_fff_params() "and it represents the maximum depth the extruder can peek before colliding with " "other printed objects."); def->sidetext = L("mm"); - def->cli = "extruder-clearance-height=f"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloat(20); @@ -655,7 +648,6 @@ void PrintConfigDef::init_fff_params() "This setting is used to check for collisions and to display the graphical preview " "in the plater."); def->sidetext = L("mm"); - def->cli = "extruder-clearance-radius=f"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloat(20); @@ -663,7 +655,6 @@ void PrintConfigDef::init_fff_params() def = this->add("extruder_colour", coStrings); def->label = L("Extruder Color"); def->tooltip = L("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->mode = comAdvanced; @@ -676,7 +667,6 @@ void PrintConfigDef::init_fff_params() "with respect to the first one. It expects positive coordinates (they will be subtracted " "from the XY coordinate)."); def->sidetext = L("mm"); - def->cli = "extruder-offset=s@"; def->mode = comAdvanced; def->default_value = new ConfigOptionPoints { Vec2d(0,0) }; @@ -684,7 +674,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Extrusion axis"); def->tooltip = L("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); @@ -693,7 +682,6 @@ void PrintConfigDef::init_fff_params() "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->mode = comSimple; def->default_value = new ConfigOptionFloats { 1. }; @@ -705,7 +693,6 @@ void PrintConfigDef::init_fff_params() "(see the tooltips for perimeter extrusion width, infill extrusion width etc). " "If expressed as percentage (for example: 105%), it will be computed over nozzle diameter."); def->sidetext = L("mm or % (leave 0 for auto)"); - def->cli = "extrusion-width=s"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -713,7 +700,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Keep fan always on"); def->tooltip = L("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->mode = comSimple; def->default_value = new ConfigOptionBools { false }; @@ -722,7 +708,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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 = L("approximate seconds"); - def->cli = "fan-below-layer-time=i@"; def->width = 60; def->min = 0; def->max = 1000; @@ -733,7 +718,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Color"); def->full_label = L("Filament color"); def->tooltip = L("This is only used in the Slic3r interface as a visual help."); - def->cli = "filament-color=s@"; def->gui_type = "color"; def->mode = comAdvanced; def->default_value = new ConfigOptionStrings { "#29B2B2" }; @@ -741,7 +725,6 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_notes", coStrings); def->label = L("Filament notes"); def->tooltip = L("You can put your notes regarding the filament here."); - def->cli = "filament-notes=s@"; def->multiline = true; def->full_width = true; def->height = 130; @@ -754,7 +737,6 @@ void PrintConfigDef::init_fff_params() "speed of a print to the minimum of print and filament volumetric speed. " "Set to zero for no limit."); def->sidetext = L("mm³/s"); - def->cli = "filament-max-volumetric-speed=f@"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0. }; @@ -763,7 +745,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Max speed on the wipe tower"); def->tooltip = L("This setting is used to set the maximum speed when extruding inside the wipe tower (use M220). In %, set 0 to disable and use the Filament type instead."); def->sidetext = L("% of mm/s"); - def->cli = "filament-max-wipe-tower-speed=f@"; def->min = 0; def->max = 200; def->mode = comExpert; @@ -773,7 +754,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Loading speed"); def->tooltip = L("Speed used for loading the filament on the wipe tower. "); def->sidetext = L("mm/s"); - def->cli = "filament-loading-speed=f@"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloats { 28. }; @@ -782,7 +762,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Loading speed at the start"); def->tooltip = L("Speed used at the very beginning of loading phase. "); def->sidetext = L("mm/s"); - def->cli = "filament-loading-speed-start=f@"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloats { 3. }; @@ -792,7 +771,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Speed used for unloading the filament on the wipe tower (does not affect " " initial part of unloading just after ramming). "); def->sidetext = L("mm/s"); - def->cli = "filament-unloading-speed=f@"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloats { 90. }; @@ -801,7 +779,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Unloading speed at the start"); def->tooltip = L("Speed used for unloading the tip of the filament immediately after ramming. "); def->sidetext = L("mm/s"); - def->cli = "filament-unloading-speed-start=f@"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloats { 100. }; @@ -812,7 +789,6 @@ void PrintConfigDef::init_fff_params() "May help to get reliable toolchanges with flexible materials " "that may need more time to shrink to original dimensions. "); def->sidetext = L("s"); - def->cli = "filament-toolchange-delay=f@"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloats { 0. }; @@ -821,7 +797,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Number of cooling moves"); def->tooltip = L("Filament is cooled by being moved back and forth in the " "cooling tubes. Specify desired number of these moves "); - def->cli = "filament-cooling-moves=i@"; def->max = 0; def->max = 20; def->mode = comExpert; @@ -830,7 +805,6 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_cooling_initial_speed", coFloats); def->label = L("Speed of the first cooling move"); def->tooltip = L("Cooling moves are gradually accelerating beginning at this speed. "); - def->cli = "filament-cooling-initial-speed=f@"; def->sidetext = L("mm/s"); def->min = 0; def->mode = comExpert; @@ -842,7 +816,6 @@ void PrintConfigDef::init_fff_params() "the nozzle may not be known, and the filament pressure is likely not yet stable. " "Before purging the print head into an infill or a sacrificial object, Slic3r will always prime " "this amount of material into the wipe tower to produce successive infill or sacrificial object extrusions reliably."); - def->cli = "filament-minimal-purge-on-wipe-tower=f@"; def->sidetext = L("mm³"); def->min = 0; def->mode = comExpert; @@ -851,7 +824,6 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_cooling_final_speed", coFloats); def->label = L("Speed of the last cooling move"); def->tooltip = L("Cooling moves are gradually accelerating towards this speed. "); - def->cli = "filament-cooling-final-speed=f@"; def->sidetext = L("mm/s"); def->min = 0; def->mode = comExpert; @@ -860,7 +832,6 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_load_time", coFloats); def->label = L("Filament load time"); def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to load a new filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator."); - def->cli = "filament-load-time=i@"; def->sidetext = L("s"); def->min = 0; def->mode = comExpert; @@ -869,7 +840,6 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_ramming_parameters", coStrings); def->label = L("Ramming parameters"); def->tooltip = L("This string is edited by RammingDialog and contains ramming specific parameters "); - def->cli = "filament-ramming-parameters=s@"; def->mode = comExpert; def->default_value = new ConfigOptionStrings { "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0|" " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" }; @@ -877,7 +847,6 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_unload_time", coFloats); def->label = L("Filament unload time"); def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to unload a filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator."); - def->cli = "filament-unload-time=i@"; def->sidetext = L("s"); def->min = 0; def->mode = comExpert; @@ -888,7 +857,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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 = L("mm"); - def->cli = "filament-diameter=f@"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 3. }; @@ -899,7 +867,6 @@ void PrintConfigDef::init_fff_params() "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 = L("g/cm³"); - def->cli = "filament-density=f@"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0. }; @@ -907,7 +874,6 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_type", coStrings); def->label = L("Filament type"); def->tooltip = L("The filament material type for use in custom G-codes."); - def->cli = "filament_type=s@"; def->gui_type = "f_enum_open"; def->gui_flags = "show_value"; def->enum_values.push_back("PLA"); @@ -935,7 +901,6 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_soluble", coBools); def->label = L("Soluble material"); def->tooltip = L("Soluble material is most likely used for a soluble support."); - def->cli = "filament-soluble!"; def->mode = comAdvanced; def->default_value = new ConfigOptionBools { false }; @@ -943,12 +908,12 @@ void PrintConfigDef::init_fff_params() def->label = L("Cost"); def->tooltip = L("Enter your filament cost per kg here. This is only for statistical information."); def->sidetext = L("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->cli = ConfigOptionDef::nocli; def = this->add("fill_angle", coFloat); def->label = L("Fill"); @@ -958,7 +923,6 @@ void PrintConfigDef::init_fff_params() "Bridges will be infilled using the best direction Slic3r can detect, so this setting " "does not affect them."); def->sidetext = L("°"); - def->cli = "fill-angle=f"; def->min = 0; def->max = 360; def->mode = comAdvanced; @@ -971,7 +935,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Infill"); def->tooltip = L("Density of internal infill, expressed in the range 0% - 100%."); def->sidetext = L("%"); - def->cli = "fill-density=s"; def->min = 0; def->max = 100; /* @@ -1037,7 +1000,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Fill pattern"); def->category = L("Infill"); def->tooltip = L("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"); @@ -1075,7 +1037,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("This is the acceleration your printer will use for first layer. Set zero " "to disable acceleration control for first layer."); def->sidetext = L("mm/s²"); - def->cli = "first-layer-acceleration=f"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); @@ -1085,7 +1046,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("First layer bed temperature"); def->tooltip = L("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->sidetext = L("°C"); def->max = 0; def->max = 300; @@ -1101,7 +1061,6 @@ void PrintConfigDef::init_fff_params() "of the nozzle used for the type of extrusion. " "If set to zero, it will use the default extrusion width."); def->sidetext = L("mm or % (leave 0 for default)"); - def->cli = "first-layer-extrusion-width=s"; def->ratio_over = "first_layer_height"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(140, true); @@ -1114,7 +1073,6 @@ void PrintConfigDef::init_fff_params() "This can be expressed as an absolute value or as a percentage (for example: 150%) " "over the default layer height."); def->sidetext = L("mm or %"); - def->cli = "first-layer-height=s"; def->ratio_over = "layer_height"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0.35, false); @@ -1127,7 +1085,6 @@ void PrintConfigDef::init_fff_params() "speed if it's lower than that. If expressed as a percentage " "(for example: 40%) it will scale the 'default' speeds."); def->sidetext = L("mm/s or %"); - def->cli = "first-layer-speed=s"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(30, false); @@ -1140,7 +1097,6 @@ void PrintConfigDef::init_fff_params() "speed if it's lower than that. If expressed as a percentage " "(for example: 40%) it will scale the 'default' speed."); def->sidetext = L("mm/s or %"); - def->cli = "first-layer-infill-speed=s"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloatOrPercent(30, false); @@ -1150,7 +1106,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("First layer temperature"); def->tooltip = L("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->sidetext = L("°C"); def->min = 0; def->max = max_temp; @@ -1161,7 +1116,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Advanced"); def->tooltip = L("Enable gap fill algorithm. It will extrude small lines between perimeters " "when there is not enough space for another perimeter or an infill."); - def->cli = "gap-fill!"; def->mode = comExpert; def->default_value = new ConfigOptionBool(true); @@ -1172,7 +1126,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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 = L("mm/s"); - def->cli = "gap-fill-speed=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(20); @@ -1182,7 +1135,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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->mode = comExpert; def->default_value = new ConfigOptionBool(0); @@ -1191,7 +1143,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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"); @@ -1221,7 +1172,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = "Enable this to add comments into the G-Code labeling print moves with what object they belong to," " which is useful for the Octoprint CancelObject plugin. This settings is NOT compatible with " "Single Extruder Multi Material setup and Wipe into Object / Wipe into Infill."; - def->cli = "gcode-label-objects!"; def->aliases = { "label_printed_objects" }; def->mode = comAdvanced; def->default_value = new ConfigOptionBool(1); @@ -1231,7 +1181,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("It may be beneficial to increase the extruder motor current during the filament exchange" " sequence to allow for rapid ramming feed rates and to overcome resistance when loading" " a filament with an ugly shaped tip."); - def->cli = "high-current-on-filament-swap!"; def->mode = comExpert; def->default_value = new ConfigOptionBool(0); @@ -1241,7 +1190,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("This is the acceleration your printer will use for infill. Set zero to disable " "acceleration control for infill."); def->sidetext = L("mm/s²"); - def->cli = "infill-acceleration=f"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); @@ -1252,7 +1200,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("This feature allows to combine infill and speed up your print by extruding thicker " "infill layers while preserving thin perimeters, thus accuracy."); def->sidetext = L("layers"); - def->cli = "infill-every-layers=i"; def->full_label = L("Combine infill every n layers"); def->min = 1; def->mode = comAdvanced; @@ -1264,7 +1211,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Infill"); def->tooltip = L("Enables the creation of a support layer under the first solid layer. This allows you to use a lower infill ratio without compromising the top quality." " The dense infill is laid out with a 50% infill density."); - def->cli = "infill-dense!"; def->mode = comSimple; def->default_value = new ConfigOptionBool(false); @@ -1272,7 +1218,6 @@ void PrintConfigDef::init_fff_params() def->label = ("Do not connect infill lines to each other"); def->category = L("Infill"); def->tooltip = L("If checked, the infill algorithm will try to not connect the lines near the infill. Can be useful for art or with high infill/perimeter overlap."); - def->cli = "infill-not-connected!"; def->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -1282,7 +1227,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Choose the way the dense layer is lay out." " The automatic option let it try to draw the smallest surface with only strait lines inside the sparse infill." " The anchored just enlarge a bit (by 'Default infill margin') the surfaces that need a better support."); - def->cli = "infill-dense-algo=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("automatic"); def->enum_values.push_back("autosmall"); @@ -1297,7 +1241,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Infill extruder"); def->category = L("Extruders"); def->tooltip = L("The extruder to use when printing infill."); - def->cli = "infill-extruder=i"; def->min = 1; def->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); @@ -1310,7 +1253,6 @@ void PrintConfigDef::init_fff_params() "You may want to use fatter extrudates to speed up the infill and make your parts stronger. " "If expressed as percentage (for example 110%) it will be computed over nozzle diameter."); def->sidetext = L("mm or % (leave 0 for default)"); - def->cli = "infill-extrusion-width=s"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -1318,7 +1260,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Infill before perimeters"); def->category = L("Infill"); def->tooltip = L("This option will switch the print order of perimeters and infill, making the latter first."); - def->cli = "infill-first!"; def->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -1328,7 +1269,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); @@ -1339,7 +1279,6 @@ void PrintConfigDef::init_fff_params() "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 = L("mm or %"); - def->cli = "infill-overlap=s"; def->ratio_over = "perimeter_extrusion_width"; def->mode = comExpert; def->default_value = new ConfigOptionFloatOrPercent(25, true); @@ -1350,7 +1289,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Speed"); def->tooltip = L("Speed for printing the internal fill. Set to zero for auto."); def->sidetext = L("mm/s"); - def->cli = "infill-speed=f"; def->aliases = { "print_feed_rate", "infill_feed_rate" }; def->min = 0; def->mode = comAdvanced; @@ -1362,18 +1300,19 @@ void PrintConfigDef::init_fff_params() def->full_width = true; def->height = 50; def->default_value = new ConfigOptionString(); + def->cli = ConfigOptionDef::nocli; // The following value is to be stored into the project file (AMF, 3MF, Config ...) // and it contains a sum of "inherits" values over the print and filament profiles. def = this->add("inherits_cummulative", coStrings); def->default_value = new ConfigOptionStrings(); + def->cli = ConfigOptionDef::nocli; def = this->add("interface_shells", coBool); def->label = L("Interface shells"); def->tooltip = L("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 = L("Layers and Perimeters"); def->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -1383,7 +1322,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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->cli = "after-layer-gcode|layer-gcode"; def->multiline = true; def->full_width = true; def->height = 50; @@ -1394,7 +1333,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Exact last layer height"); def->category = L("Layers and Perimeters"); def->tooltip = L("This setting controls the height of last object layers to put the last layer at the exact highest height possible. Experimental."); - def->cli = "exact_last-layer-height=f"; def->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -1405,7 +1343,6 @@ void PrintConfigDef::init_fff_params() " intervals into the G-code to let the firmware show accurate remaining time." " As of now only the Prusa i3 MK3 firmware recognizes M73." " Also the i3 MK3 firmware supports M73 Qxx Sxx for the silent mode."); - def->cli = "remaining-times!"; def->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -1419,9 +1356,9 @@ void PrintConfigDef::init_fff_params() def = this->add("fan_speedup_time", coFloat); def->label = L("fan startup delay"); def->category = L("Firmware"); - def->tooltip = L("Move the M106 in the past by at least this delay and add the 'D' option to it to tell to the firware when the fan have to be at this speed." + def->tooltip = L("Move the M106 in the past by at least this delay (in seconds, you can use decimals) and add the 'D' option to it to tell to the firware when the fan have to be at this speed." " It assume infinite acceleration for this time estimation, and only takes into account G1 and G0 moves. Use 0 to deactivate, negative to remove the 'D' option."); - def->cli = "fan-speedup-time=f"; + def->sidetext = L("s"); def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0); @@ -1524,7 +1461,6 @@ void PrintConfigDef::init_fff_params() def->full_label = ("Max fan speed"); def->tooltip = L("This setting represents the maximum speed of your fan."); def->sidetext = L("%"); - def->cli = "max-fan-speed=i@"; def->min = 0; def->max = 100; def->mode = comAdvanced; @@ -1538,7 +1474,6 @@ void PrintConfigDef::init_fff_params() "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 = L("mm"); - def->cli = "max-layer-height=f@"; def->min = 0; def->mode = comSimple; def->default_value = new ConfigOptionFloats { 0. }; @@ -1549,7 +1484,6 @@ void PrintConfigDef::init_fff_params() "in order to keep constant extruder pressure. This experimental setting is used " "to set the highest print speed you want to allow."); def->sidetext = L("mm/s"); - def->cli = "max-print-speed=f"; def->min = 1; def->mode = comExpert; def->default_value = new ConfigOptionFloat(80); @@ -1559,7 +1493,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("This experimental setting is used to set the maximum volumetric speed your " "extruder supports."); def->sidetext = L("mm³/s"); - def->cli = "max-volumetric-speed=f"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); @@ -1572,7 +1505,6 @@ void PrintConfigDef::init_fff_params() "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 = L("mm³/s²"); - def->cli = "max-volumetric-extrusion-rate-slope-positive=f"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); @@ -1584,7 +1516,6 @@ void PrintConfigDef::init_fff_params() "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 = L("mm³/s²"); - def->cli = "max-volumetric-extrusion-rate-slope-negative=f"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); @@ -1595,7 +1526,6 @@ void PrintConfigDef::init_fff_params() def->full_label = ("Min fan speed"); def->tooltip = L("This setting represents the minimum PWM your fan needs to work."); def->sidetext = L("%"); - def->cli = "min-fan-speed=i@"; def->min = 0; def->max = 100; def->mode = comSimple; @@ -1607,7 +1537,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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 = L("mm"); - def->cli = "min-layer-height=f@"; def->min = 0; def->mode = comSimple; def->default_value = new ConfigOptionFloats { 0.07 }; @@ -1616,7 +1545,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Min print speed"); def->tooltip = L("Slic3r will not scale speed down below this speed."); def->sidetext = L("mm/s"); - def->cli = "min-print-speed=f@"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloats { 10. }; @@ -1627,7 +1555,6 @@ void PrintConfigDef::init_fff_params() "the specified amount of filament on the bottom layer. For multi-extruder machines, " "this minimum applies to each extruder."); def->sidetext = L("mm"); - def->cli = "min-skirt-length=f"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); @@ -1636,7 +1563,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Configuration notes"); def->tooltip = L("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; @@ -1647,7 +1573,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Nozzle diameter"); def->tooltip = L("This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)"); def->sidetext = L("mm"); - def->cli = "nozzle-diameter=f@"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0.5 }; @@ -1655,7 +1580,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Host Type"); def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain " "the kind of the host."); - def->cli = "host-type=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("octoprint"); def->enum_values.push_back("duet"); @@ -1668,7 +1592,6 @@ void PrintConfigDef::init_fff_params() def->label = L("API Key / Password"); def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " "the API Key or the password required for authentication."); - def->cli = "printhost-apikey=s"; def->mode = comAdvanced; def->default_value = new ConfigOptionString(""); @@ -1676,7 +1599,6 @@ void PrintConfigDef::init_fff_params() def->label = L("HTTPS CA File"); def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " "If left blank, the default OS CA certificate repository is used."; - def->cli = "printhost-cafile=s"; def->mode = comAdvanced; def->default_value = new ConfigOptionString(""); @@ -1684,7 +1606,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Hostname, IP or URL"); def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain " "the hostname, IP address or URL of the printer host instance."); - def->cli = "print-host=s"; def->mode = comAdvanced; def->default_value = new ConfigOptionString(""); @@ -1692,7 +1613,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Only retract when crossing perimeters"); def->tooltip = L("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->mode = comExpert; def->default_value = new ConfigOptionBool(true); @@ -1701,7 +1621,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -1711,7 +1630,6 @@ void PrintConfigDef::init_fff_params() "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->mode = comExpert; def->default_value = new ConfigOptionString("[input_filename_base]"); @@ -1721,7 +1639,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Layers and Perimeters"); def->tooltip = L("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->mode = comAdvanced; def->default_value = new ConfigOptionBool(true); @@ -1733,7 +1650,6 @@ void PrintConfigDef::init_fff_params() "\n * Keep bridges and overhangs: remove the unsupported perimeter, keep only bridges that end in solid area, fill the rest with overhang perimeters+bridges." "\n * Fill the voids with bridges: remove the unsupported perimeter, draw bridges over the whole hole. !! can lead to problems with overhangs shape like /\\, consider carefully before using this option!" "\n!!Computationally intensive!!. "); - def->cli = "no-perimeter-unsupported-algo=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("none"); def->enum_values.push_back("noperi"); @@ -1753,7 +1669,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Distance of the extruder tip from the position where the filament is parked " "when unloaded. This should match the value in printer firmware. "); def->sidetext = L("mm"); - def->cli = "parking_pos_retraction=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(92.f); @@ -1764,7 +1679,6 @@ void PrintConfigDef::init_fff_params() "is exactly the same as it was moved back during unload. When positive, it is loaded further, " " if negative, the loading move is shorter than unloading. "); def->sidetext = L("mm"); - def->cli = "extra_loading_move=f"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(-2.f); @@ -1775,7 +1689,6 @@ void PrintConfigDef::init_fff_params() "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 = L("mm/s²"); - def->cli = "perimeter-acceleration=f"; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); @@ -1783,7 +1696,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Perimeter extruder"); def->category = L("Extruders"); def->tooltip = L("The extruder to use when printing perimeters and brim. First extruder is 1."); - def->cli = "perimeter-extruder=i"; def->aliases = { "perimeters_extruder" }; def->min = 1; def->mode = comAdvanced; @@ -1798,7 +1710,6 @@ void PrintConfigDef::init_fff_params() "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 105%) it will be computed over nozzle diameter."); def->sidetext = L("mm or % (leave 0 for default)"); - def->cli = "perimeter-extrusion-width=s"; def->aliases = { "perimeters_extrusion_width" }; def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -1809,7 +1720,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Speed"); def->tooltip = L("Speed for perimeters (contours, aka vertical shells). Set to zero for auto."); def->sidetext = L("mm/s"); - def->cli = "perimeter-speed=f"; def->aliases = { "perimeter_feed_rate" }; def->min = 0; def->mode = comAdvanced; @@ -1824,9 +1734,9 @@ void PrintConfigDef::init_fff_params() "sloping surfaces which benefit from a higher number of perimeters " "if the Extra Perimeters option is enabled."); def->sidetext = L("(minimum)."); - def->cli = "perimeters=i"; def->aliases = { "perimeter_offsets" }; def->min = 0; + def->max = 10000; def->default_value = new ConfigOptionInt(3); def = this->add("post_process", coStrings); @@ -1835,7 +1745,6 @@ void PrintConfigDef::init_fff_params() "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; @@ -1847,11 +1756,11 @@ void PrintConfigDef::init_fff_params() def->label = L("Printer type"); def->tooltip = L("Type of the printer."); def->default_value = new ConfigOptionString(); + def->cli = ConfigOptionDef::nocli; def = this->add("printer_notes", coString); def->label = L("Printer notes"); def->tooltip = L("You can put your notes regarding the printer here."); - def->cli = "printer-notes=s"; def->multiline = true; def->full_width = true; def->height = 130; @@ -1862,23 +1771,26 @@ void PrintConfigDef::init_fff_params() def->label = L("Printer vendor"); def->tooltip = L("Name of the printer vendor."); def->default_value = new ConfigOptionString(); + def->cli = ConfigOptionDef::nocli; def = this->add("printer_variant", coString); def->label = L("Printer variant"); def->tooltip = L("Name of the printer variant. For example, the printer variants may be differentiated by a nozzle diameter."); def->default_value = new ConfigOptionString(); + def->cli = ConfigOptionDef::nocli; def = this->add("print_settings_id", coString); def->default_value = new ConfigOptionString(""); + def->cli = ConfigOptionDef::nocli; def = this->add("printer_settings_id", coString); def->default_value = new ConfigOptionString(""); + def->cli = ConfigOptionDef::nocli; def = this->add("support_material_solid_first_layer", coBool); def->label = L("Solid first layer"); def->category = L("Support material"); def->tooltip = L("Use a solid layer instead of a raft for the layer that touch the build plate."); - def->cli = "support-material-solid-first-layer!"; def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); @@ -1888,7 +1800,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("The object will be raised by this number of layers, and support material " "will be generated under it."); def->sidetext = L("layers"); - def->cli = "raft-layers=i"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionInt(0); @@ -1901,7 +1812,6 @@ void PrintConfigDef::init_fff_params() "and use full resolution from input. " "\nNote: slic3r simplify the geometry with a treshold of 0.0125mm and has an internal resolution of 0.0001mm."); def->sidetext = L("mm"); - def->cli = "resolution=f"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); @@ -1910,7 +1820,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Minimum travel after retraction"); def->tooltip = L("Retraction is not triggered when travel moves are shorter than this length."); def->sidetext = L("mm"); - def->cli = "retract-before-travel=f@"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 2. }; @@ -1919,14 +1828,12 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("With bowden extruders, it may be wise to do some amount of quick retract " "before doing the wipe movement."); def->sidetext = L("%"); - def->cli = "retract-before-wipe=s@"; def->mode = comAdvanced; def->default_value = new ConfigOptionPercents { 0. }; def = this->add("retract_layer_change", coBools); def->label = L("Retract on layer change"); def->tooltip = L("This flag enforces a retraction whenever a Z move is done."); - def->cli = "retract-layer-change!"; def->mode = comAdvanced; def->default_value = new ConfigOptionBools { false }; @@ -1937,7 +1844,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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 = L("mm (zero to disable)"); - def->cli = "retract-length=f@"; def->default_value = new ConfigOptionFloats { 2. }; def = this->add("retract_length_toolchange", coFloats); @@ -1948,7 +1854,6 @@ void PrintConfigDef::init_fff_params() "by the specified amount (the length is measured on raw filament, before it enters " "the extruder)."); def->sidetext = L("mm (zero to disable)"); - def->cli = "retract-length-toolchange=f@"; def->mode = comExpert; def->default_value = new ConfigOptionFloats { 10. }; @@ -1958,7 +1863,6 @@ void PrintConfigDef::init_fff_params() "is triggered. When using multiple extruders, only the setting for the first extruder " "will be considered."); def->sidetext = L("mm"); - def->cli = "retract-lift=f@"; def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_lift_above", coFloats); @@ -1968,7 +1872,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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 = L("mm"); - def->cli = "retract-lift-above=f@"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0. }; @@ -1980,7 +1883,6 @@ void PrintConfigDef::init_fff_params() "the specified absolute Z. You can tune this setting for limiting lift " "to the first layers."); def->sidetext = L("mm"); - def->cli = "retract-lift-below=f@"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0. }; @@ -1997,7 +1899,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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 = L("mm"); - def->cli = "retract-restart-extra=f@"; def->mode = comExpert; def->default_value = new ConfigOptionFloats { 0. }; @@ -2007,7 +1908,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("When the retraction is compensated after changing tool, the extruder will push " "this additional amount of filament."); def->sidetext = L("mm"); - def->cli = "retract-restart-extra-toolchange=f@"; def->mode = comExpert; def->default_value = new ConfigOptionFloats { 0. }; @@ -2016,7 +1916,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Retraction Speed"); def->tooltip = L("The speed for retractions (it only applies to the extruder motor)."); def->sidetext = L("mm/s"); - def->cli = "retract-speed=f@"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 40. }; @@ -2026,7 +1925,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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 = L("mm/s"); - def->cli = "retract-speed=f@"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloats { 0. }; @@ -2034,7 +1932,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Seam position"); def->category = L("Layers and Perimeters"); def->tooltip = L("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"); @@ -2064,7 +1961,6 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("°"); def->full_label = L("Preferred direction of the seam"); def->tooltip = L("Seam preferred direction"); - def->cli = "seam-preferred-direction=f"; def->min = 0; def->max = 360; def->default_value = new ConfigOptionFloat(0); @@ -2075,7 +1971,6 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("°"); def->full_label = L("Seam preferred direction jitter"); def->tooltip = L("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); @@ -2086,7 +1981,6 @@ void PrintConfigDef::init_fff_params() def->label = ""; def->full_label = L("Serial port"); def->tooltip = L("USB/serial port for printer connection."); - def->cli = "serial-port=s"; def->width = 200; def->default_value = new ConfigOptionString(""); @@ -2095,7 +1989,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Speed"); def->full_label = L("Serial port speed"); def->tooltip = L("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"); @@ -2108,7 +2001,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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 = L("mm"); - def->cli = "skirt-distance=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(6); @@ -2118,7 +2010,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Height of skirt expressed in layers. Set this to a tall value to use skirt " "as a shield against drafts."); def->sidetext = L("layers"); - def->cli = "skirt-height=i"; def->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); @@ -2128,7 +2019,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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->mode = comSimple; def->default_value = new ConfigOptionInt(1); @@ -2138,7 +2028,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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 = L("approximate seconds"); - def->cli = "slowdown-below-layer-time=i@"; def->width = 60; def->min = 0; def->max = 1000; @@ -2153,7 +2042,6 @@ void PrintConfigDef::init_fff_params() "(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 = L("mm/s or %"); - def->cli = "small-perimeter-speed=s"; def->ratio_over = "perimeter_speed"; def->min = 0; def->mode = comAdvanced; @@ -2193,7 +2081,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Infill"); def->tooltip = L("Force solid infill for regions having a smaller area than the specified threshold."); def->sidetext = L("mm²"); - def->cli = "solid-infill-below-area=f"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionFloat(70); @@ -2202,7 +2089,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Solid infill extruder"); def->category = L("Extruders"); def->tooltip = L("The extruder to use when printing solid infill."); - def->cli = "solid-infill-extruder=i"; def->min = 1; def->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); @@ -2215,7 +2101,6 @@ void PrintConfigDef::init_fff_params() "Slic3r will automatically choose the maximum possible number of layers " "to combine according to nozzle diameter and layer height."); def->sidetext = L("layers"); - def->cli = "solid-infill-every-layers=i"; def->min = 0; def->mode = comExpert; def->default_value = new ConfigOptionInt(0); @@ -2228,7 +2113,6 @@ void PrintConfigDef::init_fff_params() "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 110%) it will be computed over nozzle diameter."); def->sidetext = L("mm or % (leave 0 for default)"); - def->cli = "solid-infill-extrusion-width=s"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -2240,7 +2124,6 @@ void PrintConfigDef::init_fff_params() "This can be expressed as a percentage (for example: 80%) over the default " "infill speed above. Set to zero for auto."); def->sidetext = L("mm/s or %"); - def->cli = "solid-infill-speed=s"; def->ratio_over = "infill_speed"; def->aliases = { "solid_infill_feed_rate" }; def->min = 0; @@ -2250,7 +2133,6 @@ void PrintConfigDef::init_fff_params() def = this->add("solid_layers", coInt); def->label = L("Solid layers"); def->tooltip = L("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; @@ -2262,7 +2144,6 @@ void PrintConfigDef::init_fff_params() "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); @@ -2270,7 +2151,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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->mode = comExpert; @@ -2285,7 +2165,6 @@ void PrintConfigDef::init_fff_params() "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; @@ -2303,7 +2182,6 @@ void PrintConfigDef::init_fff_params() "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; @@ -2319,7 +2197,6 @@ void PrintConfigDef::init_fff_params() " It's used to align points that should be in the same line." " Put 0 to disable."); def->sidetext = L("mm"); - def->cli = "model-precision=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0.0001); @@ -2327,14 +2204,12 @@ void PrintConfigDef::init_fff_params() def = this->add("single_extruder_multi_material", coBool); def->label = L("Single Extruder Multi Material"); def->tooltip = L("The printer multiplexes filaments into a single hot end."); - def->cli = "single-extruder-multi-material!"; def->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); def = this->add("single_extruder_multi_material_priming", coBool); def->label = L("Prime all printing extruders"); def->tooltip = L("If enabled, all printing extruders will be primed at the front edge of the print bed at the start of the print."); - def->cli = "single-extruder-multi-material-priming!"; def->mode = comAdvanced; def->default_value = new ConfigOptionBool(true); @@ -2342,7 +2217,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Generate support material"); def->category = L("Support material"); def->tooltip = L("Enable support material generation."); - def->cli = "support-material!"; def->default_value = new ConfigOptionBool(false); def = this->add("support_material_auto", coBool); @@ -2350,7 +2224,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("If checked, supports will be generated automatically based on the overhang threshold value."\ " If unchecked, supports will be generated inside the \"Support Enforcer\" volumes only."); - def->cli = "support-material-auto!"; def->mode = comSimple; def->default_value = new ConfigOptionBool(true); @@ -2360,7 +2233,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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 = L("mm or %"); - def->cli = "support-material-xy-spacing=s"; def->ratio_over = "external_perimeter_extrusion_width"; def->min = 0; def->mode = comAdvanced; @@ -2373,7 +2245,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("Use this setting to rotate the support material pattern on the horizontal plane."); def->sidetext = L("°"); - def->cli = "support-material-angle=f"; def->min = 0; def->max = 359; def->mode = comExpert; @@ -2383,7 +2254,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Support on build plate only"); def->category = L("Support material"); def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print."); - def->cli = "support-material-buildplate-only!"; def->mode = comSimple; def->default_value = new ConfigOptionBool(false); @@ -2395,7 +2265,6 @@ void PrintConfigDef::init_fff_params() "From filament: it use the nearest bit of the filament. When a bridge is extruded, it goes below the current plane.\n" "From plane: it use the plane-z. Same than 'from filament' if no 'bridge' is extruded.\n" "None: No z-offset. Useful for Soluble supports.\n"); - def->cli = "support-material-contact-type=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("filament"); def->enum_values.push_back("plane"); @@ -2415,7 +2284,12 @@ void PrintConfigDef::init_fff_params() "Setting this to 0 will also prevent Slic3r from using bridge flow and speed " "for the first object layer. Can be a % of the extruder size used for the interface layers."); def->sidetext = L("mm"); - def->cli = "support-material-contact-distance-top=f"; + def->enum_labels.push_back((boost::format("0.2 (%1%)") % L("detachable")).str()); +// def->min = 0; + def->enum_values.push_back("0"); + def->enum_values.push_back("0.2"); + def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str()); + def->enum_labels.push_back((boost::format("0.2 (%1%)") % L("detachable")).str()); def->mode = comAdvanced; def->aliases = { "support_material_contact_distance" }; def->default_value = new ConfigOptionFloatOrPercent(100,true); @@ -2427,7 +2301,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("The vertical distance between object and support material interface" "(when the support is printed on top of the object). Can be a % of the extruder size used for the interface layers."); def->sidetext = L("mm"); - def->cli = "support-material-contact-distance-bottom=f"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(100,true); @@ -2439,7 +2312,6 @@ void PrintConfigDef::init_fff_params() "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 = L("layers"); - def->cli = "support-material-enforce-layers=f"; def->full_label = L("Enforce support for the first n layers"); def->min = 0; def->mode = comExpert; @@ -2450,7 +2322,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Extruders"); def->tooltip = L("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->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); @@ -2463,7 +2334,6 @@ void PrintConfigDef::init_fff_params() "If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. " "If expressed as percentage (for example 110%) it will be computed over nozzle diameter."); def->sidetext = L("mm or % (leave 0 for default)"); - def->cli = "support-material-extrusion-width=s"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -2471,7 +2341,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Interface loops"); def->category = L("Support material"); def->tooltip = L("Cover the top contact layer of the supports with loops. Disabled by default."); - def->cli = "support-material-interface-contact-loops!"; def->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -2480,7 +2349,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Extruders"); def->tooltip = L("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->mode = comAdvanced; def->default_value = new ConfigOptionInt(1); @@ -2490,7 +2358,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("Number of interface layers to insert between the object(s) and support material."); def->sidetext = L("layers"); - def->cli = "support-material-interface-layers=i"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionInt(3); @@ -2500,7 +2367,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("Spacing between interface lines. Set zero to get a solid interface."); def->sidetext = L("mm"); - def->cli = "support-material-interface-spacing=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0); @@ -2512,7 +2378,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Speed for printing support material interface layers. If expressed as percentage " "(for example 50%) it will be calculated over support material speed."); def->sidetext = L("mm/s or %"); - def->cli = "support-material-interface-speed=s"; def->ratio_over = "support_material_speed"; def->min = 0; def->mode = comAdvanced; @@ -2523,7 +2388,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Support pattern"); def->category = L("Support material"); def->tooltip = L("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"); @@ -2539,7 +2403,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Support interface pattern"); def->category = L("Support material"); def->tooltip = L("Pattern for interface layer."); - def->cli = "support-material-interface-pattern=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("rectilinear"); def->enum_values.push_back("concentric"); @@ -2561,7 +2424,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("Spacing between support material lines."); def->sidetext = L("mm"); - def->cli = "support-material-spacing=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(2.5); @@ -2572,7 +2434,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("Speed for printing support material."); def->sidetext = L("mm/s"); - def->cli = "support-material-speed=f"; def->min = 0; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(60); @@ -2582,7 +2443,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("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->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -2595,7 +2455,6 @@ void PrintConfigDef::init_fff_params() "that you can print without support material. Set to zero for automatic detection " "(recommended)."); def->sidetext = L("°"); - def->cli = "support-material-threshold=i"; def->min = 0; def->max = 90; def->mode = comAdvanced; @@ -2606,7 +2465,6 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("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->mode = comExpert; def->default_value = new ConfigOptionBool(true); @@ -2615,7 +2473,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Temperature"); def->tooltip = L("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 = L("Temperature"); def->sidetext = L("°C"); def->min = 0; @@ -2629,7 +2486,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Detect single-width walls (parts where two extrusions don't fit and we need " "to collapse them into a single trace). If unchecked, slic3r may try to fit perimeters " "where it's not possible, creating some overlap leading to over-extrusion."); - def->cli = "thin-walls!"; def->mode = comAdvanced; def->default_value = new ConfigOptionBool(true); @@ -2640,7 +2496,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Minimum width for the extrusion to be extruded (widths lower than the nozzle diameter will be over-extruded at the nozzle diameter)." " If expressed as percentage (for example 110%) it will be computed over nozzle diameter." " The default behavior of slic3r and slic3rPE is with a 33% value. Put 100% to avoid any sort of over-extrusion."); - def->cli = "thin-walls-min-width=s"; def->mode = comExpert; def->min = 0; def->default_value = new ConfigOptionFloatOrPercent(33, true); @@ -2650,7 +2505,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Thin wall overlap"); def->category = L("Layers and Perimeters"); def->tooltip = L("Overlap between the thin wall and the perimeters. Can be a % of the external perimeter width (default 50%)"); - def->cli = "thin-walls-overlap=s"; def->mode = comExpert; def->min = 0; def->default_value = new ConfigOptionFloatOrPercent(50, true); @@ -2659,12 +2513,12 @@ void PrintConfigDef::init_fff_params() def->label = L("Threads"); def->tooltip = L("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->cli == ConfigOptionDef::nocli; } def = this->add("toolchange_gcode", coString); @@ -2672,7 +2526,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("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; @@ -2687,7 +2540,6 @@ void PrintConfigDef::init_fff_params() "If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. " "If expressed as percentage (for example 110%) it will be computed over nozzle diameter."); def->sidetext = L("mm or % (leave 0 for default)"); - def->cli = "top-infill-extrusion-width=s"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -2701,7 +2553,6 @@ void PrintConfigDef::init_fff_params() "as a percentage (for example: 80%) over the solid infill speed above. " "Set to zero for auto."); def->sidetext = L("mm/s or %"); - def->cli = "top-solid-infill-speed=s"; def->ratio_over = "solid_infill_speed"; def->min = 0; def->mode = comAdvanced; @@ -2712,7 +2563,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Top layers"); def->category = L("Layers and Perimeters"); def->tooltip = L("Number of solid layers to generate on top surfaces."); - def->cli = "top-solid-layers=i"; def->full_label = L("Top solid layers"); def->min = 0; def->default_value = new ConfigOptionInt(3); @@ -2722,7 +2572,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Travel speed"); def->tooltip = L("Speed for travel moves (jumps between distant extrusion points)."); def->sidetext = L("mm/s"); - def->cli = "travel-speed=f"; def->aliases = { "travel_feed_rate" }; def->min = 1; def->mode = comAdvanced; @@ -2732,7 +2581,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Use firmware retraction"); def->tooltip = L("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->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -2740,7 +2588,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Use relative E distances"); def->tooltip = L("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->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -2752,7 +2599,6 @@ void PrintConfigDef::init_fff_params() "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->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -2760,7 +2606,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Enable variable layer height feature"); def->tooltip = L("Some printers or printer setups may have difficulties printing " "with a variable layer height. Enabled by default."); - def->cli = "variable-layer-height!"; def->mode = comExpert; def->default_value = new ConfigOptionBool(true); @@ -2768,7 +2613,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Wipe while retracting"); def->tooltip = L("This flag will move the nozzle while retracting to minimize the possible blob " "on leaky extruders."); - def->cli = "wipe!"; def->mode = comAdvanced; def->default_value = new ConfigOptionBools { false }; @@ -2777,7 +2621,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Enable wipe tower"); def->tooltip = L("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->mode = comAdvanced; def->default_value = new ConfigOptionBool(false); @@ -2786,14 +2629,12 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("This vector saves required volumes to change from/to each tool used on the " "wipe tower. These values are used to simplify creation of the full purging " "volumes below. "); - def->cli = "wiping-volumes-extruders=f@"; def->default_value = new ConfigOptionFloats { 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f }; def = this->add("wiping_volumes_matrix", coFloats); def->label = L("Purging volumes - matrix"); def->tooltip = L("This matrix describes volumes (in cubic milimetres) required to purge the" " new filament on the wipe tower for any given pair of tools. "); - def->cli = "wiping-volumes-matrix=f@"; def->default_value = new ConfigOptionFloats { 0.f, 140.f, 140.f, 140.f, 140.f, 140.f, 0.f, 140.f, 140.f, 140.f, 140.f, 140.f, 0.f, 140.f, 140.f, @@ -2804,7 +2645,6 @@ void PrintConfigDef::init_fff_params() def = this->add("wipe_advanced", coBool); def->label = L("Enable advanced wiping volume"); def->tooltip = L("Allow slic3r to compute the purge volume via smart computations. Use the pigment% of each filament and following parameters"); - def->cli = "wipe-advanced!"; def->mode = comExpert; def->default_value = new ConfigOptionBool(false); @@ -2812,14 +2652,12 @@ void PrintConfigDef::init_fff_params() def->label = L("Nozzle volume"); def->tooltip = L("The volume of melted plastic inside your nozlle. Used by 'advanced wiping'."); def->sidetext = L("mm3"); - def->cli = "wipe-advanced-volume=f"; def->mode = comExpert; def->default_value = new ConfigOptionFloat(120); def = this->add("filament_wipe_advanced_pigment", coFloats); def->label = L("Pigment percentage"); def->tooltip = L("The pigment % for this filament (bewteen 0 and 1, 1=100%). 0 for translucent/natural, 0.2-0.5 for white and 1 for black."); - def->cli = "wipe-advanced-pigment=f@"; def->mode = comExpert; def->min = 0; def->max = 1; @@ -2830,7 +2668,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Auto-wipe multiplier"); def->tooltip = L("The volume multiplier used to compute the final volume to extrude by the algorithm."); def->sidetext = L("mm3"); - def->cli = "wipe-advanced-mult=f"; def->mode = comExpert; def->default_value = new ConfigOptionFloat(60); @@ -2842,7 +2679,6 @@ void PrintConfigDef::init_fff_params() "Linear : volume = nozzle + volume_mult * (pigmentBefore-pigmentAfter)\n" "Quadratic: volume = nozzle + volume_mult * (pigmentBefore-pigmentAfter)+ volume_mult * (pigmentBefore-pigmentAfter)^3\n" "Hyperbola: volume = nozzle + volume_mult * (0.5+pigmentBefore) / (0.5+pigmentAfter)"); - def->cli = "wipe-advanced-algo=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("linear"); def->enum_values.push_back("quadra"); @@ -2858,7 +2694,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Wipe tower X"); def->tooltip = L("X coordinate of the left front corner of a wipe tower"); def->sidetext = L("mm"); - def->cli = "wipe-tower-x=f"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(180.); @@ -2867,7 +2702,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Wipe tower Y"); def->tooltip = L("Y coordinate of the left front corner of a wipe tower"); def->sidetext = L("mm"); - def->cli = "wipe-tower-y=f"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(140.); @@ -2876,7 +2710,6 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Wipe tower Width"); def->tooltip = L("Width of a wipe tower"); def->sidetext = L("mm"); - def->cli = "wipe-tower-width=f"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(60.); @@ -2884,7 +2717,6 @@ void PrintConfigDef::init_fff_params() def->label = L("Wipe tower rotation angle"); def->tooltip = L("Wipe tower rotation angle with respect to x-axis "); def->sidetext = L("degrees"); - def->cli = "wipe-tower-rotation-angle=f"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0.); @@ -2894,7 +2726,6 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Purging after toolchange will done inside this object's infills. " "This lowers the amount of waste but may result in longer print time " " due to additional travel moves."); - def->cli = "wipe-into-infill!"; def->default_value = new ConfigOptionBool(false); def = this->add("wipe_into_objects", coBool); @@ -2903,14 +2734,12 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Object will be used to purge the nozzle after a toolchange to save material " "that would otherwise end up in the wipe tower and decrease print time. " "Colours of the objects will be mixed as a result."); - def->cli = "wipe-into-objects!"; def->default_value = new ConfigOptionBool(false); def = this->add("wipe_tower_bridging", coFloat); def->label = L("Maximal bridging distance"); def->tooltip = L("Maximal distance between supports on sparse infill sections. "); def->sidetext = L("mm"); - def->cli = "wipe-tower-bridging=f"; def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(10.); @@ -2922,7 +2751,6 @@ void PrintConfigDef::init_fff_params() "(negative = inwards, positive = outwards). This might be useful " "for fine-tuning sizes."); def->sidetext = L("mm"); - def->cli = "xy-size-compensation=f"; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); @@ -2934,7 +2762,6 @@ void PrintConfigDef::init_fff_params() " (negative = inwards, positive = outwards, should be negative as the holes are always a bit smaller irl)." " This might be useful for fine-tuning hole sizes."); def->sidetext = L("mm"); - def->cli = "hole-size-compensation=f"; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); @@ -2945,56 +2772,8 @@ void PrintConfigDef::init_fff_params() "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 = L("mm"); - def->cli = "z-offset=f"; def->mode = comExpert; def->default_value = new ConfigOptionFloat(0); - - def = this->add("bed_size_x", coFloat); - def->label = L("Bed size X"); - def->category = L("Dwarf"); - def->sidetext = L("mm"); - def->cli = "bed-size-x=f"; - def->default_value = new ConfigOptionFloat(68.); - - def = this->add("bed_size_y", coFloat); - def->label = L("Bed size Y"); - def->category = L("Dwarf"); - def->sidetext = L("mm"); - def->cli = "bed-size-y=f"; - def->default_value = new ConfigOptionFloat(120.); - - def = this->add("pixel_width", coInt); - def->label = L("Picture resolution X"); - def->category = L("Dwarf"); - def->sidetext = L("px"); - def->cli = "pixel-width=i"; - def->min = 1; - def->mode = comExpert; - def->default_value = new ConfigOptionInt(1440); - - def = this->add("pixel_height", coInt); - def->label = L("Picture resolution Y"); - def->category = L("Dwarf"); - def->sidetext = L("px"); - def->cli = "pixel-height=i"; - def->min = 1; - def->default_value = new ConfigOptionInt(2560); - - def = this->add("exp_time", coFloat); - def->label = L("Exposure time"); - def->category = L("Dwarf"); - def->sidetext = L("s"); - def->cli = "exp-time=f"; - def->min = 1; - def->default_value = new ConfigOptionFloat(8.); - - def = this->add("exp_time_first", coFloat); - def->label = L("Exposure time first layers"); - def->category = L("Dwarf"); - def->sidetext = L("s"); - def->cli = "exp-time-first=f"; - def->min = 1; - def->default_value = new ConfigOptionFloat(35.); } void PrintConfigDef::init_sla_params() @@ -3003,17 +2782,16 @@ void PrintConfigDef::init_sla_params() ConfigOptionDef* def; // SLA Printer settings + def = this->add("display_width", coFloat); def->label = L("Display width"); def->tooltip = L("Width of the display"); - def->cli = "display-width=f"; def->min = 1; def->default_value = new ConfigOptionFloat(120.); def = this->add("display_height", coFloat); def->label = L("Display height"); def->tooltip = L("Height of the display"); - def->cli = "display-height=f"; def->min = 1; def->default_value = new ConfigOptionFloat(68.); @@ -3021,14 +2799,12 @@ void PrintConfigDef::init_sla_params() def->full_label = L("Number of pixels in"); def->label = ("X"); def->tooltip = L("Number of pixels in X"); - def->cli = "display-pixels-x=i"; def->min = 100; def->default_value = new ConfigOptionInt(2560); def = this->add("display_pixels_y", coInt); def->label = ("Y"); def->tooltip = L("Number of pixels in Y"); - def->cli = "display-pixels-y=i"; def->min = 100; def->default_value = new ConfigOptionInt(1440); @@ -3037,12 +2813,12 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("Set the actual LCD display orientation inside the SLA printer." " Portrait mode will flip the meaning of display width and height parameters" " and the output images will be rotated by 90 degrees."); - def->cli = "display-orientation=s"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("landscape"); def->enum_values.push_back("portrait"); def->enum_labels.push_back(L("Landscape")); def->enum_labels.push_back(L("Portrait")); + def->mode = comExpert; def->default_value = new ConfigOptionEnum(sladoPortrait); def = this->add("fast_tilt_time", coFloat); @@ -3082,7 +2858,6 @@ void PrintConfigDef::init_sla_params() def->label = L("Initial layer height"); def->tooltip = L("Initial layer height"); def->sidetext = L("mm"); - def->cli = "initial-layer-height=f"; def->min = 0; def->default_value = new ConfigOptionFloat(0.3); @@ -3098,7 +2873,6 @@ void PrintConfigDef::init_sla_params() def->label = L("Exposure time"); def->tooltip = L("Exposure time"); def->sidetext = L("s"); - def->cli = "exposure-time=f"; def->min = 0; def->default_value = new ConfigOptionFloat(10); @@ -3106,7 +2880,6 @@ void PrintConfigDef::init_sla_params() def->label = L("Initial exposure time"); def->tooltip = L("Initial exposure time"); def->sidetext = L("s"); - def->cli = "initial-exposure-time=f"; def->min = 0; def->default_value = new ConfigOptionFloat(15); @@ -3125,7 +2898,6 @@ void PrintConfigDef::init_sla_params() def = this->add("material_notes", coString); def->label = L("SLA print material notes"); def->tooltip = L("You can put your notes regarding the SLA print material here."); - def->cli = "material-notes=s"; def->multiline = true; def->full_width = true; def->height = 130; @@ -3137,25 +2909,28 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("Default print profile associated with the current printer profile. " "On selection of the current printer profile, this print profile will be activated."); def->default_value = new ConfigOptionString(); + def->cli = ConfigOptionDef::nocli; def = this->add("sla_material_settings_id", coString); def->default_value = new ConfigOptionString(""); - + def->cli = ConfigOptionDef::nocli; def = this->add("default_sla_print_profile", coString); def->label = L("Default SLA material profile"); def->tooltip = L("Default print profile associated with the current printer profile. " "On selection of the current printer profile, this print profile will be activated."); def->default_value = new ConfigOptionString(); + def->cli = ConfigOptionDef::nocli; def = this->add("sla_print_settings_id", coString); def->default_value = new ConfigOptionString(""); + def->cli = ConfigOptionDef::nocli; def = this->add("supports_enable", coBool); def->label = L("Generate supports"); def->category = L("Supports"); def->tooltip = L("Generate supports for the models"); - def->cli = ""; + def->mode = comSimple; def->default_value = new ConfigOptionBool(true); def = this->add("support_head_front_diameter", coFloat); @@ -3163,8 +2938,8 @@ void PrintConfigDef::init_sla_params() def->category = L("Supports"); def->tooltip = L("Diameter of the pointing side of the head"); def->sidetext = L("mm"); - def->cli = ""; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(0.4); def = this->add("support_head_penetration", coFloat); @@ -3172,7 +2947,7 @@ void PrintConfigDef::init_sla_params() def->category = L("Supports"); def->tooltip = L("How much the pinhead has to penetrate the model surface"); def->sidetext = L("mm"); - def->cli = ""; + def->mode = comAdvanced; def->min = 0; def->default_value = new ConfigOptionFloat(0.2); @@ -3181,8 +2956,9 @@ void PrintConfigDef::init_sla_params() def->category = L("Supports"); def->tooltip = L("Width from the back sphere center to the front sphere center"); def->sidetext = L("mm"); - def->cli = ""; def->min = 0; + def->max = 20; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(1.0); def = this->add("support_pillar_diameter", coFloat); @@ -3190,8 +2966,9 @@ void PrintConfigDef::init_sla_params() def->category = L("Supports"); def->tooltip = L("Diameter in mm of the support pillars"); def->sidetext = L("mm"); - def->cli = ""; def->min = 0; + def->max = 15; + def->mode = comSimple; def->default_value = new ConfigOptionFloat(1.0); def = this->add("support_pillar_connection_mode", coEnum); @@ -3200,7 +2977,6 @@ void PrintConfigDef::init_sla_params() " Can be zig-zag, cross (double zig-zag) or dynamic which" " will automatically switch between the first two depending" " on the distance of the two pillars."); - def->cli = ""; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("zigzag"); def->enum_values.push_back("cross"); @@ -3208,13 +2984,13 @@ void PrintConfigDef::init_sla_params() def->enum_labels.push_back(L("Zig-Zag")); def->enum_labels.push_back(L("Cross")); def->enum_labels.push_back(L("Dynamic")); + def->mode = comAdvanced; def->default_value = new ConfigOptionEnum(slapcmDynamic); def = this->add("support_buildplate_only", coBool); def->label = L("Support on build plate only"); def->category = L("Supports"); def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print."); - def->cli = "support-buildplate-only!"; def->mode = comSimple; def->default_value = new ConfigOptionBool(false); @@ -3224,9 +3000,9 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("Merging bridges or pillars into another pillars can " "increase the radius. Zero means no increase, one means " "full increase."); - def->cli = ""; def->min = 0; def->max = 1; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(0.0); def = this->add("support_base_diameter", coFloat); @@ -3234,8 +3010,9 @@ void PrintConfigDef::init_sla_params() def->category = L("Supports"); def->tooltip = L("Diameter in mm of the pillar base"); def->sidetext = L("mm"); - def->cli = ""; def->min = 0; + def->max = 30; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(4.0); def = this->add("support_base_height", coFloat); @@ -3243,8 +3020,8 @@ void PrintConfigDef::init_sla_params() def->category = L("Supports"); def->tooltip = L("The height of the pillar base cone"); def->sidetext = L("mm"); - def->cli = ""; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(1.0); def = this->add("support_critical_angle", coFloat); @@ -3252,8 +3029,9 @@ void PrintConfigDef::init_sla_params() def->category = L("Supports"); def->tooltip = L("The default angle for connecting support sticks and junctions."); def->sidetext = L("°"); - def->cli = ""; def->min = 0; + def->max = 90; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(45); def = this->add("support_max_bridge_length", coFloat); @@ -3261,17 +3039,28 @@ void PrintConfigDef::init_sla_params() def->category = L("Supports"); def->tooltip = L("The max length of a bridge"); def->sidetext = L("mm"); - def->cli = ""; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(15.0); + def = this->add("support_max_pillar_link_distance", coFloat); + def->label = L("Max pillar linking distance"); + def->category = L("Supports"); + def->tooltip = L("The max distance of two pillars to get linked with each other." + " A zero value will prohibit pillar cascading."); + def->sidetext = L("mm"); + def->min = 0; // 0 means no linking + def->mode = comAdvanced; + def->default_value = new ConfigOptionFloat(10.0); + def = this->add("support_object_elevation", coFloat); def->label = L("Object elevation"); def->category = L("Supports"); def->tooltip = L("How much the supports should lift up the supported object."); def->sidetext = L("mm"); - def->cli = ""; def->min = 0; + def->max = 150; // This is the max height of print on SL1 + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(5.0); def = this->add("support_points_density_relative", coInt); @@ -3279,7 +3068,6 @@ void PrintConfigDef::init_sla_params() def->category = L("Supports"); def->tooltip = L("This is a relative measure of support points density."); def->sidetext = L("%"); - def->cli = ""; def->min = 0; def->default_value = new ConfigOptionInt(100); @@ -3288,7 +3076,6 @@ void PrintConfigDef::init_sla_params() def->category = L("Supports"); def->tooltip = L("No support points will be placed closer than this threshold."); def->sidetext = L("mm"); - def->cli = ""; def->min = 0; def->default_value = new ConfigOptionFloat(0.f); @@ -3296,43 +3083,50 @@ void PrintConfigDef::init_sla_params() def->label = L("Use pad"); def->category = L("Pad"); def->tooltip = L("Add a pad underneath the supported model"); - def->cli = ""; + def->mode = comSimple; def->default_value = new ConfigOptionBool(true); def = this->add("pad_wall_thickness", coFloat); def->label = L("Pad wall thickness"); def->category = L("Pad"); -// def->tooltip = L(""); + def->tooltip = L("The thickness of the pad and its optional cavity walls."); def->sidetext = L("mm"); - def->cli = ""; def->min = 0; + def->max = 30; + def->mode = comSimple; def->default_value = new ConfigOptionFloat(2.0); def = this->add("pad_wall_height", coFloat); def->label = L("Pad wall height"); + def->tooltip = L("Defines the cavity depth. Set to zero to disable the cavity."); def->category = L("Pad"); // def->tooltip = L(""); def->sidetext = L("mm"); - def->cli = ""; def->min = 0; + def->max = 30; + def->mode = comSimple; def->default_value = new ConfigOptionFloat(5.0); def = this->add("pad_max_merge_distance", coFloat); def->label = L("Max merge distance"); def->category = L("Pad"); -// def->tooltip = L(""); + def->tooltip = L("Some objects can get along with a few smaller pads " + "instead of a single big one. This parameter defines " + "how far the center of two smaller pads should be. If they" + "are closer, they will get merged into one pad."); def->sidetext = L("mm"); - def->cli = ""; def->min = 0; + def->mode = comExpert; def->default_value = new ConfigOptionFloat(50.0); + // This is disabled on the UI. I hope it will never be enabled. def = this->add("pad_edge_radius", coFloat); def->label = L("Pad edge radius"); def->category = L("Pad"); // def->tooltip = L(""); def->sidetext = L("mm"); - def->cli = ""; def->min = 0; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(1.0); def = this->add("pad_wall_slope", coFloat); @@ -3341,9 +3135,9 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("The slope of the pad wall relative to the bed plane. " "90 degrees means straight walls."); def->sidetext = L("degrees"); - def->cli = ""; def->min = 45; def->max = 90; + def->mode = comAdvanced; def->default_value = new ConfigOptionFloat(45.0); } @@ -3573,7 +3367,7 @@ std::string FullPrintConfig::validate() 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) + for (unsigned char wipe : this->wipe.values) if (wipe) return "--use-firmware-retraction is not compatible with --wipe"; @@ -3627,7 +3421,7 @@ std::string FullPrintConfig::validate() return "Invalid value for --extruder-clearance-height"; // --extrusion-multiplier - for (float em : this->extrusion_multiplier.values) + for (double em : this->extrusion_multiplier.values) if (em <= 0) return "Invalid value for --extrusion-multiplier"; @@ -3732,164 +3526,218 @@ StaticPrintConfig::StaticCache SLAPrintObje StaticPrintConfig::StaticCache SLAPrinterConfig::s_cache_SLAPrinterConfig; StaticPrintConfig::StaticCache SLAFullPrintConfig::s_cache_SLAFullPrintConfig; -CLIConfigDef::CLIConfigDef() +CLIActionsConfigDef::CLIActionsConfigDef() { - ConfigOptionDef *def; + ConfigOptionDef* def; - def = this->add("cut", coFloat); - def->label = L("Cut"); - def->tooltip = L("Cut model at the given Z."); - def->cli = "cut"; - def->default_value = new ConfigOptionFloat(0); - - def = this->add("dont_arrange", coBool); - def->label = L("Dont arrange"); - def->tooltip = L("Don't arrange the objects on the build plate. The model coordinates " - "define the absolute positions on the build plate. " - "The option --center will be ignored."); - def->cli = "dont-arrange"; + // Actions: + def = this->add("export_obj", coBool); + def->label = L("Export SVG"); + def->tooltip = L("Export the model(s) as OBJ."); + def->default_value = new ConfigOptionBool(false); + +/* + def = this->add("export_svg", coBool); + def->label = L("Export SVG"); + def->tooltip = L("Slice the model and export solid slices as SVG."); + def->default_value = new ConfigOptionBool(false); +*/ + + def = this->add("export_sla", coBool); + def->label = L("Export SLA"); + def->tooltip = L("Slice the model and export SLA printing layers as PNG."); + def->cli = "export-sla|sla"; def->default_value = new ConfigOptionBool(false); - - def = this->add("datadir", coString); - def->label = L("User data directory"); - def->tooltip = L("Load and store settings at the given directory. " - "This is useful for maintaining different profiles or including " - "configurations from a network storage."); - def->cli = "datadir"; - def->default_value = new ConfigOptionString(); def = this->add("export_3mf", coBool); def->label = L("Export 3MF"); - def->tooltip = L("Slice the model and export slices as 3MF."); - def->cli = "export-3mf"; + def->tooltip = L("Export the model(s) as 3MF."); + def->default_value = new ConfigOptionBool(false); + + def = this->add("export_amf", coBool); + def->label = L("Export AMF"); + def->tooltip = L("Export the model(s) as AMF."); + def->default_value = new ConfigOptionBool(false); + + def = this->add("export_stl", coBool); + def->label = L("Export STL"); + def->tooltip = L("Export the model(s) as STL."); + def->default_value = new ConfigOptionBool(false); + + def = this->add("export_gcode", coBool); + def->label = L("Export G-code"); + def->tooltip = L("Slice the model and export toolpaths as G-code."); + def->cli = "export-gcode|gcode|g"; def->default_value = new ConfigOptionBool(false); def = this->add("slice", coBool); def->label = L("Slice"); - def->tooltip = L("Slice the model and export gcode."); - def->cli = "slice"; + def->tooltip = L("Slice the model as FFF or SLA based on the printer_technology configuration value."); + def->cli = "slice|s"; def->default_value = new ConfigOptionBool(false); def = this->add("help", coBool); def->label = L("Help"); def->tooltip = L("Show this help."); - def->cli = "help"; + def->cli = "help|h"; def->default_value = new ConfigOptionBool(false); - def = this->add("gui", coBool); - def->label = L("Use GUI"); - def->tooltip = L("Forces the GUI launch instead of command line slicing " - "(if you supply a model file, it will be loaded into the plater)"); - def->cli = "gui"; + def = this->add("help_fff", coBool); + def->label = L("Help (FFF options)"); + def->tooltip = L("Show the full list of print/G-code configuration options."); + def->default_value = new ConfigOptionBool(false); + + def = this->add("help_sla", coBool); + def->label = L("Help (SLA options)"); + def->tooltip = L("Show the full list of SLA print configuration options."); def->default_value = new ConfigOptionBool(false); def = this->add("info", coBool); def->label = L("Output Model Info"); def->tooltip = L("Write information about the model to the console."); - def->cli = "info"; def->default_value = new ConfigOptionBool(false); - def = this->add("load", coStrings); - def->label = L("Load config file"); - def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); - def->cli = "load"; - def->default_value = new ConfigOptionStrings(); - - def = this->add("no_gui", coBool); - def->label = L("Do not use GUI"); - def->tooltip = L("Forces the command line slicing instead of gui. This takes precedence over --gui if both are present."); - def->cli = "no-gui"; - def->default_value = new ConfigOptionBool(false); - - def = this->add("output", coString); - def->label = L("Output File"); - def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file)."); - def->cli = "output"; - def->default_value = new ConfigOptionString(""); - - def = this->add("rotate", coFloat); - def->label = L("Rotate"); - def->tooltip = L("Rotation angle around the Z axis in degrees (0-360, default: 0)."); - def->cli = "rotate"; - def->default_value = new ConfigOptionFloat(0); - - def = this->add("rotate_x", coFloat); - def->label = L("Rotate around X"); - def->tooltip = L("Rotation angle around the X axis in degrees (0-360, default: 0)."); - def->cli = "rotate-x"; - def->default_value = new ConfigOptionFloat(0); - - def = this->add("rotate_y", coFloat); - def->label = L("Rotate around Y"); - def->tooltip = L("Rotation angle around the Y axis in degrees (0-360, default: 0)."); - def->cli = "rotate-y"; - def->default_value = new ConfigOptionFloat(0); - def = this->add("save", coString); def->label = L("Save config file"); def->tooltip = L("Save configuration to the specified file."); - def->cli = "save"; def->default_value = new ConfigOptionString(); - - def = this->add("scale", coFloat); - def->label = L("Scale"); - def->tooltip = L("Scaling factor (default: 1)."); - def->cli = "scale"; - def->default_value = new ConfigOptionFloat(1); +} -/* +CLITransformConfigDef::CLITransformConfigDef() +{ + ConfigOptionDef* def; + + // Transform options: + def = this->add("align_xy", coPoint); + def->label = L("Align XY"); + def->tooltip = L("Align the model to the given point."); + def->default_value = new ConfigOptionPoint(Vec2d(100,100)); + + def = this->add("cut", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model at the given Z."); + def->default_value = new ConfigOptionFloat(0); + +/* + def = this->add("cut_grid", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model in the XY plane into tiles of the specified max size."); + def->default_value = new ConfigOptionPoint(); + + def = this->add("cut_x", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model at the given X."); + def->default_value = new ConfigOptionFloat(0); + + def = this->add("cut_y", coFloat); + def->label = L("Cut"); + def->tooltip = L("Cut model at the given Y."); + def->default_value = new ConfigOptionFloat(0); +*/ + + def = this->add("center", coPoint); + def->label = L("Center"); + def->tooltip = L("Center the print around the given center."); + def->default_value = new ConfigOptionPoint(Vec2d(100,100)); + + def = this->add("dont_arrange", coBool); + def->label = L("Don't arrange"); + def->tooltip = L("Do not rearrange the given models before merging and keep their original XY coordinates."); + + def = this->add("duplicate", coInt); + def->label = L("Duplicate"); + def->tooltip =L("Multiply copies by this factor."); + def->min = 1; + + def = this->add("duplicate_grid", coPoint); + def->label = L("Duplicate by grid"); + def->tooltip = L("Multiply copies by creating a grid."); + + def = this->add("merge", coBool); + def->label = L("Merge"); + def->tooltip = L("Arrange the supplied models in a plate and merge them in a single model in order to perform actions once."); + def->cli = "merge|m"; + + def = this->add("repair", coBool); + def->label = L("Repair"); + def->tooltip = L("Try to repair any non-manifold meshes (this option is implicitly added whenever we need to slice the model to perform the requested action)."); + + def = this->add("rotate", coFloat); + def->label = L("Rotate"); + def->tooltip = L("Rotation angle around the Z axis in degrees."); + def->default_value = new ConfigOptionFloat(0); + + def = this->add("rotate_x", coFloat); + def->label = L("Rotate around X"); + def->tooltip = L("Rotation angle around the X axis in degrees."); + def->default_value = new ConfigOptionFloat(0); + + def = this->add("rotate_y", coFloat); + def->label = L("Rotate around Y"); + def->tooltip = L("Rotation angle around the Y axis in degrees."); + def->default_value = new ConfigOptionFloat(0); + + def = this->add("scale", coFloatOrPercent); + def->label = L("Scale"); + def->tooltip = L("Scaling factor or percentage."); + def->default_value = new ConfigOptionFloatOrPercent(1, false); + + def = this->add("split", coBool); + def->label = L("Split"); + def->tooltip = L("Detect unconnected parts in the given model(s) and split them into separate objects."); + def = this->add("scale_to_fit", coPoint3); def->label = L("Scale to Fit"); def->tooltip = L("Scale to fit the given volume."); - def->cli = "scale-to-fit"; - def->default_value = new ConfigOptionPoint3(Pointf3(0,0,0)); -*/ - - def = this->add("print_center", coPoint); - def->label = L("Print center"); - def->tooltip = L("Center the print around the given center (default: 100, 100)."); - def->cli = "print-center"; - def->default_value = new ConfigOptionPoint(Vec2d(100,100)); + def->default_value = new ConfigOptionPoint3(Vec3d(0,0,0)); } -const CLIConfigDef cli_config_def; +CLIMiscConfigDef::CLIMiscConfigDef() +{ + ConfigOptionDef* def; + + def = this->add("ignore_nonexistent_config", coBool); + def->label = L("Ignore non-existent config files"); + def->tooltip = L("Do not fail if a file supplied to --load does not exist."); + + def = this->add("load", coStrings); + def->label = L("Load config file"); + def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); + + def = this->add("output", coString); + def->label = L("Output File"); + def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file)."); + def->cli = "output|o"; + +/* + def = this->add("autosave", coString); + def->label = L("Autosave"); + def->tooltip = L("Automatically export current configuration to the specified file."); +*/ + + def = this->add("datadir", coString); + def->label = L("Data directory"); + def->tooltip = L("Load and store settings at the given directory. This is useful for maintaining different profiles or including configurations from a network storage."); + + def = this->add("loglevel", coInt); + def->label = L("Logging level"); + def->tooltip = L("Messages with severity lower or eqal to the loglevel will be printed out. 0:trace, 1:debug, 2:info, 3:warning, 4:error, 5:fatal"); + def->min = 0; +} + +const CLIActionsConfigDef cli_actions_config_def; +const CLITransformConfigDef cli_transform_config_def; +const CLIMiscConfigDef cli_misc_config_def; + DynamicPrintAndCLIConfig::PrintAndCLIConfigDef DynamicPrintAndCLIConfig::s_def; void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std::string &value) const { - if (cli_config_def.options.find(opt_key) == cli_config_def.options.end()) { - PrintConfigDef::handle_legacy(opt_key, value); - } -} - -std::ostream& print_cli_options(std::ostream& out) -{ - for (const auto& opt : cli_config_def.options) { - if (opt.second.cli.size() != 0) { - out << "\t" << std::left << std::setw(40) << std::string("--") + opt.second.cli; - out << "\t" << opt.second.tooltip << "\n"; - if (opt.second.default_value != nullptr) - out << "\t" << std::setw(40) << " " << "\t" << " (default: " << opt.second.default_value->serialize() << ")"; - out << "\n"; - } - } - std::cerr << std::endl; - return out; -} - -std::ostream& print_print_options(std::ostream& out) -{ - for (const auto& opt : print_config_def.options) { - if (opt.second.cli.size() != 0) { - out << "\t" << std::left << std::setw(40) << std::string("--") + opt.second.cli; - out << "\t" << opt.second.tooltip << "\n"; - if (opt.second.default_value != nullptr) - out << "\t" << std::setw(40) << " " << "\t" << " (default: " << opt.second.default_value->serialize() << ")"; - out << "\n"; - } - } - std::cerr << std::endl; - return out; + if (cli_actions_config_def .options.find(opt_key) == cli_actions_config_def .options.end() && + cli_transform_config_def.options.find(opt_key) == cli_transform_config_def.options.end() && + cli_misc_config_def .options.find(opt_key) == cli_misc_config_def .options.end()) { + PrintConfigDef::handle_legacy(opt_key, value); + } } } diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 35c80e612..5b3b4cfef 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -24,17 +24,6 @@ namespace Slic3r { -enum PrinterTechnology -{ - // Fused Filament Fabrication - ptFFF, - // Stereolitography - ptSLA, - // laser-sintered powder - ptSLS, -}; - - enum WipeAlgo { waLinear, waQuadra, @@ -47,7 +36,7 @@ enum GCodeFlavor { }; enum PrintHostType { - htOctoPrint, htDuet, htSL1, + htOctoPrint, htDuet }; enum InfillPattern { @@ -97,6 +86,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnumKEY - -class CLIConfig : public virtual ConfigBase, public StaticConfig +class CLITransformConfigDef : public ConfigDef { public: - ConfigOptionFloat cut; - ConfigOptionString datadir; - ConfigOptionBool dont_arrange; - ConfigOptionBool export_3mf; - ConfigOptionBool gui; - ConfigOptionBool info; - ConfigOptionBool help; - ConfigOptionStrings load; - ConfigOptionBool no_gui; - ConfigOptionString output; - ConfigOptionPoint print_center; - ConfigOptionFloat rotate; - ConfigOptionFloat rotate_x; - ConfigOptionFloat rotate_y; - ConfigOptionString save; - ConfigOptionFloat scale; -// ConfigOptionPoint3 scale_to_fit; - ConfigOptionBool slice; - - CLIConfig() : ConfigBase(), StaticConfig() - { - this->set_defaults(); - }; - - // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. - const ConfigDef* def() const override { return &cli_config_def; } - t_config_option_keys keys() const override { return cli_config_def.keys(); } - - ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override - { - OPT_PTR(cut); - OPT_PTR(datadir); - OPT_PTR(dont_arrange); - OPT_PTR(export_3mf); - OPT_PTR(gui); - OPT_PTR(help); - OPT_PTR(info); - OPT_PTR(load); - OPT_PTR(no_gui); - OPT_PTR(output); - OPT_PTR(print_center); - OPT_PTR(rotate); - OPT_PTR(rotate_x); - OPT_PTR(rotate_y); - OPT_PTR(save); - OPT_PTR(scale); -// OPT_PTR(scale_to_fit); - OPT_PTR(slice); - return NULL; - } + CLITransformConfigDef(); }; -#undef OPT_PTR +class CLIMiscConfigDef : public ConfigDef +{ +public: + CLIMiscConfigDef(); +}; + +// This class defines the command line options representing actions. +extern const CLIActionsConfigDef cli_actions_config_def; + +// This class defines the command line options representing transforms. +extern const CLITransformConfigDef cli_transform_config_def; + +// This class defines all command line options that are not actions or transforms. +extern const CLIMiscConfigDef cli_misc_config_def; class DynamicPrintAndCLIConfig : public DynamicPrintConfig { @@ -1390,19 +1332,16 @@ private: public: PrintAndCLIConfigDef() { this->options.insert(print_config_def.options.begin(), print_config_def.options.end()); - this->options.insert(cli_config_def.options.begin(), cli_config_def.options.end()); + this->options.insert(cli_actions_config_def.options.begin(), cli_actions_config_def.options.end()); + this->options.insert(cli_transform_config_def.options.begin(), cli_transform_config_def.options.end()); + this->options.insert(cli_misc_config_def.options.begin(), cli_misc_config_def.options.end()); } - // Do not release the default values, they are handled by print_config_def & cli_config_def. + // Do not release the default values, they are handled by print_config_def & cli_actions_config_def / cli_transform_config_def / cli_misc_config_def. ~PrintAndCLIConfigDef() { this->options.clear(); } }; static PrintAndCLIConfigDef s_def; }; -/// Iterate through all of the print options and write them to a stream. -std::ostream& print_print_options(std::ostream& out); -/// Iterate through all of the CLI options and write them to a stream. -std::ostream& print_cli_options(std::ostream& out); - } // namespace Slic3r #endif diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1302002dc..23c9143e5 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -104,7 +104,7 @@ void PrintObject::slice() return; m_print->set_status(10, "Processing triangulated mesh"); std::vector layer_height_profile; - this->update_layer_height_profile(*this->model_object(), this->slicing_parameters(), layer_height_profile); + this->update_layer_height_profile(*this->model_object(), m_slicing_params, layer_height_profile); m_print->throw_if_canceled(); this->_slice(layer_height_profile); m_print->throw_if_canceled(); @@ -608,8 +608,11 @@ bool PrintObject::invalidate_step(PrintObjectStep step) } else if (step == posSlice) { invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posSupportMaterial }); invalidated |= m_print->invalidate_steps({ psSkirt, psBrim }); - } else if (step == posSupportMaterial) + this->m_slicing_params.valid = false; + } else if (step == posSupportMaterial) { invalidated |= m_print->invalidate_steps({ psSkirt, psBrim }); + this->m_slicing_params.valid = false; + } // Wipe tower depends on the ordering of extruders, which in turn depends on everything. // It also decides about what the wipe_into_infill / wipe_into_object features will do, @@ -1065,7 +1068,7 @@ void PrintObject::process_external_surfaces() for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); // BOOST_LOG_TRIVIAL(trace) << "Processing external surface, layer" << m_layers[layer_idx]->print_z; - m_layers[layer_idx]->get_region(region_id)->process_external_surfaces((layer_idx == 0) ? NULL : m_layers[layer_idx - 1]); + m_layers[layer_idx]->get_region((int)region_id)->process_external_surfaces((layer_idx == 0) ? NULL : m_layers[layer_idx - 1]); } } ); @@ -1684,11 +1687,11 @@ PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegion return config; } -SlicingParameters PrintObject::slicing_parameters() const +void PrintObject::update_slicing_parameters() { - return SlicingParameters::create_from_config( - this->print()->config(), m_config, - unscale(this->size(2)), this->object_extruders()); + if (! m_slicing_params.valid) + m_slicing_params = SlicingParameters::create_from_config( + this->print()->config(), m_config, unscale(this->size(2)), this->object_extruders()); } SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object) @@ -1775,23 +1778,21 @@ void PrintObject::_slice(const std::vector &layer_height_profile) 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, layer_height_profile); + std::vector object_layers = generate_object_layers(m_slicing_params, 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()); + int id = int(m_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); + Layer *layer = this->add_layer(id ++, hi - lo, hi + m_slicing_params.object_print_z_min, slice_z); slice_zs.push_back(float(slice_z)); if (prev != nullptr) { prev->upper_layer = layer; @@ -1803,46 +1804,151 @@ void PrintObject::_slice(const std::vector &layer_height_profile) prev = layer; } } + + // Count model parts and modifier meshes, check whether the model parts are of the same region. + int single_volume_region = -2; // not set yet + size_t num_volumes = 0; + size_t num_modifiers = 0; + std::vector map_volume_to_region(this->model_object()->volumes.size()); + for (int region_id = 0; region_id < (int)this->region_volumes.size(); ++ region_id) { + for (int volume_id : this->region_volumes[region_id]) { + const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; + if (model_volume->is_model_part()) { + map_volume_to_region[volume_id] = region_id; + if (single_volume_region == -2) + // first model volume met + single_volume_region = region_id; + else if (single_volume_region != region_id) + // multiple volumes met and they are not equal + single_volume_region = -1; + ++ num_volumes; + } else if (model_volume->is_modifier()) + ++ num_modifiers; + } + } + assert(num_volumes > 0); // Slice all non-modifier volumes. - for (size_t region_id = 0; region_id < this->region_volumes.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); - m_print->throw_if_canceled(); - 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) - m_layers[layer_id]->regions()[region_id]->slices.append(std::move(expolygons_by_layer[layer_id]), stPosInternal | stDensSparse); - m_print->throw_if_canceled(); - BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " end"; + bool clipped = false; + bool upscaled = false; + if (! m_config.clip_multipart_objects.value || single_volume_region >= 0) { + // Cheap path: Slice regions without mutual clipping. + // The cheap path is possible if no clipping is allowed or if slicing volumes of just a single region. + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + BOOST_LOG_TRIVIAL(debug) << "Slicing objects - region " << region_id; + // slicing in parallel + std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, false); + m_print->throw_if_canceled(); + 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) + m_layers[layer_id]->regions()[region_id]->slices.append(std::move(expolygons_by_layer[layer_id]), stPosInternal | stDensSparse); + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " end"; + } + } else { + // Expensive path: Slice one volume after the other in the order they are presented at the user interface, + // clip the last volumes with the first. + // First slice the volumes. + struct SlicedVolume { + SlicedVolume(int volume_id, int region_id, std::vector &&expolygons_by_layer) : + volume_id(volume_id), region_id(region_id), expolygons_by_layer(std::move(expolygons_by_layer)) {} + int volume_id; + int region_id; + std::vector expolygons_by_layer; + }; + std::vector sliced_volumes; + sliced_volumes.reserve(num_volumes); + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) + for (int volume_id : this->region_volumes[region_id]) { + const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; + if (model_volume->is_model_part()) { + BOOST_LOG_TRIVIAL(debug) << "Slicing objects - volume " << volume_id; + // slicing in parallel + sliced_volumes.emplace_back(volume_id, map_volume_to_region[volume_id], this->_slice_volume(slice_zs, *model_volume)); + } + } + // Second clip the volumes in the order they are presented at the user interface. + BOOST_LOG_TRIVIAL(debug) << "Slicing objects - parallel clipping - start"; + tbb::parallel_for( + tbb::blocked_range(0, slice_zs.size()), + [this, &sliced_volumes, num_modifiers](const tbb::blocked_range& range) { + float delta = float(scale_(m_config.xy_size_compensation.value)); + // Only upscale together with clipping if there are no modifiers, as the modifiers shall be applied before upscaling + // (upscaling may grow the object outside of the modifier mesh). + bool upscale = delta > 0 && num_modifiers == 0; + for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { + m_print->throw_if_canceled(); + // Trim volumes in a single layer, one by the other, possibly apply upscaling. + { + Polygons processed; + for (SlicedVolume &sliced_volume : sliced_volumes) { + ExPolygons slices = std::move(sliced_volume.expolygons_by_layer[layer_id]); + if (upscale) + slices = offset_ex(std::move(slices), delta); + if (! processed.empty()) + // Trim by the slices of already processed regions. + slices = diff_ex(to_polygons(std::move(slices)), processed); + if (size_t(&sliced_volume - &sliced_volumes.front()) + 1 < sliced_volumes.size()) + // Collect the already processed regions to trim the to be processed regions. + polygons_append(processed, slices); + sliced_volume.expolygons_by_layer[layer_id] = std::move(slices); + } + } + // Collect and union volumes of a single region. + for (int region_id = 0; region_id < (int)this->region_volumes.size(); ++ region_id) { + ExPolygons expolygons; + size_t num_volumes = 0; + for (SlicedVolume &sliced_volume : sliced_volumes) + if (sliced_volume.region_id == region_id && ! sliced_volume.expolygons_by_layer[layer_id].empty()) { + ++ num_volumes; + append(expolygons, std::move(sliced_volume.expolygons_by_layer[layer_id])); + } + if (num_volumes > 1) + // Merge the islands using a positive / negative offset. + expolygons = offset_ex(offset_ex(expolygons, float(scale_(EPSILON))), -float(scale_(EPSILON))); + m_layers[layer_id]->regions()[region_id]->slices.append(std::move(expolygons), stPosInternal | stDensSparse); + } + } + }); + BOOST_LOG_TRIVIAL(debug) << "Slicing objects - parallel clipping - end"; + clipped = true; + upscaled = m_config.xy_size_compensation.value > 0 && num_modifiers == 0; } // Slice all modifier volumes. if (this->region_volumes.size() > 1) { for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - region " << region_id; + // slicing in parallel std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, true); m_print->throw_if_canceled(); + if (expolygons_by_layer.empty()) + continue; // 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->region_volumes.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 = m_layers[layer_id]; - LayerRegion *layerm = layer->m_regions[region_id]; - LayerRegion *other_layerm = layer->m_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)), stPosInternal | stDensSparse); - // Append new parts to our region. - layerm->slices.append(std::move(my_parts), stPosInternal | stDensSparse); - } - } + tbb::parallel_for( + tbb::blocked_range(0, m_layers.size()), + [this, &expolygons_by_layer, region_id](const tbb::blocked_range& range) { + for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { + for (size_t other_region_id = 0; other_region_id < this->region_volumes.size(); ++ other_region_id) { + if (region_id == other_region_id) + continue; + Layer *layer = m_layers[layer_id]; + LayerRegion *layerm = layer->m_regions[region_id]; + LayerRegion *other_layerm = layer->m_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)), stPosInternal | stDensSparse); + // Append new parts to our region. + layerm->slices.append(std::move(my_parts), stPosInternal | stDensSparse); + } + } + }); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - stealing " << region_id << " end"; } @@ -1865,43 +1971,97 @@ end: BOOST_LOG_TRIVIAL(debug) << "Slicing objects - make_slices in parallel - begin"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this](const tbb::blocked_range& range) { + [this, upscaled, clipped](const tbb::blocked_range& range) { for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { m_print->throw_if_canceled(); Layer *layer = m_layers[layer_id]; // Apply size compensation and perform clipping of multi-part objects. float delta = float(scale_(m_config.xy_size_compensation.value)); float hole_delta = float(scale_(this->config().hole_size_compensation.value)); + float elephant_foot_compensation = 0.f; if (layer_id == 0) - delta += float(scale_(m_config.elefant_foot_compensation.value)); - bool scale = delta != 0.f; - bool clip = m_config.clip_multipart_objects.value || delta > 0.f; + elephant_foot_compensation = float(scale_(m_config.elefant_foot_compensation.value)); if (layer->m_regions.size() == 1) { - if (scale) { + // Optimized version for a single region layer. + if (layer_id == 0) { + if (delta > elephant_foot_compensation) { + delta -= elephant_foot_compensation; + elephant_foot_compensation = 0.f; + } else if (delta > 0) + elephant_foot_compensation -= delta; + } + if (delta != 0.f || elephant_foot_compensation > 0.f) { // Single region, growing or shrinking. LayerRegion *layerm = layer->m_regions.front(); - layerm->slices.set(offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), delta), stPosInternal | stDensSparse); + // Apply the XY compensation. + ExPolygons expolygons = (delta == 0.f) ? + to_expolygons(std::move(layerm->slices.surfaces)) : + offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), delta); + // Apply the elephant foot compensation. + if (elephant_foot_compensation > 0) { + float elephant_foot_spacing = float(layerm->flow(frExternalPerimeter).scaled_elephant_foot_spacing()); + float external_perimeter_nozzle = float(scale_(this->print()->config().nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder.value - 1))); + // Apply the elephant foot compensation by steps of 1/10 nozzle diameter. + float steps = std::ceil(elephant_foot_compensation / (0.1f * external_perimeter_nozzle)); + size_t nsteps = size_t(steps); + float step = elephant_foot_compensation / steps; + for (size_t i = 0; i < nsteps; ++ i) { + Polygons tmp = offset(expolygons, - step); + append(tmp, diff(to_polygons(expolygons), offset(offset_ex(expolygons, -elephant_foot_spacing - step), elephant_foot_spacing + step))); + expolygons = union_ex(tmp); + } + } + layerm->slices.set(std::move(expolygons), stPosInternal | stDensSparse); } _offset_holes(hole_delta, layer->regions().front()); _smooth_curves(layer->regions().front()); - } else if (scale || clip || hole_delta != 0.f) { - // 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->m_regions.size(); ++ region_id) { - LayerRegion *layerm = layer->m_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->m_regions.size()) - // Collect the already processed regions to trim the to be processed regions. - polygons_append(processed, slices); - layerm->slices.set(std::move(slices), stPosInternal | stDensSparse); - _offset_holes(hole_delta, layerm); - _smooth_curves(layerm); + } else { + bool upscale = ! upscaled && delta > 0.f; + bool clip = ! clipped && m_config.clip_multipart_objects.value; + if (upscale || clip || hole_delta != 0.f) { + // Multiple regions, growing 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->m_regions.size(); ++ region_id) { + LayerRegion *layerm = layer->m_regions[region_id]; + ExPolygons slices = to_expolygons(std::move(layerm->slices.surfaces)); + if (upscale) + slices = offset_ex(std::move(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->m_regions.size())) + // Collect the already processed regions to trim the to be processed regions. + polygons_append(processed, slices); + layerm->slices.set(std::move(slices), stPosInternal | stDensSparse); + } + } + if (delta < 0.f) { + // Apply the negative XY compensation. + Polygons trimming = offset(layer->merged(float(EPSILON)), delta - float(EPSILON)); + for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) + layer->m_regions[region_id]->trim_surfaces(trimming); + } + if (elephant_foot_compensation > 0.f) { + // Apply the elephant foot compensation. + std::vector elephant_foot_spacing; + elephant_foot_spacing.reserve(layer->m_regions.size()); + float external_perimeter_nozzle = 0.f; + for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) { + LayerRegion *layerm = layer->m_regions[region_id]; + elephant_foot_spacing.emplace_back(float(layerm->flow(frExternalPerimeter).scaled_elephant_foot_spacing())); + external_perimeter_nozzle += float(scale_(this->print()->config().nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder.value - 1))); + } + external_perimeter_nozzle /= (float)layer->m_regions.size(); + // Apply the elephant foot compensation by steps of 1/10 nozzle diameter. + float steps = std::ceil(elephant_foot_compensation / (0.1f * external_perimeter_nozzle)); + size_t nsteps = size_t(steps); + float step = elephant_foot_compensation / steps; + for (size_t i = 0; i < nsteps; ++ i) { + Polygons trimming_polygons = offset(layer->merged(float(EPSILON)), - step - float(EPSILON)); + for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) + layer->m_regions[region_id]->elephant_foot_compensation_step(elephant_foot_spacing[region_id] + step, trimming_polygons); + } } } // Merge all regions' slices to get islands, chain them by a shortest path. @@ -2109,6 +2269,28 @@ std::vector PrintObject::_slice_volumes(const std::vector &z, return layers; } +std::vector PrintObject::_slice_volume(const std::vector &z, const ModelVolume &volume) const +{ + std::vector layers; + // Compose mesh. + //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. + TriangleMesh mesh(volume.mesh); + mesh.transform(volume.get_matrix()); + if (mesh.stl.stats.number_of_facets > 0) { + mesh.transform(m_trafo); + // apply XY shift + mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), 0); + // perform actual slicing + TriangleMeshSlicer mslicer(float(m_config.slice_closing_radius.value), float(m_config.model_precision.value)); + const Print *print = this->print(); + auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); + mslicer.init(&mesh, callback); + mslicer.slice(z, &layers, callback); + m_print->throw_if_canceled(); + } + return layers; +} + std::string PrintObject::_fix_slicing_errors() { // Collect layers with slicing errors. @@ -2708,7 +2890,7 @@ void PrintObject::combine_infill() void PrintObject::_generate_support_material() { - PrintObjectSupportMaterial support_material(this, PrintObject::slicing_parameters()); + PrintObjectSupportMaterial support_material(this, m_slicing_params); support_material.generate(*this); } diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index e99ed111e..9e9f07b6c 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -496,7 +496,7 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end()); } for (const Vec2f &pt : poisson_samples) { - m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, 0.2f, is_new_island); + m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, m_config.head_diameter/2.f, is_new_island); structure.supports_force_this_layer += m_config.support_force(); grid3d.insert(pt, &structure); } diff --git a/src/libslic3r/SLA/SLAAutoSupports.hpp b/src/libslic3r/SLA/SLAAutoSupports.hpp index 5a758c2a6..038b505da 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.hpp +++ b/src/libslic3r/SLA/SLAAutoSupports.hpp @@ -17,6 +17,7 @@ public: struct Config { float density_relative; float minimal_distance; + float head_diameter; /////////////// inline float support_force() const { return 10.f / density_relative; } // a force one point can support (arbitrary force unit) inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 62d8f0f30..6518ede07 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -601,14 +601,16 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, const double thickness = cfg.min_wall_thickness_mm; const double wingheight = cfg.min_wall_height_mm; const double fullheight = wingheight + thickness; - const double slope = cfg.wall_slope; + const double slope = cfg.wall_slope; const double wingdist = wingheight / std::tan(slope); + const double bottom_offs = (thickness + wingheight) / std::tan(slope); // scaled values const coord_t s_thickness = mm(thickness); const coord_t s_eradius = mm(cfg.edge_radius_mm); const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness); const coord_t s_wingdist = mm(wingdist); + const coord_t s_bottom_offs = mm(bottom_offs); auto& thrcl = cfg.throw_on_cancel; @@ -620,7 +622,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, // Get rid of any holes in the concave hull output. concaveh.holes.clear(); - // Here lies the trick that does the smooting only with clipper offset + // Here lies the trick that does the smoothing only with clipper offset // calls. The offset is configured to round edges. Inner edges will // be rounded because we offset twice: ones to get the outer (top) plate // and again to get the inner (bottom) plate @@ -628,10 +630,9 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, outer_base.holes.clear(); offset(outer_base, s_safety_dist + s_wingdist + s_thickness); - ExPolygon bottom_poly = outer_base; bottom_poly.holes.clear(); - if(s_wingdist > 0) offset(bottom_poly, -s_wingdist); + offset(bottom_poly, -s_bottom_offs); // Punching a hole in the top plate for the cavity ExPolygon top_poly; @@ -692,7 +693,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, // Now that we have the rounded edge connecting the top plate with // the outer side walls, we can generate and merge the sidewall geometry pool.merge(walls(ob.contour, bottom_poly.contour, wh, -fullheight, - wingdist, thrcl)); + bottom_offs, thrcl)); if(wingheight > 0) { // Generate the smoothed edge geometry diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index bdeead9ca..855802759 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -2,6 +2,7 @@ #define SLACOMMON_HPP #include +#include // #define SLIC3R_SLA_NEEDS_WINDTREE @@ -44,7 +45,6 @@ struct SupportPoint { bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); } }; - /// An index-triangle structure for libIGL functions. Also serves as an /// alternative (raw) input format for the SLASupportTree /*struct EigenMesh3D { @@ -78,24 +78,38 @@ public: // Result of a raycast class hit_result { - double m_t = std::numeric_limits::infinity(); + double m_t = std::nan(""); int m_face_id = -1; - const EigenMesh3D& m_mesh; + const EigenMesh3D *m_mesh = nullptr; Vec3d m_dir; - inline hit_result(const EigenMesh3D& em): m_mesh(em) {} + Vec3d m_source; friend class EigenMesh3D; + + // A valid object of this class can only be obtained from + // EigenMesh3D::query_ray_hit method. + explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {} public: + // This can create a placeholder object which is invalid (not created + // by a query_ray_hit call) but the distance can be preset to + // a specific value for distinguishing the placeholder. + inline hit_result(double val = std::nan("")): m_t(val) {} + inline double distance() const { return m_t; } inline const Vec3d& direction() const { return m_dir; } + inline Vec3d position() const { return m_source + m_dir * m_t; } inline int face() const { return m_face_id; } + inline bool is_valid() const { return m_mesh != nullptr; } + + // Hit_result can decay into a double as the hit distance. + inline operator double() const { return distance(); } inline Vec3d normal() const { - if(m_face_id < 0) return {}; - auto trindex = m_mesh.m_F.row(m_face_id); - const Vec3d& p1 = m_mesh.V().row(trindex(0)); - const Vec3d& p2 = m_mesh.V().row(trindex(1)); - const Vec3d& p3 = m_mesh.V().row(trindex(2)); + if(m_face_id < 0 || !is_valid()) return {}; + auto trindex = m_mesh->m_F.row(m_face_id); + const Vec3d& p1 = m_mesh->V().row(trindex(0)); + const Vec3d& p2 = m_mesh->V().row(trindex(1)); + const Vec3d& p3 = m_mesh->V().row(trindex(2)); Eigen::Vector3d U = p2 - p1; Eigen::Vector3d V = p3 - p1; return U.cross(V).normalized(); @@ -133,6 +147,8 @@ public: bool inside(const Vec3d& p) const; #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ + + double squared_distance(const Vec3d& p, int& i, Vec3d& c) const; }; @@ -142,4 +158,4 @@ public: } // namespace Slic3r -#endif // SLASUPPORTTREE_HPP \ No newline at end of file +#endif // SLASUPPORTTREE_HPP diff --git a/src/libslic3r/SLA/SLASpatIndex.hpp b/src/libslic3r/SLA/SLASpatIndex.hpp index 792e86c85..e5fbfa7d4 100644 --- a/src/libslic3r/SLA/SLASpatIndex.hpp +++ b/src/libslic3r/SLA/SLASpatIndex.hpp @@ -43,6 +43,8 @@ public: // For testing size_t size() const; bool empty() const { return size() == 0; } + + void foreach(std::function fn); }; } diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 913e9beda..5efa3001c 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -12,9 +12,14 @@ #include #include -#include +#include #include #include +#include + +//! macro used to mark string used at localization, +//! return same string +#define L(s) Slic3r::I18N::translate(s) /** * Terminology: @@ -58,6 +63,21 @@ namespace Slic3r { namespace sla { +// Compile time configuration value definitions: + +// The max Z angle for a normal at which it will get completely ignored. +const double SupportConfig::normal_cutoff_angle = 150.0 * M_PI / 180.0; + +// The shortest distance of any support structure from the model surface +const double SupportConfig::safety_distance_mm = 0.5; + +const double SupportConfig::max_solo_pillar_height_mm = 15.0; +const double SupportConfig::max_dual_pillar_height_mm = 35.0; +const double SupportConfig::optimizer_rel_score_diff = 1e-6; +const unsigned SupportConfig::optimizer_max_iterations = 1000; +const unsigned SupportConfig::pillar_cascade_neighbors = 3; +const unsigned SupportConfig::max_bridges_on_pillar = 3; + using Coordf = double; using Portion = std::tuple; @@ -335,6 +355,11 @@ struct Head { return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm; } + static double fullwidth(const SupportConfig& cfg) { + return 2 * cfg.head_front_radius_mm + cfg.head_width_mm + + 2 * cfg.head_back_radius_mm - cfg.head_penetration_mm; + } + Vec3d junction_point() const { return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir; } @@ -366,7 +391,8 @@ struct Pillar { Contour3D base; double r = 1; size_t steps = 0; - Vec3d endpoint; + Vec3d endpt; + double height = 0; long id = -1; @@ -374,20 +400,27 @@ struct Pillar { bool starts_from_head = true; // Could start from a junction as well long start_junction_id = -1; + // How many bridges are connected to this pillar + unsigned bridges = 0; + + // How many pillars are cascaded with this one + unsigned links = 0; + Pillar(const Vec3d& jp, const Vec3d& endp, double radius = 1, size_t st = 45): - r(radius), steps(st), endpoint(endp), starts_from_head(false) + r(radius), steps(st), endpt(endp), starts_from_head(false) { assert(steps > 0); - double h = jp(Z) - endp(Z); - assert(h > 0); // Endpoint is below the starting point + height = jp(Z) - endp(Z); + if(height > 0) { // Endpoint is below the starting point - // We just create a bridge geometry with the pillar parameters and - // move the data. - Contour3D body = cylinder(radius, h, st, endp); - mesh.points.swap(body.points); - mesh.indices.swap(body.indices); + // We just create a bridge geometry with the pillar parameters and + // move the data. + Contour3D body = cylinder(radius, height, st, endp); + mesh.points.swap(body.points); + mesh.indices.swap(body.indices); + } } Pillar(const Junction& junc, const Vec3d& endp): @@ -399,8 +432,15 @@ struct Pillar { { } - void add_base(double height = 3, double radius = 2) { - if(height <= 0) return; + inline Vec3d startpoint() const { + return {endpt(X), endpt(Y), endpt(Z) + height}; + } + + inline const Vec3d& endpoint() const { return endpt; } + + Pillar& add_base(double baseheight = 3, double radius = 2) { + if(baseheight <= 0) return *this; + if(baseheight > height) baseheight = height; assert(steps >= 0); auto last = int(steps - 1); @@ -408,24 +448,24 @@ struct Pillar { if(radius < r ) radius = r; double a = 2*PI/steps; - double z = endpoint(2) + height; + double z = endpt(Z) + baseheight; for(size_t i = 0; i < steps; ++i) { double phi = i*a; - double x = endpoint(0) + r*std::cos(phi); - double y = endpoint(1) + r*std::sin(phi); + double x = endpt(X) + r*std::cos(phi); + double y = endpt(Y) + r*std::sin(phi); base.points.emplace_back(x, y, z); } for(size_t i = 0; i < steps; ++i) { double phi = i*a; - double x = endpoint(0) + radius*std::cos(phi); - double y = endpoint(1) + radius*std::sin(phi); - base.points.emplace_back(x, y, z - height); + double x = endpt(X) + radius*std::cos(phi); + double y = endpt(Y) + radius*std::sin(phi); + base.points.emplace_back(x, y, z - baseheight); } - auto ep = endpoint; ep(2) += height; - base.points.emplace_back(endpoint); + auto ep = endpt; ep(Z) += baseheight; + base.points.emplace_back(endpt); base.points.emplace_back(ep); auto& indices = base.indices; @@ -443,7 +483,7 @@ struct Pillar { indices.emplace_back(last, offs + last, offs); indices.emplace_back(hcenter, last, 0); indices.emplace_back(offs, offs + last, lcenter); - + return *this; } bool has_base() const { return !base.points.empty(); } @@ -551,209 +591,13 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers X, Y, Z }; -PointSet to_point_set(const std::vector &v) -{ - PointSet ret(v.size(), 3); - long i = 0; - for(const SupportPoint& support_point : v) { - ret.row(i)(0) = support_point.pos(0); - ret.row(i)(1) = support_point.pos(1); - ret.row(i)(2) = support_point.pos(2); - ++i; - } - return ret; -} - -Vec3d model_coord(const ModelInstance& object, const Vec3f& mesh_coord) { - return object.transform_vector(mesh_coord.cast()); -} - -inline double ray_mesh_intersect(const Vec3d& s, - const Vec3d& dir, - const EigenMesh3D& m) -{ - return m.query_ray_hit(s, dir).distance(); -} - -// This function will test if a future pinhead would not collide with the model -// geometry. It does not take a 'Head' object because those are created after -// this test. -// Parameters: -// s: The touching point on the model surface. -// dir: This is the direction of the head from the pin to the back -// r_pin, r_back: the radiuses of the pin and the back sphere -// width: This is the full width from the pin center to the back center -// m: The object mesh -// -// Optional: -// samples: how many rays will be shot -// safety distance: This will be added to the radiuses to have a safety distance -// from the mesh. -double pinhead_mesh_intersect(const Vec3d& s, - const Vec3d& dir, - double r_pin, - double r_back, - double width, - const EigenMesh3D& m, - unsigned samples = 16, - double safety_distance = 0.001) -{ - // method based on: - // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space - - - // We will shoot multiple rays from the head pinpoint in the direction of - // the pinhead robe (side) surface. The result will be the smallest hit - // distance. - - // Move away slightly from the touching point to avoid raycasting on the - // inner surface of the mesh. - Vec3d v = dir; // Our direction (axis) - Vec3d c = s + width * dir; - const double& sd = safety_distance; - - // Two vectors that will be perpendicular to each other and to the axis. - // Values for a(X) and a(Y) are now arbitrary, a(Z) is just a placeholder. - Vec3d a(0, 1, 0), b; - - // The portions of the circle (the head-back circle) for which we will shoot - // rays. - std::vector phis(samples); - for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); - - // We have to address the case when the direction vector v (same as dir) - // is coincident with one of the world axes. In this case two of its - // components will be completely zero and one is 1.0. Our method becomes - // dangerous here due to division with zero. Instead, vector 'a' can be an - // element-wise rotated version of 'v' - auto chk1 = [] (double val) { return std::abs(std::abs(val) - 1) < 1e-20; }; - if(chk1(v(X)) || chk1(v(Y)) || chk1(v(Z))) { - a = {v(Z), v(X), v(Y)}; - b = {v(Y), v(Z), v(X)}; - } - else { - a(Z) = -(v(Y)*a(Y)) / v(Z); a.normalize(); - b = a.cross(v); - } - - // Now a and b vectors are perpendicular to v and to each other. Together - // they define the plane where we have to iterate with the given angles - // in the 'phis' vector - - tbb::parallel_for(size_t(0), phis.size(), - [&phis, &m, sd, r_pin, r_back, s, a, b, c](size_t i) - { - double& phi = phis[i]; - double sinphi = std::sin(phi); - double cosphi = std::cos(phi); - - // Let's have a safety coefficient for the radiuses. - double rpscos = (sd + r_pin) * cosphi; - double rpssin = (sd + r_pin) * sinphi; - double rpbcos = (sd + r_back) * cosphi; - double rpbsin = (sd + r_back) * sinphi; - - // Point on the circle on the pin sphere - Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X), - s(Y) + rpscos * a(Y) + rpssin * b(Y), - s(Z) + rpscos * a(Z) + rpssin * b(Z)); - - // Point ps is not on mesh but can be inside or outside as well. This - // would cause many problems with ray-casting. To detect the position we - // will use the ray-casting result (which has an is_inside predicate). - - // This is the point on the circle on the back sphere - Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X), - c(Y) + rpbcos * a(Y) + rpbsin * b(Y), - c(Z) + rpbcos * a(Z) + rpbsin * b(Z)); - - Vec3d n = (p - ps).normalized(); - auto hr = m.query_ray_hit(ps + sd*n, n); - - if(hr.is_inside()) { // the hit is inside the model - if(hr.distance() > 2*r_pin) phi = 0; - else { - // re-cast the ray from the outside of the object - auto hr2 = m.query_ray_hit(ps + (hr.distance() + 2*sd)*n, n); - phi = hr2.distance(); - } - } else phi = hr.distance(); - }); - - auto mit = std::min_element(phis.begin(), phis.end()); - - return *mit; -} - - -// Checking bridge (pillar and stick as well) intersection with the model. If -// the function is used for headless sticks, the ins_check parameter have to be -// true as the beginning of the stick might be inside the model geometry. -double bridge_mesh_intersect(const Vec3d& s, - const Vec3d& dir, - double r, - const EigenMesh3D& m, - bool ins_check = false, - unsigned samples = 4, - double safety_distance = 0.001) -{ - // helper vector calculations - Vec3d a(0, 1, 0), b; - const double& sd = safety_distance; - - // INFO: for explanation of the method used here, see the previous method's - // comments. - - auto chk1 = [] (double val) { return std::abs(std::abs(val) - 1) < 1e-20; }; - if(chk1(dir(X)) || chk1(dir(Y)) || chk1(dir(Z))) { - a = {dir(Z), dir(X), dir(Y)}; - b = {dir(Y), dir(Z), dir(X)}; - } - else { - a(Z) = -(dir(Y)*a(Y)) / dir(Z); a.normalize(); - b = a.cross(dir); - } - - // circle portions - std::vector phis(samples); - for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); - - tbb::parallel_for(size_t(0), phis.size(), - [&phis, &m, a, b, sd, dir, r, s, ins_check](size_t i) - { - double& phi = phis[i]; - double sinphi = std::sin(phi); - double cosphi = std::cos(phi); - - // Let's have a safety coefficient for the radiuses. - double rcos = (sd + r) * cosphi; - double rsin = (sd + r) * sinphi; - - // Point on the circle on the pin sphere - Vec3d p (s(X) + rcos * a(X) + rsin * b(X), - s(Y) + rcos * a(Y) + rsin * b(Y), - s(Z) + rcos * a(Z) + rsin * b(Z)); - - auto hr = m.query_ray_hit(p + sd*dir, dir); - - if(ins_check && hr.is_inside()) { - if(hr.distance() > 2*r) phi = 0; - else { - // re-cast the ray from the outside of the object - auto hr2 = m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir); - phi = hr2.distance(); - } - } else phi = hr.distance(); - }); - - auto mit = std::min_element(phis.begin(), phis.end()); - - return *mit; -} - -PointSet normals(const PointSet& points, const EigenMesh3D& mesh, +// Calculate the normals for the selected points (from 'points' set) on the +// mesh. This will call squared distance for each point. +PointSet normals(const PointSet& points, + const EigenMesh3D& mesh, double eps = 0.05, // min distance from edges - std::function throw_on_cancel = [](){}); + std::function throw_on_cancel = [](){}, + const std::vector& selected_points = {}); inline Vec2d to_vec2(const Vec3d& v3) { return {v3(X), v3(Y)}; @@ -763,11 +607,21 @@ bool operator==(const SpatElement& e1, const SpatElement& e2) { return e1.second == e2.second; } -// Clustering a set of points by the given criteria +// Clustering a set of points by the given distance. +ClusteredPoints cluster(const std::vector& indices, + std::function pointfn, + double dist, + unsigned max_points); + +ClusteredPoints cluster(const PointSet& points, + double dist, + unsigned max_points); + ClusteredPoints cluster( - const PointSet& points, - std::function pred, - unsigned max_points = 0); + const std::vector& indices, + std::function pointfn, + std::function predicate, + unsigned max_points); // This class will hold the support tree meshes with some additional bookkeeping // as well. Various parts of the support geometry are stored separately and are @@ -783,7 +637,7 @@ ClusteredPoints cluster( // The support pad is considered an auxiliary geometry and is not part of the // merged mesh. It can be retrieved using a dedicated method (pad()) class SLASupportTree::Impl { - std::vector m_heads; + std::map m_heads; std::vector m_pillars; std::vector m_junctions; std::vector m_bridges; @@ -801,16 +655,19 @@ public: const Controller& ctl() const { return m_ctl; } - template Head& add_head(Args&&... args) { - m_heads.emplace_back(std::forward(args)...); - m_heads.back().id = long(m_heads.size() - 1); + template Head& add_head(unsigned id, Args&&... args) { + auto el = m_heads.emplace(std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple(std::forward(args)...)); + el.first->second.id = id; meshcache_valid = false; - return m_heads.back(); + return el.first->second; } - template Pillar& add_pillar(long headid, Args&&... args) { - assert(headid >= 0 && headid < m_heads.size()); - Head& head = m_heads[size_t(headid)]; + template Pillar& add_pillar(unsigned headid, Args&&... args) { + auto it = m_heads.find(headid); + assert(it != m_heads.end()); + Head& head = it->second; m_pillars.emplace_back(head, std::forward(args)...); Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); @@ -821,19 +678,45 @@ public: return m_pillars.back(); } - const Head& pillar_head(long pillar_id) const { - assert(pillar_id >= 0 && pillar_id < m_pillars.size()); - const Pillar& p = m_pillars[size_t(pillar_id)]; - assert(p.starts_from_head && p.start_junction_id >= 0 && - p.start_junction_id < m_heads.size() ); - return m_heads[size_t(p.start_junction_id)]; + void increment_bridges(const Pillar& pillar) { + assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); + + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) + m_pillars[size_t(pillar.id)].bridges++; } - const Pillar& head_pillar(long headid) const { - assert(headid >= 0 && headid < m_heads.size()); - const Head& h = m_heads[size_t(headid)]; - assert(h.pillar_id >= 0 && h.pillar_id < m_pillars.size()); - return m_pillars[size_t(h.pillar_id)]; + void increment_links(const Pillar& pillar) { + assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); + + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) + m_pillars[size_t(pillar.id)].links++; + } + + template Pillar& add_pillar(Args&&...args) + { + m_pillars.emplace_back(std::forward(args)...); + Pillar& pillar = m_pillars.back(); + pillar.id = long(m_pillars.size() - 1); + pillar.starts_from_head = false; + meshcache_valid = false; + return m_pillars.back(); + } + + const Head& pillar_head(long pillar_id) const { + assert(pillar_id >= 0 && pillar_id < long(m_pillars.size())); + const Pillar& p = m_pillars[size_t(pillar_id)]; + assert(p.starts_from_head && p.start_junction_id >= 0); + auto it = m_heads.find(unsigned(p.start_junction_id)); + assert(it != m_heads.end()); + return it->second; + } + + const Pillar& head_pillar(unsigned headid) const { + auto it = m_heads.find(headid); + assert(it != m_heads.end()); + const Head& h = it->second; + assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); + return pillar(h.pillar_id); } template const Junction& add_junction(Args&&... args) { @@ -858,8 +741,13 @@ public: return m_compact_bridges.back(); } - const std::vector& heads() const { return m_heads; } - Head& head(size_t idx) { meshcache_valid = false; return m_heads[idx]; } + const std::map& heads() const { return m_heads; } + Head& head(unsigned idx) { + meshcache_valid = false; + auto it = m_heads.find(idx); + assert(it != m_heads.end()); + return it->second; + } const std::vector& pillars() const { return m_pillars; } const std::vector& bridges() const { return m_bridges; } const std::vector& junctions() const { return m_junctions; } @@ -867,6 +755,13 @@ public: return m_compact_bridges; } + template inline const Pillar& pillar(T id) const { + static_assert(std::is_integral::value, "Invalid index type"); + assert(id >= 0 && id < m_pillars.size() && + id < std::numeric_limits::max()); + return m_pillars[size_t(id)]; + } + const Pad& create_pad(const TriangleMesh& object_supports, const ExPolygons& baseplate, const PoolConfig& cfg) { @@ -886,10 +781,10 @@ public: Contour3D merged; - for(auto& head : heads()) { + for(auto& headel : heads()) { if(m_ctl.stopcondition()) break; - if(head.is_valid()) - merged.merge(head.mesh); + if(headel.second.is_valid()) + merged.merge(headel.second.mesh); } for(auto& stick : pillars()) { @@ -950,6 +845,8 @@ public: }; +// This function returns the position of the centroid in the input 'clust' +// vector of point indices. template long cluster_centroid(const ClusterEl& clust, std::function pointfn, @@ -970,6 +867,9 @@ long cluster_centroid(const ClusterEl& clust, // distance for the two points and add the distance to the averages. // The point with the smallest average than wins. + // The complexity should be O(n^2) but we will mostly apply this function + // for small clusters only (cca 3 elements) + std::vector sel(clust.size(), false); // create full zero bitmask std::fill(sel.end() - 2, sel.end(), true); // insert the two ones std::vector avgs(clust.size(), 0.0); // store the average distances @@ -995,230 +895,519 @@ long cluster_centroid(const ClusterEl& clust, return long(minit - avgs.begin()); } -/** - * This function will calculate the convex hull of the input point set and - * return the indices of those points belonging to the chull in the right - * (counter clockwise) order. The input is also the set of indices and a - * functor to get the actual point form the index. - * - * I've adapted this algorithm from here: - * https://www.geeksforgeeks.org/convex-hull-set-1-jarviss-algorithm-or-wrapping/ - * and modified it so that it starts with the leftmost lower vertex. Also added - * support for floating point coordinates. - * - * This function is a modded version of the standard convex hull. If the points - * are all collinear with each other, it will return their indices in spatially - * subsequent order (the order they appear on the screen). - */ -ClusterEl pts_convex_hull(const ClusterEl& inpts, - std::function pfn) -{ - using Point = Vec2d; - using std::vector; - - static const double ERR = 1e-3; - - auto orientation = [](const Point& p, const Point& q, const Point& r) - { - double val = (q(Y) - p(Y)) * (r(X) - q(X)) - - (q(X) - p(X)) * (r(Y) - q(Y)); - - if (std::abs(val) < ERR) return 0; // collinear - return (val > ERR)? 1: 2; // clock or counterclockwise - }; - - size_t n = inpts.size(); - - if (n < 3) return inpts; - - // Initialize Result - ClusterEl hull; - vector points; points.reserve(n); - for(auto i : inpts) { - points.emplace_back(pfn(i)); - } - - // Check if the triplet of points is collinear. The standard convex hull - // algorithms are not capable of handling such input properly. - bool collinear = true; - for(auto one = points.begin(), two = std::next(one), three = std::next(two); - three != points.end() && collinear; - ++one, ++two, ++three) - { - // check if the points are collinear - if(orientation(*one, *two, *three) != 0) collinear = false; - } - - // Find the leftmost (bottom) point - size_t l = 0; - for (size_t i = 1; i < n; i++) { - if(std::abs(points[i](X) - points[l](X)) < ERR) { - if(points[i](Y) < points[l](Y)) l = i; - } - else if (points[i](X) < points[l](X)) l = i; - } - - if(collinear) { - // fill the output with the spatially ordered set of points. - - // find the direction - hull = inpts; - auto& lp = points[l]; - std::sort(hull.begin(), hull.end(), - [&lp, points](unsigned i1, unsigned i2) { - // compare the distance from the leftmost point - return distance(lp, points[i1]) < distance(lp, points[i2]); - }); - - return hull; - } - - // TODO: this algorithm is O(m*n) and O(n^2) in the worst case so it needs - // to be replaced with a graham scan or something O(nlogn) - - // Start from leftmost point, keep moving counterclockwise - // until reach the start point again. This loop runs O(h) - // times where h is number of points in result or output. - size_t p = l; - do - { - // Add current point to result - hull.push_back(inpts[p]); - - // Search for a point 'q' such that orientation(p, x, - // q) is counterclockwise for all points 'x'. The idea - // is to keep track of last visited most counterclock- - // wise point in q. If any point 'i' is more counterclock- - // wise than q, then update q. - size_t q = (p + 1) % n; - for (size_t i = 0; i < n; i++) - { - // If i is more counterclockwise than current q, then - // update q - if (orientation(points[p], points[i], points[q]) == 2) q = i; - } - - // Now q is the most counterclockwise with respect to p - // Set p as q for next iteration, so that q is added to - // result 'hull' - p = q; - - } while (p != l); // While we don't come to first point - - auto first = hull.front(); - hull.emplace_back(first); - - return hull; -} - -Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { +inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) { return (endp - startp).normalized(); } -/// Generation of the supports, entry point function. This is called from the -/// SLASupportTree constructor and throws an SLASupportsStoppedException if it -/// gets canceled by the ctl object's stopcondition functor. -bool SLASupportTree::generate(const PointSet &points, - const EigenMesh3D& mesh, - const SupportConfig &cfg, - const Controller &ctl) -{ - // If there are no input points there is no point in doing anything - if(points.rows() == 0) return false; +class SLASupportTree::Algorithm { + const SupportConfig& m_cfg; + const EigenMesh3D& m_mesh; + const std::vector& m_support_pts; - PointSet filtered_points; // all valid support points - PointSet head_positions; // support points with pinhead - PointSet head_normals; // head normals - PointSet headless_positions; // headless support points - PointSet headless_normals; // headless support point normals + using PtIndices = std::vector; - using IndexSet = std::vector; + PtIndices m_iheads; // support points with pinhead + PtIndices m_iheadless; // headless support points - // Distances from head positions to ground or mesh touch points - std::vector head_heights; + // supp. pts. connecting to model: point index and the ray hit data + std::vector> m_iheads_onmodel; - // Indices of those who touch the ground - IndexSet ground_heads; + // normals for support points from model faces. + PointSet m_support_nmls; - // Indices of those who don't touch the ground - IndexSet noground_heads; - - // Groups of the 'ground_head' indices that belong into one cluster. These - // are candidates to be connected to one pillar. - ClusteredPoints ground_connectors; - - // A help function to translate ground head index to the actual coordinates. - auto gnd_head_pt = [&ground_heads, &head_positions] (size_t idx) { - return Vec3d(head_positions.row(ground_heads[idx])); - }; + // Clusters of points which can reach the ground directly and can be + // bridged to one central pillar + std::vector m_pillar_clusters; // This algorithm uses the Impl class as its output stream. It will be // filled gradually with support elements (heads, pillars, bridges, ...) using Result = SLASupportTree::Impl; - Result& result = *m_impl; - // Let's define the individual steps of the processing. We can experiment - // later with the ordering and the dependencies between them. - enum Steps { - BEGIN, - FILTER, - PINHEADS, - CLASSIFY, - ROUTING_GROUND, - ROUTING_NONGROUND, - HEADLESS, - DONE, - HALT, - ABORT, - NUM_STEPS - //... - }; + Result& m_result; + + // support points in Eigen/IGL format + PointSet m_points; // throw if canceled: It will be called many times so a shorthand will // come in handy. - auto& thr = ctl.cancelfn; + ThrowOnCancel m_thr; - // Filtering step: here we will discard inappropriate support points and - // decide the future of the appropriate ones. We will check if a pinhead - // is applicable and adjust its angle at each support point. - // We will also merge the support points that are just too close and can be - // considered as one. - auto filterfn = [thr] ( - const SupportConfig& cfg, - const PointSet& points, - const EigenMesh3D& mesh, - PointSet& filt_pts, - PointSet& head_norm, - PointSet& head_pos, - PointSet& headless_pos, - PointSet& headless_norm) + // A spatial index to easily find strong pillars to connect to. + SpatIndex m_pillar_index; + + inline double ray_mesh_intersect(const Vec3d& s, + const Vec3d& dir) { - // Get the points that are too close to each other and keep only the - // first one - auto aliases = - cluster(points, - [thr](const SpatElement& p, const SpatElement& se) - { - thr(); - return distance(p.first, se.first) < D_SP; - }, 2); + return m_mesh.query_ray_hit(s, dir).distance(); + } - filt_pts.resize(Eigen::Index(aliases.size()), 3); - int count = 0; - for(auto& a : aliases) { - // Here we keep only the front point of the cluster. - filt_pts.row(count++) = points.row(a.front()); + // This function will test if a future pinhead would not collide with the + // model geometry. It does not take a 'Head' object because those are + // created after this test. Parameters: s: The touching point on the model + // surface. dir: This is the direction of the head from the pin to the back + // r_pin, r_back: the radiuses of the pin and the back sphere width: This + // is the full width from the pin center to the back center m: The object + // mesh. + // The return value is the hit result from the ray casting. If the starting + // point was inside the model, an "invalid" hit_result will be returned + // with a zero distance value instead of a NAN. This way the result can + // be used safely for comparison with other distances. + EigenMesh3D::hit_result pinhead_mesh_intersect( + const Vec3d& s, + const Vec3d& dir, + double r_pin, + double r_back, + double width) + { + static const size_t SAMPLES = 8; + + // method based on: + // https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space + + // We will shoot multiple rays from the head pinpoint in the direction + // of the pinhead robe (side) surface. The result will be the smallest + // hit distance. + + // Move away slightly from the touching point to avoid raycasting on the + // inner surface of the mesh. + Vec3d v = dir; // Our direction (axis) + Vec3d c = s + width * dir; + const double& sd = m_cfg.safety_distance_mm; + + // Two vectors that will be perpendicular to each other and to the + // axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a + // placeholder. + Vec3d a(0, 1, 0), b; + + // The portions of the circle (the head-back circle) for which we will + // shoot rays. + std::array phis; + for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); + + auto& m = m_mesh; + using HitResult = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + // We have to address the case when the direction vector v (same as + // dir) is coincident with one of the world axes. In this case two of + // its components will be completely zero and one is 1.0. Our method + // becomes dangerous here due to division with zero. Instead, vector + // 'a' can be an element-wise rotated version of 'v' + auto chk1 = [] (double val) { + return std::abs(std::abs(val) - 1) < 1e-20; + }; + + if(chk1(v(X)) || chk1(v(Y)) || chk1(v(Z))) { + a = {v(Z), v(X), v(Y)}; + b = {v(Y), v(Z), v(X)}; + } + else { + a(Z) = -(v(Y)*a(Y)) / v(Z); a.normalize(); + b = a.cross(v); } - thr(); + // Now a and b vectors are perpendicular to v and to each other. + // Together they define the plane where we have to iterate with the + // given angles in the 'phis' vector + tbb::parallel_for(size_t(0), phis.size(), + [&phis, &hits, &m, sd, r_pin, r_back, s, a, b, c] + (size_t i) + { + double& phi = phis[i]; + double sinphi = std::sin(phi); + double cosphi = std::cos(phi); - // calculate the normals to the triangles belonging to filtered points - auto nmls = sla::normals(filt_pts, mesh, cfg.head_front_radius_mm, thr); + // Let's have a safety coefficient for the radiuses. + double rpscos = (sd + r_pin) * cosphi; + double rpssin = (sd + r_pin) * sinphi; + double rpbcos = (sd + r_back) * cosphi; + double rpbsin = (sd + r_back) * sinphi; - head_norm.resize(count, 3); - head_pos.resize(count, 3); - headless_pos.resize(count, 3); - headless_norm.resize(count, 3); + // Point on the circle on the pin sphere + Vec3d ps(s(X) + rpscos * a(X) + rpssin * b(X), + s(Y) + rpscos * a(Y) + rpssin * b(Y), + s(Z) + rpscos * a(Z) + rpssin * b(Z)); + + // Point ps is not on mesh but can be inside or outside as well. + // This would cause many problems with ray-casting. To detect the + // position we will use the ray-casting result (which has an + // is_inside predicate). + + // This is the point on the circle on the back sphere + Vec3d p(c(X) + rpbcos * a(X) + rpbsin * b(X), + c(Y) + rpbcos * a(Y) + rpbsin * b(Y), + c(Z) + rpbcos * a(Z) + rpbsin * b(Z)); + + Vec3d n = (p - ps).normalized(); + auto q = m.query_ray_hit(ps + sd*n, n); + + if(q.is_inside()) { // the hit is inside the model + if(q.distance() > r_pin + sd) { + // If we are inside the model and the hit distance is bigger + // than our pin circle diameter, it probably indicates that + // the support point was already inside the model, or there + // is really no space around the point. We will assign a + // zero hit distance to these cases which will enforce the + // function return value to be an invalid ray with zero hit + // distance. (see min_element at the end) + hits[i] = HitResult(0.0); + } + else { + // re-cast the ray from the outside of the object. + // The starting point has an offset of 2*safety_distance + // because the original ray has also had an offset + auto q2 = m.query_ray_hit(ps + (q.distance() + 2*sd)*n, n); + hits[i] = q2; + } + } else hits[i] = q; + }); + + auto mit = std::min_element(hits.begin(), hits.end()); + + return *mit; + } + + // Checking bridge (pillar and stick as well) intersection with the model. + // If the function is used for headless sticks, the ins_check parameter + // have to be true as the beginning of the stick might be inside the model + // geometry. + // The return value is the hit result from the ray casting. If the starting + // point was inside the model, an "invalid" hit_result will be returned + // with a zero distance value instead of a NAN. This way the result can + // be used safely for comparison with other distances. + EigenMesh3D::hit_result bridge_mesh_intersect( + const Vec3d& s, + const Vec3d& dir, + double r, + bool ins_check = false) + { + static const size_t SAMPLES = 8; + + // helper vector calculations + Vec3d a(0, 1, 0), b; + const double& sd = m_cfg.safety_distance_mm; + + // INFO: for explanation of the method used here, see the previous + // method's comments. + + auto chk1 = [] (double val) { + return std::abs(std::abs(val) - 1) < 1e-20; + }; + + if(chk1(dir(X)) || chk1(dir(Y)) || chk1(dir(Z))) { + a = {dir(Z), dir(X), dir(Y)}; + b = {dir(Y), dir(Z), dir(X)}; + } + else { + a(Z) = -(dir(Y)*a(Y)) / dir(Z); a.normalize(); + b = a.cross(dir); + } + + // circle portions + std::array phis; + for(size_t i = 0; i < phis.size(); ++i) phis[i] = i*2*PI/phis.size(); + + auto& m = m_mesh; + using HitResult = EigenMesh3D::hit_result; + + // Hit results + std::array hits; + + tbb::parallel_for(size_t(0), phis.size(), + [&m, &phis, a, b, sd, dir, r, s, ins_check, &hits] + (size_t i) + { + double& phi = phis[i]; + double sinphi = std::sin(phi); + double cosphi = std::cos(phi); + + // Let's have a safety coefficient for the radiuses. + double rcos = (sd + r) * cosphi; + double rsin = (sd + r) * sinphi; + + // Point on the circle on the pin sphere + Vec3d p (s(X) + rcos * a(X) + rsin * b(X), + s(Y) + rcos * a(Y) + rsin * b(Y), + s(Z) + rcos * a(Z) + rsin * b(Z)); + + auto hr = m.query_ray_hit(p + sd*dir, dir); + + if(ins_check && hr.is_inside()) { + if(hr.distance() > r + sd) hits[i] = HitResult(0.0); + else { + // re-cast the ray from the outside of the object + auto hr2 = + m.query_ray_hit(p + (hr.distance() + 2*sd)*dir, dir); + + hits[i] = hr2; + } + } else hits[i] = hr; + }); + + auto mit = std::min_element(hits.begin(), hits.end()); + + return *mit; + } + + // Helper function for interconnecting two pillars with zig-zag bridges. + bool interconnect(const Pillar& pillar, const Pillar& nextpillar) + { + // We need to get the starting point of the zig-zag pattern. We have to + // be aware that the two head junctions are at different heights. We + // may start from the lowest junction and call it a day but this + // strategy would leave unconnected a lot of pillar duos where the + // shorter pillar is too short to start a new bridge but the taller + // pillar could still be bridged with the shorter one. + bool was_connected = false; + + Vec3d supper = pillar.startpoint(); + Vec3d slower = nextpillar.startpoint(); + Vec3d eupper = pillar.endpoint(); + Vec3d elower = nextpillar.endpoint(); + + double zmin = m_result.ground_level + m_cfg.base_height_mm; + eupper(Z) = std::max(eupper(Z), zmin); + elower(Z) = std::max(elower(Z), zmin); + + // The usable length of both pillars should be positive + if(slower(Z) - elower(Z) < 0) return false; + if(supper(Z) - eupper(Z) < 0) return false; + + double pillar_dist = distance(Vec2d{slower(X), slower(Y)}, + Vec2d{supper(X), supper(Y)}); + double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope); + double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope); + + if(pillar_dist < 2 * m_cfg.head_back_radius_mm || + pillar_dist > m_cfg.max_pillar_link_distance_mm) return false; + + if(supper(Z) < slower(Z)) supper.swap(slower); + if(eupper(Z) < elower(Z)) eupper.swap(elower); + + double startz = 0, endz = 0; + + startz = slower(Z) - zstep < supper(Z) ? slower(Z) - zstep : slower(Z); + endz = eupper(Z) + zstep > elower(Z) ? eupper(Z) + zstep : eupper(Z); + + if(slower(Z) - eupper(Z) < std::abs(zstep)) { + // no space for even one cross + + // Get max available space + startz = std::min(supper(Z), slower(Z) - zstep); + endz = std::max(eupper(Z) + zstep, elower(Z)); + + // Align to center + double available_dist = (startz - endz); + double rounds = std::floor(available_dist / std::abs(zstep)); + startz -= 0.5 * (available_dist - rounds * std::abs(zstep));; + } + + auto pcm = m_cfg.pillar_connection_mode; + bool docrosses = + pcm == PillarConnectionMode::cross || + (pcm == PillarConnectionMode::dynamic && + pillar_dist > 2*m_cfg.base_radius_mm); + + // 'sj' means starting junction, 'ej' is the end junction of a bridge. + // They will be swapped in every iteration thus the zig-zag pattern. + // According to a config parameter, a second bridge may be added which + // results in a cross connection between the pillars. + Vec3d sj = supper, ej = slower; sj(Z) = startz; ej(Z) = sj(Z) + zstep; + + // TODO: This is a workaround to not have a faulty last bridge + while(ej(Z) >= eupper(Z) /*endz*/) { + if(bridge_mesh_intersect(sj, + dirv(sj, ej), + pillar.r) >= bridge_distance) + { + m_result.add_bridge(sj, ej, pillar.r); + was_connected = true; + } + + // double bridging: (crosses) + if(docrosses) { + Vec3d sjback(ej(X), ej(Y), sj(Z)); + Vec3d ejback(sj(X), sj(Y), ej(Z)); + if(sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) && + bridge_mesh_intersect(sjback, + dirv(sjback, ejback), + pillar.r) >= bridge_distance) + { + // need to check collision for the cross stick + m_result.add_bridge(sjback, ejback, pillar.r); + was_connected = true; + } + } + + sj.swap(ej); + ej(Z) = sj(Z) + zstep; + } + + return was_connected; + } + + // For connecting a head to a nearby pillar. + bool connect_to_nearpillar(const Head& head, long nearpillar_id) { + + auto nearpillar = [this, nearpillar_id]() { return m_result.pillar(nearpillar_id); }; + if(nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; + + Vec3d headjp = head.junction_point(); + Vec3d nearjp_u = nearpillar().startpoint(); + Vec3d nearjp_l = nearpillar().endpoint(); + + double r = head.r_back_mm; + double d2d = distance(to_2d(headjp), to_2d(nearjp_u)); + double d3d = distance(headjp, nearjp_u); + + double hdiff = nearjp_u(Z) - headjp(Z); + double slope = std::atan2(hdiff, d2d); + + Vec3d bridgestart = headjp; + Vec3d bridgeend = nearjp_u; + double max_len = m_cfg.max_bridge_length_mm; + double max_slope = m_cfg.bridge_slope; + double zdiff = 0.0; + + // check the default situation if feasible for a bridge + if(d3d > max_len || slope > -max_slope) { + // not feasible to connect the two head junctions. We have to search + // for a suitable touch point. + + double Zdown = headjp(Z) + d2d * std::tan(-max_slope); + Vec3d touchjp = bridgeend; touchjp(Z) = Zdown; + double D = distance(headjp, touchjp); + zdiff = Zdown - nearjp_u(Z); + + if(zdiff > 0) { + Zdown -= zdiff; + bridgestart(Z) -= zdiff; + touchjp(Z) = Zdown; + + double t = bridge_mesh_intersect(headjp, {0,0,-1}, r); + + // We can't insert a pillar under the source head to connect + // with the nearby pillar's starting junction + if(t < zdiff) return false; + } + + if(Zdown <= nearjp_u(Z) && Zdown >= nearjp_l(Z) && D < max_len) + bridgeend(Z) = Zdown; + else + return false; + } + + // There will be a minimum distance from the ground where the + // bridge is allowed to connect. This is an empiric value. + double minz = m_result.ground_level + 2 * m_cfg.head_width_mm; + if(bridgeend(Z) < minz) return false; + + double t = bridge_mesh_intersect(bridgestart, + dirv(bridgestart, bridgeend), r); + + // Cannot insert the bridge. (further search might not worth the hassle) + if(t < distance(bridgestart, bridgeend)) return false; + + // A partial pillar is needed under the starting head. + if(zdiff > 0) { + m_result.add_pillar(unsigned(head.id), bridgestart, r); + m_result.add_junction(bridgestart, r); + } + + m_result.add_bridge(bridgestart, bridgeend, r); + m_result.increment_bridges(nearpillar()); + + return true; + } + + bool search_pillar_and_connect(const Head& head) { + SpatIndex spindex = m_pillar_index; + + long nearest_id = -1; + + Vec3d querypoint = head.junction_point(); + + while(nearest_id < 0 && !spindex.empty()) { m_thr(); + // loop until a suitable head is not found + // if there is a pillar closer than the cluster center + // (this may happen as the clustering is not perfect) + // than we will bridge to this closer pillar + + Vec3d qp(querypoint(X), querypoint(Y), m_result.ground_level); + auto qres = spindex.nearest(qp, 1); + if(qres.empty()) break; + + auto ne = qres.front(); + nearest_id = ne.second; + + if(nearest_id >= 0) { + auto nearpillarID = unsigned(nearest_id); + if(nearpillarID < m_result.pillars().size()) { + if(!connect_to_nearpillar(head, nearpillarID)) { + nearest_id = -1; // continue searching + spindex.remove(ne); // without the current pillar + } + } + } + } + + return nearest_id >= 0; + } + +public: + + Algorithm(const SupportConfig& config, + const EigenMesh3D& emesh, + const std::vector& support_pts, + Result& result, + ThrowOnCancel thr) : + m_cfg(config), + m_mesh(emesh), + m_support_pts(support_pts), + m_support_nmls(support_pts.size(), 3), + m_result(result), + m_points(support_pts.size(), 3), + m_thr(thr) + { + // Prepare the support points in Eigen/IGL format as well, we will use + // it mostly in this form. + + long i = 0; + for(const SupportPoint& sp : m_support_pts) { + m_points.row(i)(X) = double(sp.pos(X)); + m_points.row(i)(Y) = double(sp.pos(Y)); + m_points.row(i)(Z) = double(sp.pos(Z)); + ++i; + } + } + + + // Now let's define the individual steps of the support generation algorithm + + // Filtering step: here we will discard inappropriate support points + // and decide the future of the appropriate ones. We will check if a + // pinhead is applicable and adjust its angle at each support point. We + // will also merge the support points that are just too close and can + // be considered as one. + void filter() { + // Get the points that are too close to each other and keep only the + // first one + auto aliases = cluster(m_points, D_SP, 2); + + PtIndices filtered_indices; + filtered_indices.reserve(aliases.size()); + m_iheads.reserve(aliases.size()); + m_iheadless.reserve(aliases.size()); + for(auto& a : aliases) { + // Here we keep only the front point of the cluster. + filtered_indices.emplace_back(a.front()); + } + + // calculate the normals to the triangles for filtered points + auto nmls = sla::normals(m_points, m_mesh, m_cfg.head_front_radius_mm, + m_thr, filtered_indices); // Not all of the support points have to be a valid position for // support creation. The angle may be inappropriate or there may @@ -1227,13 +1416,14 @@ bool SLASupportTree::generate(const PointSet &points, using libnest2d::opt::bound; using libnest2d::opt::initvals; - using libnest2d::opt::SimplexOptimizer; + using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::StopCriteria; - static const unsigned MAX_TRIES = 100; - int pcount = 0, hlcount = 0; - for(int i = 0; i < count; i++) { - thr(); + for(unsigned i = 0, fidx = 0; i < filtered_indices.size(); ++i) + { + m_thr(); + + fidx = filtered_indices[i]; auto n = nmls.row(i); // for all normals we generate the spherical coordinates and @@ -1249,17 +1439,19 @@ bool SLASupportTree::generate(const PointSet &points, double azimuth = std::atan2(n(1), n(0)); // skip if the tilt is not sane - if(polar >= PI - cfg.normal_cutoff_angle) { + if(polar >= PI - m_cfg.normal_cutoff_angle) { // We saturate the polar angle to 3pi/4 polar = std::max(polar, 3*PI / 4); // save the head (pinpoint) position - Vec3d hp = filt_pts.row(i); + Vec3d hp = m_points.row(fidx); - double w = cfg.head_width_mm + - cfg.head_back_radius_mm + - 2*cfg.head_front_radius_mm; + double w = m_cfg.head_width_mm + + m_cfg.head_back_radius_mm + + 2*m_cfg.head_front_radius_mm; + + double pin_r = double(m_support_pts[fidx].head_front_radius); // Reassemble the now corrected normal auto nn = Vec3d(std::cos(azimuth) * std::sin(polar), @@ -1270,352 +1462,167 @@ bool SLASupportTree::generate(const PointSet &points, double t = pinhead_mesh_intersect( hp, // touching point nn, // normal - cfg.head_front_radius_mm, - cfg.head_back_radius_mm, - w, - mesh); + pin_r, + m_cfg.head_back_radius_mm, + w); if(t <= w) { - // Let's try to optimize this angle, there might be a viable - // normal that doesn't collide with the model geometry and - // its very close to the default. + + // Let's try to optimize this angle, there might be a + // viable normal that doesn't collide with the model + // geometry and its very close to the default. StopCriteria stc; - stc.max_iterations = MAX_TRIES; - stc.relative_score_difference = 1e-3; + stc.max_iterations = m_cfg.optimizer_max_iterations; + stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; stc.stop_score = w; // space greater than w is enough - SimplexOptimizer solver(stc); + GeneticOptimizer solver(stc); + solver.seed(0); // we want deterministic behavior auto oresult = solver.optimize_max( - [&mesh, &cfg, w, hp](double plr, double azm) + [this, pin_r, w, hp](double plr, double azm) { auto n = Vec3d(std::cos(azm) * std::sin(plr), std::sin(azm) * std::sin(plr), std::cos(plr)).normalized(); - double score = pinhead_mesh_intersect( - hp, n, - cfg.head_front_radius_mm, - cfg.head_back_radius_mm, - w, - mesh); + double score = pinhead_mesh_intersect( hp, n, pin_r, + m_cfg.head_back_radius_mm, w); + return score; }, - initvals(polar, azimuth), // let's start with what we have + initvals(polar, azimuth), // start with what we have bound(3*PI/4, PI), // Must not exceed the tilt limit - bound(-PI, PI) // azimuth can be a full range search + bound(-PI, PI) // azimuth can be a full search ); - t = oresult.score; - polar = std::get<0>(oresult.optimum); - azimuth = std::get<1>(oresult.optimum); - nn = Vec3d(std::cos(azimuth) * std::sin(polar), - std::sin(azimuth) * std::sin(polar), - std::cos(polar)).normalized(); + if(oresult.score > w) { + polar = std::get<0>(oresult.optimum); + azimuth = std::get<1>(oresult.optimum); + nn = Vec3d(std::cos(azimuth) * std::sin(polar), + std::sin(azimuth) * std::sin(polar), + std::cos(polar)).normalized(); + t = oresult.score; + } } + // save the verified and corrected normal + m_support_nmls.row(fidx) = nn; + if(t > w) { - head_pos.row(pcount) = hp; - - // save the verified and corrected normal - head_norm.row(pcount) = nn; - - ++pcount; + // mark the point for needing a head. + m_iheads.emplace_back(fidx); } else if( polar >= 3*PI/4 ) { - // Headless supports do not tilt like the headed ones so // the normal should point almost to the ground. - headless_norm.row(hlcount) = nn; - headless_pos.row(hlcount++) = hp; + m_iheadless.emplace_back(fidx); } } } - head_pos.conservativeResize(pcount, Eigen::NoChange); - head_norm.conservativeResize(pcount, Eigen::NoChange); - headless_pos.conservativeResize(hlcount, Eigen::NoChange); - headless_norm.conservativeResize(hlcount, Eigen::NoChange); - }; + m_thr(); + } - // Pinhead creation: based on the filtering results, the Head objects will - // be constructed (together with their triangle meshes). - auto pinheadfn = [thr] ( - const SupportConfig& cfg, - PointSet& head_pos, - PointSet& nmls, - Result& result - ) + // Pinhead creation: based on the filtering results, the Head objects + // will be constructed (together with their triangle meshes). + void add_pinheads() { - - /* ******************************************************** */ - /* Generating Pinheads */ - /* ******************************************************** */ - - for (int i = 0; i < head_pos.rows(); ++i) { - thr(); - result.add_head( - cfg.head_back_radius_mm, - cfg.head_front_radius_mm, - cfg.head_width_mm, - cfg.head_penetration_mm, - nmls.row(i), // dir - head_pos.row(i) // displacement + for (unsigned i : m_iheads) { + m_thr(); + m_result.add_head( + i, + m_cfg.head_back_radius_mm, + m_support_pts[i].head_front_radius, + m_cfg.head_width_mm, + m_cfg.head_penetration_mm, + m_support_nmls.row(i), // dir + m_support_pts[i].pos.cast() // displacement ); } - }; + } // Further classification of the support points with pinheads. If the - // ground is directly reachable through a vertical line parallel to the Z - // axis we consider a support point as pillar candidate. If touches the - // model geometry, it will be marked as non-ground facing and further steps - // will process it. Also, the pillars will be grouped into clusters that can - // be interconnected with bridges. Elements of these groups may or may not - // be interconnected. Here we only run the clustering algorithm. - auto classifyfn = [thr] ( - const SupportConfig& cfg, - const EigenMesh3D& mesh, - PointSet& head_pos, - IndexSet& gndidx, - IndexSet& nogndidx, - std::vector& gndheight, - ClusteredPoints& ground_clusters, - Result& result - ) + // ground is directly reachable through a vertical line parallel to the + // Z axis we consider a support point as pillar candidate. If touches + // the model geometry, it will be marked as non-ground facing and + // further steps will process it. Also, the pillars will be grouped + // into clusters that can be interconnected with bridges. Elements of + // these groups may or may not be interconnected. Here we only run the + // clustering algorithm. + void classify() { - - /* ******************************************************** */ - /* Classification */ - /* ******************************************************** */ - // We should first get the heads that reach the ground directly - gndheight.reserve(size_t(head_pos.rows())); - gndidx.reserve(size_t(head_pos.rows())); - nogndidx.reserve(size_t(head_pos.rows())); + PtIndices ground_head_indices; + ground_head_indices.reserve(m_iheads.size()); + m_iheads_onmodel.reserve(m_iheads.size()); // First we decide which heads reach the ground and can be full - // pillars and which shall be connected to the model surface (or search - // a suitable path around the surface that leads to the ground -- TODO) - for(unsigned i = 0; i < head_pos.rows(); i++) { - thr(); - auto& head = result.head(i); + // pillars and which shall be connected to the model surface (or + // search a suitable path around the surface that leads to the + // ground -- TODO) + for(unsigned i : m_iheads) { + m_thr(); - Vec3d dir(0, 0, -1); - bool accept = false; - int ri = 1; - double t = std::numeric_limits::infinity(); - double hw = head.width_mm; + auto& head = m_result.head(i); + Vec3d n(0, 0, -1); + double r = head.r_back_mm; + Vec3d headjp = head.junction_point(); + // collision check + auto hit = bridge_mesh_intersect(headjp, n, r); - { -// using libnest2d::opt::Method; -// using libnest2d::opt::bound; -// using libnest2d::opt::Optimizer; -// using libnest2d::opt::TOptimizer; -// using libnest2d::opt::StopCriteria; - -// auto stopcond = [] () { return false; }; -// static const unsigned max_tries = 100; - -// auto objfunc = -// [&head](double polar, double azimuth, double width) -// { -// Vec3d nn(std::cos(azimuth) * std::sin(polar), -// std::sin(azimuth) * std::sin(polar), -// std::cos(polar)); - - -// }; - -// StopCriteria stc; -// stc.max_iterations = max_tries; -// stc.relative_score_difference = 1e-3; -// stc.stop_condition = stopcond; -// TOptimizer solver(stc); - } - - - // We will try to assign a pillar to all the pinheads. If a pillar - // would pierce the model surface, we will try to adjust slightly - // the head width so that the pillar can be deployed. - while(!accept && head.width_mm > 0) { - - Vec3d startpoint = head.junction_point(); - - // Collision detection - t = bridge_mesh_intersect(startpoint, dir, head.r_back_mm, mesh); - - // Precise distance measurement - double tprec = ray_mesh_intersect(startpoint, dir, mesh); - - if(std::isinf(tprec) && !std::isinf(t)) { - // This is a damned case where the pillar melts into the - // model but its center ray can reach the ground. We can - // not route this to the ground nor to the model surface. - head.width_mm = hw + (ri % 2? -1 : 1) * ri * head.r_back_mm; - } else { - if(!std::isinf(t) && !std::isinf(tprec) && - std::abs(tprec - t) > hw) - { - // In this case the head would scratch the model body - BOOST_LOG_TRIVIAL(warning) << "Head scratch detected."; - } - - accept = true; t = tprec; - - auto id = head.id; - // We need to regenerate the head geometry - head = Head(head.r_back_mm, - head.r_pin_mm, - head.width_mm, - head.penetration_mm, - head.dir, - head.tr); - head.id = id; - } - - ri++; - } - - // Save the distance from a surface in the Z axis downwards. It may - // be infinity but that is telling us that it touches the ground. - gndheight.emplace_back(t); - - if(accept) { - if(std::isinf(t)) gndidx.emplace_back(i); - else nogndidx.emplace_back(i); - } else { - // This is a serious issue. There was no way to deploy a pillar - // for the given pinhead. The whole thing has to be discarded - // leaving the model potentially unprintable. - // - // TODO: In the future this has to be solved by searching for - // a path in 3D space from this support point to a suitable - // pillar position or an existing pillar. - // As a workaround we could mark this head as "sidehead only" - // let it go trough the nearby pillar search in the next step. - BOOST_LOG_TRIVIAL(warning) << "A support point at " - << head.tr.transpose() - << " had to be discarded as there is" - << " nowhere to route it."; - head.invalidate(); + if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i); + else { + if(m_cfg.ground_facing_only) head.invalidate(); + m_iheads_onmodel.emplace_back(std::make_pair(i, hit)); } } - // Transform the ground facing point indices top actual coordinates. - PointSet gnd(gndidx.size(), 3); - for(size_t i = 0; i < gndidx.size(); i++) - gnd.row(long(i)) = head_pos.row(gndidx[i]); + // We want to search for clusters of points that are far enough + // from each other in the XY plane to not cross their pillar bases + // These clusters of support points will join in one pillar, + // possibly in their centroid support point. + auto pointfn = [this](unsigned i) { + return m_result.head(i).junction_point(); + }; + auto predicate = [this](const SpatElement& e1, const SpatElement& e2) { + double d2d = distance(to_2d(e1.first), to_2d(e2.first)); + double d3d = distance(e1.first, e2.first); + return d2d < 2 * m_cfg.base_radius_mm && + d3d < m_cfg.max_bridge_length_mm; + }; + m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate, + m_cfg.max_bridges_on_pillar); + } - // We want to search for clusters of points that are far enough from - // each other in the XY plane to not cross their pillar bases - // These clusters of support points will join in one pillar, possibly in - // their centroid support point. - auto d_base = 2*cfg.base_radius_mm; - ground_clusters = - cluster( - gnd, - [d_base, thr](const SpatElement& p, const SpatElement& s) - { - thr(); - return distance(Vec2d(p.first(X), p.first(Y)), - Vec2d(s.first(X), s.first(Y))) < d_base; - }, 3); // max 3 heads to connect to one centroid - }; - - // Helper function for interconnecting two pillars with zig-zag bridges. - // This is not an individual step. - auto interconnect = [&cfg]( - const Pillar& pillar, - const Pillar& nextpillar, - const EigenMesh3D& emesh, - Result& result) + // Step: Routing the ground connected pinheads, and interconnecting + // them with additional (angled) bridges. Not all of these pinheads + // will be a full pillar (ground connected). Some will connect to a + // nearby pillar using a bridge. The max number of such side-heads for + // a central pillar is limited to avoid bad weight distribution. + void routing_to_ground() { - const Head& phead = result.pillar_head(pillar.id); - const Head& nextphead = result.pillar_head(nextpillar.id); - - Vec3d sj = phead.junction_point(); - sj(Z) = std::min(sj(Z), nextphead.junction_point()(Z)); - Vec3d ej = nextpillar.endpoint; - double pillar_dist = distance(Vec2d{sj(X), sj(Y)}, - Vec2d{ej(X), ej(Y)}); - double zstep = pillar_dist * std::tan(-cfg.tilt); - ej(Z) = sj(Z) + zstep; - - double chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, emesh); - double bridge_distance = pillar_dist / std::cos(-cfg.tilt); - - // If the pillars are so close that they touch each other, - // there is no need to bridge them together. - if(pillar_dist > 2*cfg.head_back_radius_mm && - bridge_distance < cfg.max_bridge_length_mm) - while(sj(Z) > pillar.endpoint(Z) + cfg.base_radius_mm && - ej(Z) > nextpillar.endpoint(Z) + cfg.base_radius_mm) - { - if(chkd >= bridge_distance) { - result.add_bridge(sj, ej, pillar.r); - - auto pcm = cfg.pillar_connection_mode; - - // double bridging: (crosses) - if( pcm == PillarConnectionMode::cross || - (pcm == PillarConnectionMode::dynamic && - pillar_dist > 2*cfg.base_radius_mm)) - { - // If the columns are close together, no need to - // double bridge them - Vec3d bsj(ej(X), ej(Y), sj(Z)); - Vec3d bej(sj(X), sj(Y), ej(Z)); - - // need to check collision for the cross stick - double backchkd = bridge_mesh_intersect(bsj, - dirv(bsj, bej), - pillar.r, - emesh); - - - if(backchkd >= bridge_distance) { - result.add_bridge(bsj, bej, pillar.r); - } - } - } - sj.swap(ej); - ej(Z) = sj(Z) + zstep; - chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r, emesh); - } - }; - - // Step: Routing the ground connected pinheads, and interconnecting them - // with additional (angled) bridges. Not all of these pinheads will be - // a full pillar (ground connected). Some will connect to a nearby pillar - // using a bridge. The max number of such side-heads for a central pillar - // is limited to avoid bad weight distribution. - auto routing_ground_fn = [gnd_head_pt, interconnect, thr]( - const SupportConfig& cfg, - const ClusteredPoints& gnd_clusters, - const IndexSet& gndidx, - const EigenMesh3D& emesh, - Result& result) - { - const double hbr = cfg.head_back_radius_mm; - const double pradius = cfg.head_back_radius_mm; - const double maxbridgelen = cfg.max_bridge_length_mm; - const double gndlvl = result.ground_level; + const double pradius = m_cfg.head_back_radius_mm; + const double gndlvl = m_result.ground_level; ClusterEl cl_centroids; - cl_centroids.reserve(gnd_clusters.size()); + cl_centroids.reserve(m_pillar_clusters.size()); + + for(auto& cl : m_pillar_clusters) { m_thr(); + // place all the centroid head positions into the index. We + // will query for alternative pillar positions. If a sidehead + // cannot connect to the cluster centroid, we have to search + // for another head with a full pillar. Also when there are two + // elements in the cluster, the centroid is arbitrary and the + // sidehead is allowed to connect to a nearby pillar to + // increase structural stability. - SpatIndex pheadindex; // spatial index for the junctions - for(auto& cl : gnd_clusters) { thr(); - // place all the centroid head positions into the index. We will - // query for alternative pillar positions. If a sidehead cannot - // connect to the cluster centroid, we have to search for another - // head with a full pillar. Also when there are two elements in the - // cluster, the centroid is arbitrary and the sidehead is allowed to - // connect to a nearby pillar to increase structural stability. if(cl.empty()) continue; // get the current cluster centroid - long lcid = cluster_centroid(cl, gnd_head_pt, + auto& thr = m_thr; const auto& points = m_points; + long lcid = cluster_centroid(cl, + [&points](size_t idx) { return points.row(long(idx)); }, [thr](const Vec3d& p1, const Vec3d& p2) { thr(); @@ -1623,313 +1630,439 @@ bool SLASupportTree::generate(const PointSet &points, }); assert(lcid >= 0); - auto cid = unsigned(lcid); + unsigned hid = cl[size_t(lcid)]; // Head ID - cl_centroids.push_back(unsigned(cid)); + cl_centroids.emplace_back(hid); - unsigned hid = gndidx[cl[cid]]; // Head index - Head& h = result.head(hid); + Head& h = m_result.head(hid); h.transform(); Vec3d p = h.junction_point(); p(Z) = gndlvl; + auto& plr = m_result.add_pillar(hid, p, h.r_back_mm) + .add_base(m_cfg.base_height_mm, + m_cfg.base_radius_mm); - pheadindex.insert(p, hid); + // Save the pillar endpoint and the pillar id in the spatial index + m_pillar_index.insert(plr.endpoint(), unsigned(plr.id)); } // now we will go through the clusters ones again and connect the // sidepoints with the cluster centroid (which is a ground pillar) // or a nearby pillar if the centroid is unreachable. size_t ci = 0; - for(auto cl : gnd_clusters) { thr(); + for(auto cl : m_pillar_clusters) { m_thr(); - auto cidx = cl_centroids[ci]; - cl_centroids[ci++] = cl[cidx]; + auto cidx = cl_centroids[ci++]; - size_t index_to_heads = gndidx[cl[cidx]]; - auto& head = result.head(index_to_heads); + // TODO: don't consider the cluster centroid but calculate a + // central position where the pillar can be placed. this way + // the weight is distributed more effectively on the pillar. - Vec3d startpoint = head.junction_point(); - auto endpoint = startpoint; endpoint(Z) = gndlvl; + auto centerpillarID = m_result.head_pillar(cidx).id; - // Create the central pillar of the cluster with its base on the - // ground - result.add_pillar(long(index_to_heads), endpoint, pradius) - .add_base(cfg.base_height_mm, cfg.base_radius_mm); + for(auto c : cl) { m_thr(); + if(c == cidx) continue; - // Process side point in current cluster - cl.erase(cl.begin() + cidx); // delete the centroid before looping - - // TODO: dont consider the cluster centroid but calculate a central - // position where the pillar can be placed. this way the weight - // is distributed more effectively on the pillar. - - auto search_nearest = - [&thr, &cfg, &result, &emesh, maxbridgelen, gndlvl, pradius] - (SpatIndex& spindex, const Vec3d& jsh) - { - long nearest_id = -1; - const double max_len = maxbridgelen / 2; - while(nearest_id < 0 && !spindex.empty()) { thr(); - // loop until a suitable head is not found - // if there is a pillar closer than the cluster center - // (this may happen as the clustering is not perfect) - // than we will bridge to this closer pillar - - Vec3d qp(jsh(X), jsh(Y), gndlvl); - auto ne = spindex.nearest(qp, 1).front(); - const Head& nearhead = result.heads()[ne.second]; - - Vec3d jh = nearhead.junction_point(); - Vec3d jp = jsh; - double dist2d = distance(qp, ne.first); - - // Bridge endpoint on the main pillar - Vec3d jn(jh(X), jh(Y), jp(Z) + dist2d*std::tan(-cfg.tilt)); - - if(jn(Z) > jh(Z)) { - // If the sidepoint cannot connect to the pillar from - // its head junction, then just skip this pillar. - spindex.remove(ne); - continue; - } - - double d = distance(jp, jn); - - if(jn(Z) <= gndlvl + 2*cfg.head_width_mm || d > max_len) - break; - - double chkd = bridge_mesh_intersect(jp, dirv(jp, jn), - pradius, - emesh); - if(chkd >= d) nearest_id = ne.second; - - spindex.remove(ne); - } - return nearest_id; - }; - - for(auto c : cl) { thr(); - auto& sidehead = result.head(gndidx[c]); + auto& sidehead = m_result.head(c); sidehead.transform(); - Vec3d jsh = sidehead.junction_point(); - SpatIndex spindex = pheadindex; - long nearest_id = search_nearest(spindex, jsh); - - // at this point we either have our pillar index or we have - // to connect the sidehead to the ground - if(nearest_id < 0) { + if(!connect_to_nearpillar(sidehead, centerpillarID) && + !search_pillar_and_connect(sidehead)) + { + Vec3d pstart = sidehead.junction_point(); + Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; // Could not find a pillar, create one - Vec3d jp = jsh; jp(Z) = gndlvl; - result.add_pillar(sidehead.id, jp, pradius). - add_base(cfg.base_height_mm, cfg.base_radius_mm); + auto& pillar = m_result.add_pillar(unsigned(sidehead.id), + pend, pradius) + .add_base(m_cfg.base_height_mm, + m_cfg.base_radius_mm); // connects to ground, eligible for bridging - cl_centroids.emplace_back(c); - } else { - // Creating the bridge to the nearest pillar - - const Head& nearhead = result.heads()[size_t(nearest_id)]; - Vec3d jp = jsh; - Vec3d jh = nearhead.junction_point(); - - double d = distance(Vec2d{jp(X), jp(Y)}, - Vec2d{jh(X), jh(Y)}); - Vec3d jn(jh(X), jh(Y), jp(Z) + d*std::tan(-cfg.tilt)); - - if(jn(Z) > jh(Z)) { - double hdiff = jn(Z) - jh(Z); - jp(Z) -= hdiff; - jn(Z) -= hdiff; - - // pillar without base, this does not connect to ground. - result.add_pillar(sidehead.id, jp, pradius); - } - - if(jp(Z) < jsh(Z)) result.add_junction(jp, hbr); - if(jn(Z) >= jh(Z)) result.add_junction(jn, hbr); - double r_pillar = sidehead.request_pillar_radius(pradius); - result.add_bridge(jp, jn, r_pillar); + m_pillar_index.insert(pend, unsigned(pillar.id)); } } } - - // We will break down the pillar positions in 2D into concentric rings. - // Connecting the pillars belonging to the same ring will prevent - // bridges from crossing each other. After bridging the rings we can - // create bridges between the rings without the possibility of crossing - // bridges. Two pillars will be bridged with X shaped stick pairs. - // If they are really close to each other, than only one stick will be - // used in zig-zag mode. - - // Breaking down the points into rings will be done with a modified - // convex hull algorithm (see pts_convex_hull()), that works for - // collinear points as well. If the points are on the same surface, - // they can be part of an imaginary line segment for which the convex - // hull is not defined. I this case it is enough to sort the points - // spatially and create the bridge stick from the one endpoint to - // another. - - ClusterEl rem = cl_centroids; - ClusterEl ring; - - while(!rem.empty()) { // loop until all the points belong to some ring - thr(); - std::sort(rem.begin(), rem.end()); - - auto newring = pts_convex_hull(rem, - [gnd_head_pt](unsigned i) { - auto&& p = gnd_head_pt(i); - return Vec2d(p(X), p(Y)); // project to 2D in along Z axis - }); - - if(!ring.empty()) { - // inner ring is now in 'newring' and outer ring is in 'ring' - SpatIndex innerring; - for(unsigned i : newring) { thr(); - const Pillar& pill = result.head_pillar(gndidx[i]); - assert(pill.id >= 0); - innerring.insert(pill.endpoint, unsigned(pill.id)); - } - - // For all pillars in the outer ring find the closest in the - // inner ring and connect them. This will create the spider web - // fashioned connections between pillars - for(unsigned i : ring) { thr(); - const Pillar& outerpill = result.head_pillar(gndidx[i]); - auto res = innerring.nearest(outerpill.endpoint, 1); - if(res.empty()) continue; - - auto ne = res.front(); - const Pillar& innerpill = result.pillars()[ne.second]; - interconnect(outerpill, innerpill, emesh, result); - } - } - - // no need for newring anymore in the current iteration - ring.swap(newring); - - /*std::cout << "ring: \n"; - for(auto ri : ring) { - std::cout << ri << " " << " X = " << gnd_head_pt(ri)(X) - << " Y = " << gnd_head_pt(ri)(Y) << std::endl; - } - std::cout << std::endl;*/ - - // now the ring has to be connected with bridge sticks - for(auto it = ring.begin(), next = std::next(it); - next != ring.end(); - ++it, ++next) - { - thr(); - const Pillar& pillar = result.head_pillar(gndidx[*it]); - const Pillar& nextpillar = result.head_pillar(gndidx[*next]); - interconnect(pillar, nextpillar, emesh, result); - } - - auto sring = ring; ClusterEl tmp; - std::sort(sring.begin(), sring.end()); - std::set_difference(rem.begin(), rem.end(), - sring.begin(), sring.end(), - std::back_inserter(tmp)); - rem.swap(tmp); - } - }; + } // Step: routing the pinheads that would connect to the model surface // along the Z axis downwards. For now these will actually be connected with // the model surface with a flipped pinhead. In the future here we could use // some smart algorithms to search for a safe path to the ground or to a // nearby pillar that can hold the supported weight. - auto routing_nongnd_fn = [thr]( - const SupportConfig& cfg, - const std::vector& gndheight, - const IndexSet& nogndidx, - Result& result) + void routing_to_model() { - // TODO: connect these to the ground pillars if possible - for(auto idx : nogndidx) { thr(); - double gh = gndheight[idx]; - double base_width = cfg.head_width_mm; - auto& head = result.head(idx); + // We need to check if there is an easy way out to the bed surface. + // If it can be routed there with a bridge shorter than + // min_bridge_distance. - // In this case there is no room for the base pinhead. - if(gh < head.fullwidth()) { - double min_l = - 2 * cfg.head_front_radius_mm + - 2 * cfg.head_back_radius_mm - cfg.head_penetration_mm; - - base_width = gh - min_l; - } - - if(base_width < 0) { - // There is really no space for even a reduced size head. We - // have to replace that with a small half sphere that touches - // the model surface. (TODO) - head.invalidate(); - continue; - } + // First we want to index the available pillars. The best is to connect + // these points to the available pillars + auto routedown = [this](Head& head, const Vec3d& dir, double dist) + { head.transform(); + Vec3d hjp = head.junction_point(); + Vec3d endp = hjp + dist * dir; + m_result.add_bridge(hjp, endp, head.r_back_mm); + m_result.add_junction(endp, head.r_back_mm); - Vec3d headend = head.junction_point(); + auto groundp = endp; + groundp(Z) = m_result.ground_level; + auto& newpillar = m_result.add_pillar(endp, groundp, head.r_back_mm) + .add_base(m_cfg.base_height_mm, + m_cfg.base_radius_mm); + m_pillar_index.insert(groundp, unsigned(newpillar.id)); + }; - Head base_head(cfg.head_back_radius_mm, - cfg.head_front_radius_mm, - base_width, - cfg.head_penetration_mm, - {0.0, 0.0, 1.0}, - {headend(X), headend(Y), headend(Z) - gh}); + std::vector modelpillars; - base_head.transform(); + // TODO: connect these to the ground pillars if possible + for(auto item : m_iheads_onmodel) { m_thr(); + unsigned idx = item.first; + EigenMesh3D::hit_result hit = item.second; - // Robustness check: - if(headend(Z) < base_head.junction_point()(Z)) { - // This should not happen it is against all assumptions - BOOST_LOG_TRIVIAL(warning) - << "Ignoring invalid supports connecting to model body"; - head.invalidate(); + auto& head = m_result.head(idx); + Vec3d hjp = head.junction_point(); + + // ///////////////////////////////////////////////////////////////// + // Search nearby pillar + // ///////////////////////////////////////////////////////////////// + + if(search_pillar_and_connect(head)) { head.transform(); continue; } + + // ///////////////////////////////////////////////////////////////// + // Try straight path + // ///////////////////////////////////////////////////////////////// + + // Cannot connect to nearby pillar. We will try to search for + // a route to the ground. + + double t = bridge_mesh_intersect(hjp, head.dir, head.r_back_mm); + double d = 0, tdown = 0; + Vec3d dirdown(0.0, 0.0, -1.0); + + t = std::min(t, m_cfg.max_bridge_length_mm); + + while(d < t && !std::isinf(tdown = bridge_mesh_intersect( + hjp + d*head.dir, + dirdown, head.r_back_mm))) { + d += head.r_back_mm; + } + + if(std::isinf(tdown)) { // we heave found a route to the ground + routedown(head, head.dir, d); continue; + } + + // ///////////////////////////////////////////////////////////////// + // Optimize bridge direction + // ///////////////////////////////////////////////////////////////// + + // Straight path failed so we will try to search for a suitable + // direction out of the cavity. + + // Get the spherical representation of the normal. its easier to + // work with. + double z = head.dir(Z); + double r = 1.0; // for normalized vector + double polar = std::acos(z / r); + double azimuth = std::atan2(head.dir(Y), head.dir(X)); + + using libnest2d::opt::bound; + using libnest2d::opt::initvals; + using libnest2d::opt::GeneticOptimizer; + using libnest2d::opt::StopCriteria; + + StopCriteria stc; + stc.max_iterations = m_cfg.optimizer_max_iterations; + stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; + stc.stop_score = 1e6; + GeneticOptimizer solver(stc); + solver.seed(0); // we want deterministic behavior + + double r_back = head.r_back_mm; + + auto oresult = solver.optimize_max( + [this, hjp, r_back](double plr, double azm) + { + Vec3d n = Vec3d(std::cos(azm) * std::sin(plr), + std::sin(azm) * std::sin(plr), + std::cos(plr)).normalized(); + return bridge_mesh_intersect(hjp, n, r_back); + }, + initvals(polar, azimuth), // let's start with what we have + bound(3*PI/4, PI), // Must not exceed the slope limit + bound(-PI, PI) // azimuth can be a full range search + ); + + d = 0; t = oresult.score; + + polar = std::get<0>(oresult.optimum); + azimuth = std::get<1>(oresult.optimum); + Vec3d bridgedir = Vec3d(std::cos(azimuth) * std::sin(polar), + std::sin(azimuth) * std::sin(polar), + std::cos(polar)).normalized(); + + t = std::min(t, m_cfg.max_bridge_length_mm); + + while(d < t && !std::isinf(tdown = bridge_mesh_intersect( + hjp + d*bridgedir, + dirdown, + head.r_back_mm))) { + d += head.r_back_mm; + } + + if(std::isinf(tdown)) { // we heave found a route to the ground + routedown(head, bridgedir, d); continue; + } + + // ///////////////////////////////////////////////////////////////// + // Route to model body + // ///////////////////////////////////////////////////////////////// + + double zangle = std::asin(hit.direction()(Z)); + zangle = std::max(zangle, PI/4); + double h = std::sin(zangle) * head.fullwidth(); + + // The width of the tail head that we would like to have... + h = std::min(hit.distance() - head.r_back_mm, h); + + if(h > 0) { + Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h}; + auto center_hit = m_mesh.query_ray_hit(hjp, dirdown); + + double hitdiff = center_hit.distance() - hit.distance(); + Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm? + center_hit.position() : hit.position(); + + head.transform(); + + Pillar& pill = m_result.add_pillar(unsigned(head.id), + endp, + head.r_back_mm); + + Vec3d taildir = endp - hitp; + double dist = distance(endp, hitp) + m_cfg.head_penetration_mm; + double w = dist - 2 * head.r_pin_mm - head.r_back_mm; + + Head tailhead(head.r_back_mm, + head.r_pin_mm, + w, + m_cfg.head_penetration_mm, + taildir, + hitp); + + tailhead.transform(); + pill.base = tailhead.mesh; + + // Experimental: add the pillar to the index for cascading + modelpillars.emplace_back(unsigned(pill.id)); continue; } - double hl = base_head.fullwidth() - head.r_back_mm; - - result.add_pillar(idx, - Vec3d{headend(X), headend(Y), headend(Z) - gh + hl}, - cfg.head_back_radius_mm - ).base = base_head.mesh; + // We have failed to route this head. + BOOST_LOG_TRIVIAL(warning) + << "Failed to route model facing support point." + << " ID: " << idx; + head.invalidate(); } - }; + + for(auto pillid : modelpillars) { + auto& pillar = m_result.pillar(pillid); + m_pillar_index.insert(pillar.endpoint(), pillid); + } + } + + void cascade_pillars() { + // Now comes the algorithm that connects pillars with each other. + // Ideally every pillar should be connected with at least one of its + // neighbors if that neighbor is within max_pillar_link_distance + + // Pillars with height exceeding H1 will require at least one neighbor + // to connect with. Height exceeding H2 require two neighbors. + double H1 = m_cfg.max_solo_pillar_height_mm; + double H2 = m_cfg.max_dual_pillar_height_mm; + double d = m_cfg.max_pillar_link_distance_mm; + + //A connection between two pillars only counts if the height ratio is + // bigger than 50% + double min_height_ratio = 0.5; + + std::set pairs; + + auto cascadefn = + [this, d, &pairs, min_height_ratio, H1] (const SpatElement& el) + { + Vec3d qp = el.first; + + const Pillar& pillar = m_result.pillar(el.second); + + unsigned neighbors = m_cfg.pillar_cascade_neighbors; + + // connections are enough for one pillar + if(pillar.links >= neighbors) return; + + // Query all remaining points within reach + auto qres = m_pillar_index.query([qp, d](const SpatElement& e){ + return distance(e.first, qp) < d; + }); + + // sort the result by distance (have to check if this is needed) + std::sort(qres.begin(), qres.end(), + [qp](const SpatElement& e1, const SpatElement& e2){ + return distance(e1.first, qp) < distance(e2.first, qp); + }); + + for(auto& re : qres) { + + if(re.second == el.second) continue; + + auto a = el.second, b = re.second; + + // I hope that the area of a square is never equal to its + // circumference + auto hashval = 2 * (a + b) + a * b; + + if(pairs.find(hashval) != pairs.end()) continue; + + const Pillar& neighborpillar = m_result.pillars()[re.second]; + + // this neighbor is occupied + if(neighborpillar.links >= neighbors) continue; + + if(interconnect(pillar, neighborpillar)) { + pairs.insert(hashval); + + // If the interconnection length between the two pillars is + // less than 50% of the longer pillar's height, don't count + if(pillar.height < H1 || + neighborpillar.height / pillar.height > min_height_ratio) + m_result.increment_links(pillar); + + if(neighborpillar.height < H1 || + pillar.height / neighborpillar.height > min_height_ratio) + m_result.increment_links(neighborpillar); + + } + + // connections are enough for one pillar + if(pillar.links >= neighbors) break; + } + }; + + m_pillar_index.foreach(cascadefn); + + size_t pillarcount = m_result.pillars().size(); + + for(size_t pid = 0; pid < pillarcount; pid++) { + auto pillar = [this, pid]() { return m_result.pillar(pid); }; + + unsigned needpillars = 0; + if(pillar().bridges > m_cfg.max_bridges_on_pillar) needpillars = 3; + else if(pillar().links < 2 && pillar().height > H2) { + // Not enough neighbors to support this pillar + needpillars = 2 - pillar().links; + } + else if(pillar().links < 1 && pillar().height > H1) { + // No neighbors could be found and the pillar is too long. + needpillars = 1; + } + + // Search for new pillar locations + bool found = false; + double alpha = 0; // goes to 2Pi + double r = 2 * m_cfg.base_radius_mm; + Vec3d pillarsp = pillar().startpoint(); + Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r); + std::vector tv(needpillars, false); + std::vector spts(needpillars); + + while(!found && alpha < 2*PI) { + + for(unsigned n = 0; n < needpillars; n++) { + double a = alpha + n * PI/3; + Vec3d s = sp; + s(X) += std::cos(a) * r; + s(Y) += std::sin(a) * r; + spts[n] = s; + auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); + tv[n] = std::isinf(hr.distance()); + } + + found = std::all_of(tv.begin(), tv.end(), [](bool v){return v;}); + + // 20 angles will be tried... + alpha += 0.1 * PI; + } + + std::vector newpills; + newpills.reserve(needpillars); + + if(found) for(unsigned n = 0; n < needpillars; n++) { + Vec3d s = spts[n]; double gnd = m_result.ground_level; + Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); + p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + + if(interconnect(pillar(), p)) { + Pillar& pp = m_result.add_pillar(p); + m_pillar_index.insert(pp.endpoint(), unsigned(pp.id)); + + m_result.add_junction(s, pillar().r); + double t = bridge_mesh_intersect(pillarsp, + dirv(pillarsp, s), + pillar().r); + if(distance(pillarsp, s) < t) + m_result.add_bridge(pillarsp, s, pillar().r); + + if(pillar().endpoint()(Z) > m_result.ground_level) + m_result.add_junction(pillar().endpoint(), pillar().r); + + newpills.emplace_back(pp.id); + m_result.increment_links(pillar()); + } + } + + if(!newpills.empty()) { + for(auto it = newpills.begin(), nx = std::next(it); + nx != newpills.end(); ++it, ++nx) { + const Pillar& itpll = m_result.pillar(*it); + const Pillar& nxpll = m_result.pillar(*nx); + if(interconnect(itpll, nxpll)) { + m_result.increment_links(itpll); + m_result.increment_links(nxpll); + } + } + + m_pillar_index.foreach(cascadefn); + } + } + } // Step: process the support points where there is not enough space for a // full pinhead. In this case we will use a rounded sphere as a touching // point and use a thinner bridge (let's call it a stick). - auto process_headless = [thr]( - const SupportConfig& cfg, - const PointSet& headless_pts, - const PointSet& headless_norm, - const EigenMesh3D& emesh, - Result& result) + void routing_headless () { // For now we will just generate smaller headless sticks with a sharp // ending point that connects to the mesh surface. - const double R = cfg.headless_pillar_radius_mm; - const double HWIDTH_MM = R/3; - // We will sink the pins into the model surface for a distance of 1/3 of // the pin radius - for(int i = 0; i < headless_pts.rows(); i++) { thr(); - Vec3d sph = headless_pts.row(i); // Exact support position - Vec3d n = headless_norm.row(i); // mesh outward normal + for(unsigned i : m_iheadless) { m_thr(); + + const auto R = double(m_support_pts[i].head_front_radius); + const double HWIDTH_MM = R/3; + + // Exact support position + Vec3d sph = m_support_pts[i].pos.cast(); + Vec3d n = m_support_nmls.row(i); // mesh outward normal Vec3d sp = sph - n * HWIDTH_MM; // stick head start point Vec3d dir = {0, 0, -1}; Vec3d sj = sp + R * n; // stick start point // This is only for checking - double idist = bridge_mesh_intersect(sph, dir, R, emesh, true); - double dist = ray_mesh_intersect(sj, dir, emesh); + double idist = bridge_mesh_intersect(sph, dir, R, true); + double dist = ray_mesh_intersect(sj, dir); if(std::isinf(idist) || std::isnan(idist) || idist < 2*R || std::isinf(dist) || std::isnan(dist) || dist < 2*R) { @@ -1940,94 +2073,93 @@ bool SLASupportTree::generate(const PointSet &points, } Vec3d ej = sj + (dist + HWIDTH_MM)* dir; - result.add_compact_bridge(sp, ej, n, R); + m_result.add_compact_bridge(sp, ej, n, R); + } + } +}; + +bool SLASupportTree::generate(const std::vector &support_points, + const EigenMesh3D& mesh, + const SupportConfig &cfg, + const Controller &ctl) +{ + if(support_points.empty()) return false; + + Algorithm alg(cfg, mesh, support_points, *m_impl, ctl.cancelfn); + + // Let's define the individual steps of the processing. We can experiment + // later with the ordering and the dependencies between them. + enum Steps { + BEGIN, + FILTER, + PINHEADS, + CLASSIFY, + ROUTING_GROUND, + ROUTING_NONGROUND, + CASCADE_PILLARS, + HEADLESS, + DONE, + ABORT, + NUM_STEPS + //... + }; + + // Collect the algorithm steps into a nice sequence + std::array, NUM_STEPS> program = { + [] () { + // Begin... + // Potentially clear up the shared data (not needed for now) + }, + + std::bind(&Algorithm::filter, &alg), + + std::bind(&Algorithm::add_pinheads, &alg), + + std::bind(&Algorithm::classify, &alg), + + std::bind(&Algorithm::routing_to_ground, &alg), + + std::bind(&Algorithm::routing_to_model, &alg), + + std::bind(&Algorithm::cascade_pillars, &alg), + + std::bind(&Algorithm::routing_headless, &alg), + + [] () { + // Done + }, + + [] () { + // Abort } }; - // Now that the individual blocks are defined, lets connect the wires. We - // will create an array of functions which represents a program. Place the - // step methods in the array and bind the right arguments to the methods - // This way the data dependencies will be easily traceable between - // individual steps. - // There will be empty steps as well like the begin step or the done or - // abort steps. These are slots for future initialization or cleanup. + Steps pc = BEGIN; - using std::cref; // Bind inputs with cref (read-only) - using std::ref; // Bind outputs with ref (writable) - using std::bind; - - // Here we can easily track what goes in and what comes out of each step: - // (see the cref-s as inputs and ref-s as outputs) - std::array, NUM_STEPS> program = { - [] () { - // Begin... - // Potentially clear up the shared data (not needed for now) - }, - - // Filtering unnecessary support points - bind(filterfn, cref(cfg), cref(points), cref(mesh), - ref(filtered_points), ref(head_normals), - ref(head_positions), ref(headless_positions), ref(headless_normals)), - - // Pinhead generation - bind(pinheadfn, cref(cfg), - ref(head_positions), ref(head_normals), ref(result)), - - // Classification of support points - bind(classifyfn, cref(cfg), cref(mesh), - ref(head_positions), ref(ground_heads), ref(noground_heads), - ref(head_heights), ref(ground_connectors), ref(result)), - - // Routing ground connecting clusters - bind(routing_ground_fn, - cref(cfg), cref(ground_connectors), cref(ground_heads), cref(mesh), - ref(result)), - - // routing non ground connecting support points - bind(routing_nongnd_fn, cref(cfg), cref(head_heights), cref(noground_heads), - ref(result)), - - bind(process_headless, - cref(cfg), cref(headless_positions), - cref(headless_normals), cref(mesh), - ref(result)), - [] () { - // Done - }, - [] () { - // Halt - }, - [] () { - // Abort - } - }; - - if(cfg.ground_facing_only) { // Delete the non-gnd steps if necessary + if(cfg.ground_facing_only) { program[ROUTING_NONGROUND] = []() { - BOOST_LOG_TRIVIAL(info) << "Skipping non-ground facing supports as " - "requested."; + BOOST_LOG_TRIVIAL(info) + << "Skipping model-facing supports as requested."; }; - program[HEADLESS] = [](){ - BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as " - "requested"; + program[HEADLESS] = []() { + BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as" + " requested."; }; } - Steps pc = BEGIN, pc_prev = BEGIN; - // Let's define a simple automaton that will run our program. - auto progress = [&ctl, &pc, &pc_prev] () { + auto progress = [&ctl, &pc] () { static const std::array stepstr { - "Starting", - "Filtering", - "Generate pinheads", - "Classification", - "Routing to ground", - "Routing supports to model surface", - "Processing small holes", - "Done", - "Halt", - "Abort" + L("Starting"), + L("Filtering"), + L("Generate pinheads"), + L("Classification"), + L("Routing to ground"), + L("Routing supports to model surface"), + L("Cascading pillars"), + L("Processing small holes"), + L("Done"), + L("Abort") }; static const std::array stepstate { @@ -2038,8 +2170,8 @@ bool SLASupportTree::generate(const PointSet &points, 60, 70, 80, + 90, 100, - 0, 0 }; @@ -2051,9 +2183,9 @@ bool SLASupportTree::generate(const PointSet &points, case PINHEADS: pc = CLASSIFY; break; case CLASSIFY: pc = ROUTING_GROUND; break; case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; - case ROUTING_NONGROUND: pc = HEADLESS; break; + case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break; + case CASCADE_PILLARS: pc = HEADLESS; break; case HEADLESS: pc = DONE; break; - case HALT: pc = pc_prev; break; case DONE: case ABORT: break; default: ; @@ -2062,13 +2194,11 @@ bool SLASupportTree::generate(const PointSet &points, }; // Just here we run the computation... - while(pc < DONE || pc == HALT) { + while(pc < DONE) { progress(); program[pc](); } - if(pc == ABORT) throw SLASupportsStoppedException(); - return pc == ABORT; } @@ -2110,6 +2240,20 @@ SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const return ret; } +SlicedSupports SLASupportTree::slice(const std::vector &heights, + float cr) const +{ + TriangleMesh fullmesh = m_impl->merged_mesh(); + fullmesh.merge(get_pad()); + TriangleMeshSlicer slicer(cr, 0); + //TriangleMeshSlicer slicer(&fullmesh); + slicer.init(&fullmesh, []() {}); + SlicedSupports ret; + slicer.slice(heights, &ret, get().ctl().cancelfn); + + return ret; +} + const TriangleMesh &SLASupportTree::add_pad(const SliceLayer& baseplate, const PoolConfig& pcfg) const { @@ -2131,7 +2275,7 @@ void SLASupportTree::remove_pad() m_impl->remove_pad(); } -SLASupportTree::SLASupportTree(const PointSet &points, +SLASupportTree::SLASupportTree(const std::vector &points, const EigenMesh3D& emesh, const SupportConfig &cfg, const Controller &ctl): @@ -2152,8 +2296,5 @@ SLASupportTree &SLASupportTree::operator=(const SLASupportTree &c) SLASupportTree::~SLASupportTree() {} -SLASupportsStoppedException::SLASupportsStoppedException(): - std::runtime_error("") {} - } } diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index c29b2a571..66677e4d7 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -50,11 +50,6 @@ struct SupportConfig { // Width in mm from the back sphere center to the front sphere center. double head_width_mm = 1.0; - // Radius in mm of the support pillars. The actual radius of the pillars - // beginning with a head will not be higher than head_back_radius but the - // headless pillars will have half of this value. - double headless_pillar_radius_mm = 0.4; - // How to connect pillars PillarConnectionMode pillar_connection_mode = PillarConnectionMode::dynamic; @@ -74,18 +69,34 @@ struct SupportConfig { double base_height_mm = 1.0; // The default angle for connecting support sticks and junctions. - double tilt = M_PI/4; + double bridge_slope = M_PI/4; // The max length of a bridge in mm - double max_bridge_length_mm = 15.0; + double max_bridge_length_mm = 10.0; + + // The max distance of a pillar to pillar link. + double max_pillar_link_distance_mm = 10.0; // The elevation in Z direction upwards. This is the space between the pad // and the model object's bounding box bottom. double object_elevation_mm = 10; - // The max Z angle for a normal at which it will get completely ignored. - double normal_cutoff_angle = 150.0 * M_PI / 180.0; + // ///////////////////////////////////////////////////////////////////////// + // Compile time configuration values (candidates for runtime) + // ///////////////////////////////////////////////////////////////////////// + // The max Z angle for a normal at which it will get completely ignored. + static const double normal_cutoff_angle; + + // The shortest distance of any support structure from the model surface + static const double safety_distance_mm; + + static const double max_solo_pillar_height_mm; + static const double max_dual_pillar_height_mm; + static const double optimizer_rel_score_diff; + static const unsigned optimizer_max_iterations; + static const unsigned pillar_cascade_neighbors; + static const unsigned max_bridges_on_pillar; }; struct PoolConfig; @@ -116,21 +127,14 @@ using PointSet = Eigen::MatrixXd; //EigenMesh3D to_eigenmesh(const ModelObject& model); // Simple conversion of 'vector of points' to an Eigen matrix -PointSet to_point_set(const std::vector&); +//PointSet to_point_set(const std::vector&); /* ************************************************************************** */ -/// Just a wrapper to the runtime error to be recognizable in try blocks -class SLASupportsStoppedException: public std::runtime_error { -public: - using std::runtime_error::runtime_error; - SLASupportsStoppedException(); -}; - /// The class containing mesh data for the generated supports. class SLASupportTree { - class Impl; + class Impl; // persistent support data std::unique_ptr m_impl; Impl& get() { return *m_impl; } @@ -140,16 +144,25 @@ class SLASupportTree { const SupportConfig&, const Controller&); - /// Generate the 3D supports for a model intended for SLA print. - bool generate(const PointSet& pts, + // The generation algorithm is quite long and will be captured in a separate + // class with private data, helper methods, etc... This data is only needed + // during the calculation whereas the Impl class contains the persistent + // data, mostly the meshes. + class Algorithm; + + // Generate the 3D supports for a model intended for SLA print. This + // will instantiate the Algorithm class and call its appropriate methods + // with status indication. + bool generate(const std::vector& pts, const EigenMesh3D& mesh, const SupportConfig& cfg = {}, const Controller& ctl = {}); + public: SLASupportTree(); - SLASupportTree(const PointSet& pts, + SLASupportTree(const std::vector& pts, const EigenMesh3D& em, const SupportConfig& cfg = {}, const Controller& ctl = {}); @@ -168,6 +181,8 @@ public: /// Get the sliced 2d layers of the support geometry. SlicedSupports slice(float layerh, float init_layerh = -1.0) const; + SlicedSupports slice(const std::vector&, float closing_radius) const; + /// Adding the "pad" (base pool) under the supports const TriangleMesh& add_pad(const SliceLayer& baseplate, const PoolConfig& pcfg) const; diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index 25638fe69..c368b8604 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -17,6 +17,8 @@ #include #include +#include + #include "SLASpatIndex.hpp" #include "ClipperUtils.hpp" @@ -89,6 +91,11 @@ size_t SpatIndex::size() const return m_impl->m_store.size(); } +void SpatIndex::foreach(std::function fn) +{ + for(auto& el : m_impl->m_store) fn(el); +} + /* **************************************************************************** * EigenMesh3D implementation * ****************************************************************************/ @@ -167,6 +174,7 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const hit_result ret(*this); ret.m_t = double(hit.t); ret.m_dir = dir; + ret.m_source = s; if(!std::isinf(hit.t) && !std::isnan(hit.t)) ret.m_face_id = hit.id; return ret; @@ -186,6 +194,15 @@ bool EigenMesh3D::inside(const Vec3d &p) const { } #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ +double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { + double sqdst = 0; + Eigen::Matrix pp = p; + Eigen::Matrix cc; + sqdst = m_aabb->squared_distance(m_V, m_F, pp, i, cc); + c = cc; + return sqdst; +} + /* **************************************************************************** * Misc functions * ****************************************************************************/ @@ -208,22 +225,31 @@ template double distance(const Vec& pp1, const Vec& pp2) { PointSet normals(const PointSet& points, const EigenMesh3D& mesh, double eps, - std::function throw_on_cancel) + std::function thr, // throw on cancel + const std::vector& pt_indices = {}) { if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) return {}; - Eigen::VectorXd dists; - Eigen::VectorXi I; - PointSet C; + std::vector range = pt_indices; + if(range.empty()) { + range.resize(size_t(points.rows()), 0); + std::iota(range.begin(), range.end(), 0); + } - igl::point_mesh_squared_distance( points, mesh.V(), mesh.F(), dists, I, C); + PointSet ret(range.size(), 3); - PointSet ret(I.rows(), 3); - for(int i = 0; i < I.rows(); i++) { - throw_on_cancel(); - auto idx = I(i); - auto trindex = mesh.F().row(idx); + tbb::parallel_for(size_t(0), range.size(), + [&ret, &range, &mesh, &points, thr, eps](size_t ridx) + { + thr(); + auto eidx = Eigen::Index(range[ridx]); + int faceid = 0; + Vec3d p; + + mesh.squared_distance(points.row(eidx), faceid, p); + + auto trindex = mesh.F().row(faceid); const Vec3d& p1 = mesh.V().row(trindex(0)); const Vec3d& p2 = mesh.V().row(trindex(1)); @@ -237,8 +263,6 @@ PointSet normals(const PointSet& points, // of its triangle. The procedure is the same, get the neighbor // triangles and calculate an average normal. - const Vec3d& p = C.row(i); - // mark the vertex indices of the edge. ia and ib marks and edge ic // will mark a single vertex. int ia = -1, ib = -1, ic = -1; @@ -266,7 +290,7 @@ PointSet normals(const PointSet& points, std::vector neigh; if(ic >= 0) { // The point is right on a vertex of the triangle for(int n = 0; n < mesh.F().rows(); ++n) { - throw_on_cancel(); + thr(); Vec3i ni = mesh.F().row(n); if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic)) neigh.emplace_back(ni); @@ -275,7 +299,7 @@ PointSet normals(const PointSet& points, else if(ia >= 0 && ib >= 0) { // the point is on and edge // now get all the neigboring triangles for(int n = 0; n < mesh.F().rows(); ++n) { - throw_on_cancel(); + thr(); Vec3i ni = mesh.F().row(n); if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) && (ni(X) == ib || ni(Y) == ib || ni(Z) == ib)) @@ -316,52 +340,32 @@ PointSet normals(const PointSet& points, Vec3d sumnorm(0, 0, 0); sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm); sumnorm.normalize(); - ret.row(i) = sumnorm; + ret.row(long(ridx)) = sumnorm; } else { // point lies safely within its triangle Eigen::Vector3d U = p2 - p1; Eigen::Vector3d V = p3 - p1; - ret.row(i) = U.cross(V).normalized(); + ret.row(long(ridx)) = U.cross(V).normalized(); } - } + }); return ret; } +namespace bgi = boost::geometry::index; +using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; -// Clustering a set of points by the given criteria -ClusteredPoints cluster( - const sla::PointSet& points, - std::function pred, - unsigned max_points = 0) +ClusteredPoints cluster(Index3D& sindex, unsigned max_points, + std::function(const Index3D&, const SpatElement&)> qfn) { - - namespace bgi = boost::geometry::index; - using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; - - // A spatial index for querying the nearest points - Index3D sindex; - - // Build the index - for(unsigned idx = 0; idx < points.rows(); idx++) - sindex.insert( std::make_pair(points.row(idx), idx)); - using Elems = std::vector; // Recursive function for visiting all the points in a given distance to // each other std::function group = - [&sindex, &group, pred, max_points](Elems& pts, Elems& cluster) + [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) { for(auto& p : pts) { - std::vector tmp; - - sindex.query( - bgi::satisfies([p, pred](const SpatElement& se) { - return pred(p, se); - }), - std::back_inserter(tmp) - ); - + std::vector tmp = qfn(sindex, p); auto cmp = [](const SpatElement& e1, const SpatElement& e2){ return e1.second < e2.second; }; @@ -405,5 +409,84 @@ ClusteredPoints cluster( return result; } +namespace { +std::vector distance_queryfn(const Index3D& sindex, + const SpatElement& p, + double dist, + unsigned max_points) +{ + std::vector tmp; tmp.reserve(max_points); + sindex.query( + bgi::nearest(p.first, max_points), + std::back_inserter(tmp) + ); + + for(auto it = tmp.begin(); it < tmp.end(); ++it) + if(distance(p.first, it->first) > dist) it = tmp.erase(it); + + return tmp; +} +} + +// Clustering a set of points by the given criteria +ClusteredPoints cluster( + const std::vector& indices, + std::function pointfn, + double dist, + unsigned max_points) +{ + // A spatial index for querying the nearest points + Index3D sindex; + + // Build the index + for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); + + return cluster(sindex, max_points, + [dist, max_points](const Index3D& sidx, const SpatElement& p) + { + return distance_queryfn(sidx, p, dist, max_points); + }); +} + +// Clustering a set of points by the given criteria +ClusteredPoints cluster( + const std::vector& indices, + std::function pointfn, + std::function predicate, + unsigned max_points) +{ + // A spatial index for querying the nearest points + Index3D sindex; + + // Build the index + for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); + + return cluster(sindex, max_points, + [max_points, predicate](const Index3D& sidx, const SpatElement& p) + { + std::vector tmp; tmp.reserve(max_points); + sidx.query(bgi::satisfies([p, predicate](const SpatElement& e){ + return predicate(p, e); + }), std::back_inserter(tmp)); + return tmp; + }); +} + +ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) +{ + // A spatial index for querying the nearest points + Index3D sindex; + + // Build the index + for(Eigen::Index i = 0; i < pts.rows(); i++) + sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); + + return cluster(sindex, max_points, + [dist, max_points](const Index3D& sidx, const SpatElement& p) + { + return distance_queryfn(sidx, p, dist, max_points); + }); +} + } } diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 803b2acbe..6d8b77ec9 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -30,7 +30,6 @@ public: std::vector support_points; // all the support points (manual/auto) SupportTreePtr support_tree_ptr; // the supports SlicedSupports support_slices; // sliced supports - std::vector level_ids; inline SupportData(const TriangleMesh& trmesh): emesh(trmesh) {} }; @@ -118,6 +117,9 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf // Make a copy of the config, normalize it. DynamicPrintConfig config(config_in); + config.option("sla_print_settings_id", true); + config.option("sla_material_settings_id", true); + config.option("printer_settings_id", true); config.normalize(); // Collect changes to print config. t_config_option_keys print_diff = m_print_config.diff(config); @@ -151,9 +153,9 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf PlaceholderParser &pp = this->placeholder_parser(); pp.apply_config(config); // Set the profile aliases for the PrintBase::output_filename() - pp.set("print_preset", config_in.option("sla_print_settings_id")->clone()); - pp.set("material_preset", config_in.option("sla_material_settings_id")->clone()); - pp.set("printer_preset", config_in.option("printer_settings_id")->clone()); + pp.set("print_preset", config.option("sla_print_settings_id")->clone()); + pp.set("material_preset", config.option("sla_material_settings_id")->clone()); + pp.set("printer_preset", config.option("printer_settings_id")->clone()); } // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. @@ -300,81 +302,82 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); assert(it_status != model_object_status.end()); assert(it_status->status != ModelObjectStatus::Deleted); - if (it_status->status == ModelObjectStatus::New) - // PrintObject instances will be added in the next loop. - continue; - // Update the ModelObject instance, possibly invalidate the linked PrintObjects. - assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); - const ModelObject &model_object_new = *model.objects[idx_model_object]; - auto it_print_object_status = print_object_status.lower_bound(PrintObjectStatus(model_object.id())); - if (it_print_object_status != print_object_status.end() && it_print_object_status->id != model_object.id()) - it_print_object_status = print_object_status.end(); - // Check whether a model part volume was added or removed, their transformations or order changed. - bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); - bool sla_trafo_differs = model_object.instances.empty() != model_object_new.instances.empty() || - (! model_object.instances.empty() && ! sla_trafo(model_object).isApprox(sla_trafo(model_object_new))); - if (model_parts_differ || sla_trafo_differs) { - // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. - if (it_print_object_status != print_object_status.end()) { - update_apply_status(it_print_object_status->print_object->invalidate_all_steps()); - const_cast(*it_print_object_status).status = PrintObjectStatus::Deleted; - } - // Copy content of the ModelObject including its ID, do not change the parent. - model_object.assign_copy(model_object_new); - } else { - // Synchronize Object's config. - bool object_config_changed = model_object.config != model_object_new.config; - if (object_config_changed) - model_object.config = model_object_new.config; - if (! object_diff.empty() || object_config_changed) { - SLAPrintObjectConfig new_config = m_default_object_config; - normalize_and_apply_config(new_config, model_object.config); - if (it_print_object_status != print_object_status.end()) { - t_config_option_keys diff = it_print_object_status->print_object->config().diff(new_config); - if (! diff.empty()) { - update_apply_status(it_print_object_status->print_object->invalidate_state_by_config_options(diff)); - it_print_object_status->print_object->config_apply_only(new_config, diff, true); - } - } - } - /*if (model_object.sla_support_points != model_object_new.sla_support_points) { - model_object.sla_support_points = model_object_new.sla_support_points; - if (it_print_object_status != print_object_status.end()) - update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); - } - if (model_object.sla_points_status != model_object_new.sla_points_status) { - // Change of this status should invalidate support points. The points themselves are not enough, there are none - // in case that nothing was generated OR that points were autogenerated already and not copied to the front-end. - // These cases can only be differentiated by checking the status change. However, changing from 'Generating' should NOT - // invalidate - that would keep stopping the background processing without a reason. - if (model_object.sla_points_status != sla::PointsStatus::Generating) - if (it_print_object_status != print_object_status.end()) - update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); - model_object.sla_points_status = model_object_new.sla_points_status; - }*/ + // PrintObject for this ModelObject, if it exists. + auto it_print_object_status = print_object_status.end(); + if (it_status->status != ModelObjectStatus::New) { + // Update the ModelObject instance, possibly invalidate the linked PrintObjects. + assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); + const ModelObject &model_object_new = *model.objects[idx_model_object]; + it_print_object_status = print_object_status.lower_bound(PrintObjectStatus(model_object.id())); + if (it_print_object_status != print_object_status.end() && it_print_object_status->id != model_object.id()) + it_print_object_status = print_object_status.end(); + // Check whether a model part volume was added or removed, their transformations or order changed. + bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); + bool sla_trafo_differs = model_object.instances.empty() != model_object_new.instances.empty() || + (! model_object.instances.empty() && ! sla_trafo(model_object).isApprox(sla_trafo(model_object_new))); + if (model_parts_differ || sla_trafo_differs) { + // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. + if (it_print_object_status != print_object_status.end()) { + update_apply_status(it_print_object_status->print_object->invalidate_all_steps()); + const_cast(*it_print_object_status).status = PrintObjectStatus::Deleted; + } + // Copy content of the ModelObject including its ID, do not change the parent. + model_object.assign_copy(model_object_new); + } else { + // Synchronize Object's config. + bool object_config_changed = model_object.config != model_object_new.config; + if (object_config_changed) + model_object.config = model_object_new.config; + if (! object_diff.empty() || object_config_changed) { + SLAPrintObjectConfig new_config = m_default_object_config; + normalize_and_apply_config(new_config, model_object.config); + if (it_print_object_status != print_object_status.end()) { + t_config_option_keys diff = it_print_object_status->print_object->config().diff(new_config); + if (! diff.empty()) { + update_apply_status(it_print_object_status->print_object->invalidate_state_by_config_options(diff)); + it_print_object_status->print_object->config_apply_only(new_config, diff, true); + } + } + } + /*if (model_object.sla_support_points != model_object_new.sla_support_points) { + model_object.sla_support_points = model_object_new.sla_support_points; + if (it_print_object_status != print_object_status.end()) + update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); + } + if (model_object.sla_points_status != model_object_new.sla_points_status) { + // Change of this status should invalidate support points. The points themselves are not enough, there are none + // in case that nothing was generated OR that points were autogenerated already and not copied to the front-end. + // These cases can only be differentiated by checking the status change. However, changing from 'Generating' should NOT + // invalidate - that would keep stopping the background processing without a reason. + if (model_object.sla_points_status != sla::PointsStatus::Generating) + if (it_print_object_status != print_object_status.end()) + update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); + model_object.sla_points_status = model_object_new.sla_points_status; + }*/ - bool old_user_modified = model_object.sla_points_status == sla::PointsStatus::UserModified; - bool new_user_modified = model_object_new.sla_points_status == sla::PointsStatus::UserModified; - if ((old_user_modified && ! new_user_modified) || // switching to automatic supports from manual supports - (! old_user_modified && new_user_modified) || // switching to manual supports from automatic supports - (new_user_modified && model_object.sla_support_points != model_object_new.sla_support_points)) { - if (it_print_object_status != print_object_status.end()) - update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); + bool old_user_modified = model_object.sla_points_status == sla::PointsStatus::UserModified; + bool new_user_modified = model_object_new.sla_points_status == sla::PointsStatus::UserModified; + if ((old_user_modified && ! new_user_modified) || // switching to automatic supports from manual supports + (! old_user_modified && new_user_modified) || // switching to manual supports from automatic supports + (new_user_modified && model_object.sla_support_points != model_object_new.sla_support_points)) { + if (it_print_object_status != print_object_status.end()) + update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); - model_object.sla_points_status = model_object_new.sla_points_status; - model_object.sla_support_points = model_object_new.sla_support_points; - } + model_object.sla_points_status = model_object_new.sla_points_status; + model_object.sla_support_points = model_object_new.sla_support_points; + } - // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. - model_object.name = model_object_new.name; - model_object.input_file = model_object_new.input_file; - model_object.clear_instances(); - model_object.instances.reserve(model_object_new.instances.size()); - for (const ModelInstance *model_instance : model_object_new.instances) { - model_object.instances.emplace_back(new ModelInstance(*model_instance)); - model_object.instances.back()->set_model_object(&model_object); - } - } + // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. + model_object.name = model_object_new.name; + model_object.input_file = model_object_new.input_file; + model_object.clear_instances(); + model_object.instances.reserve(model_object_new.instances.size()); + for (const ModelInstance *model_instance : model_object_new.instances) { + model_object.instances.emplace_back(new ModelInstance(*model_instance)); + model_object.instances.back()->set_model_object(&model_object); + } + } + } std::vector new_instances = sla_instances(model_object); if (it_print_object_status != print_object_status.end() && it_print_object_status->status != PrintObjectStatus::Deleted) { @@ -544,9 +547,9 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { scfg.head_penetration_mm = c.support_head_penetration.getFloat(); scfg.head_width_mm = c.support_head_width.getFloat(); scfg.object_elevation_mm = c.support_object_elevation.getFloat(); - scfg.tilt = c.support_critical_angle.getFloat() * PI / 180.0 ; + scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); - scfg.headless_pillar_radius_mm = 0.375*c.support_pillar_diameter.getFloat(); + scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat(); switch(c.support_pillar_connection_mode.getInt()) { case slapcmZigZag: scfg.pillar_connection_mode = sla::PillarConnectionMode::zigzag; break; @@ -563,32 +566,45 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { return scfg; } +sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { + sla::PoolConfig pcfg; + + pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat(); + pcfg.wall_slope = c.pad_wall_slope.getFloat(); + pcfg.edge_radius_mm = c.pad_edge_radius.getFloat(); + pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat(); + pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); + + return pcfg; +} + void swapXY(ExPolygon& expoly) { for(auto& p : expoly.contour.points) std::swap(p(X), p(Y)); for(auto& h : expoly.holes) for(auto& p : h.points) std::swap(p(X), p(Y)); } - } -std::vector SLAPrint::calculate_heights(const BoundingBoxf3& bb3d, - float elevation, - float initial_layer_height, - float layer_height) const +std::string SLAPrint::validate() const { - std::vector heights; - float minZ = float(bb3d.min(Z)) - float(elevation); - float maxZ = float(bb3d.max(Z)); - auto flh = float(layer_height); - auto gnd = float(bb3d.min(Z)); + for(SLAPrintObject * po : m_objects) { + sla::SupportConfig cfg = make_support_cfg(po->config()); - for(float h = minZ + initial_layer_height; h < maxZ; h += flh) - if(h >= gnd) heights.emplace_back(h); + double pinhead_width = + 2 * cfg.head_front_radius_mm + + cfg.head_width_mm + + 2 * cfg.head_back_radius_mm - + cfg.head_penetration_mm; - return heights; + if(pinhead_width > cfg.object_elevation_mm) + return L("Elevetion is too low for object."); + } + + return ""; } template -void report_status(SLAPrint& p, int st, const std::string& msg, Args&&...args) { +void report_status(SLAPrint& p, int st, const std::string& msg, Args&&...args) +{ BOOST_LOG_TRIVIAL(info) << st << "% " << msg; p.set_status(st, msg, std::forward(args)...); } @@ -599,12 +615,19 @@ void SLAPrint::process() using namespace sla; using ExPolygon = Slic3r::ExPolygon; + if(m_objects.empty()) return; + // Assumption: at this point the print objects should be populated only with // the model objects we have to process and the instances are also filtered // shortcut to initial layer height double ilhd = m_material_config.initial_layer_height.getFloat(); auto ilh = float(ilhd); + double lhd = m_objects.front()->m_config.layer_height.getFloat(); + float lh = float(lhd); + + auto ilhs = LevelID(ilhd / SCALING_FACTOR); + auto lhs = LevelID(lhd / SCALING_FACTOR); const size_t objcount = m_objects.size(); const unsigned min_objstatus = 0; // where the per object operations start @@ -625,25 +648,59 @@ void SLAPrint::process() // Slicing the model object. This method is oversimplified and needs to // be compared with the fff slicing algorithm for verification - auto slice_model = [this, ilh](SLAPrintObject& po) { - double lh = po.m_config.layer_height.getFloat(); - + auto slice_model = [this, ilhs, lhs, ilh, lh](SLAPrintObject& po) { TriangleMesh mesh = po.transformed_mesh(); + + // We need to prepare the slice index... + + auto&& bb3d = mesh.bounding_box(); + double minZ = bb3d.min(Z) - po.get_elevation(); + double maxZ = bb3d.max(Z); + + auto minZs = LevelID(minZ / SCALING_FACTOR); + auto maxZs = LevelID(maxZ / SCALING_FACTOR); + + po.m_slice_index.clear(); + po.m_slice_index.reserve(size_t(maxZs - (minZs + ilhs) / lhs) + 1); + po.m_slice_index.emplace_back(minZs + ilhs, float(minZ) + ilh / 2.f, ilh); + + for(LevelID h = minZs + ilhs + lhs; h <= maxZs; h += lhs) { + po.m_slice_index.emplace_back(h, float(h*SCALING_FACTOR) - lh / 2.f, lh); + } + + auto slindex_it = po.search_slice_index(float(bb3d.min(Z))); + + if(slindex_it == po.m_slice_index.end()) + throw std::runtime_error(L("Slicing had to be stopped " + "due to an internal error.")); + + po.m_model_height_levels.clear(); + po.m_model_height_levels.reserve(po.m_slice_index.size()); + for(auto it = slindex_it; it != po.m_slice_index.end(); ++it) + { + po.m_model_height_levels.emplace_back(it->slice_level()); + } + TriangleMeshSlicer slicer(float(po.config().slice_closing_radius.value),0); slicer.init(&mesh, [](){}); - // The 1D grid heights - std::vector heights = calculate_heights(mesh.bounding_box(), - float(po.get_elevation()), - ilh, float(lh)); + po.m_model_slices.clear(); + slicer.slice(po.m_model_height_levels, + &po.m_model_slices, + [this](){ throw_if_canceled(); }); - auto& layers = po.m_model_slices; layers.clear(); - slicer.slice(heights, &layers, [this](){ throw_if_canceled(); }); + auto mit = slindex_it; + for(size_t id = 0; + id < po.m_model_slices.size() && mit != po.m_slice_index.end(); + id++) + { + mit->set_model_slice_idx(id); ++mit; + } }; // In this step we check the slices, identify island and cover them with // support points. Then we sprinkle the rest of the mesh. - auto support_points = [this, ilh](SLAPrintObject& po) { + auto support_points = [this](SLAPrintObject& po) { const ModelObject& mo = *po.m_model_object; po.m_supportdata.reset( new SLAPrintObject::SupportData(po.transformed_mesh()) ); @@ -660,12 +717,7 @@ void SLAPrint::process() if (mo.sla_points_status != sla::PointsStatus::UserModified) { // calculate heights of slices (slices are calculated already) - double lh = po.m_config.layer_height.getFloat(); - - std::vector heights = - calculate_heights(po.transformed_mesh().bounding_box(), - float(po.get_elevation()), - ilh, float(lh)); + const std::vector& heights = po.m_model_height_levels; this->throw_if_canceled(); SLAAutoSupports::Config config; @@ -674,6 +726,7 @@ void SLAPrint::process() // the density config value is in percents: config.density_relative = float(cfg.support_points_density_relative / 100.f); config.minimal_distance = float(cfg.support_points_minimal_distance); + config.head_diameter = float(cfg.support_head_front_diameter); // Construction of this object does the calculation. this->throw_if_canceled(); @@ -711,54 +764,52 @@ void SLAPrint::process() return; } - try { - sla::SupportConfig scfg = make_support_cfg(po.m_config); - sla::Controller ctl; + sla::SupportConfig scfg = make_support_cfg(po.m_config); + sla::Controller ctl; - // some magic to scale the status values coming from the support - // tree creation into the whole print process - auto stfirst = OBJ_STEP_LEVELS.begin(); - auto stthis = stfirst + slaposSupportTree; - // we need to add up the status portions until this operation - int init = std::accumulate(stfirst, stthis, 0); - init = int(init * ostepd); // scale the init portion + // some magic to scale the status values coming from the support + // tree creation into the whole print process + auto stfirst = OBJ_STEP_LEVELS.begin(); + auto stthis = stfirst + slaposSupportTree; + // we need to add up the status portions until this operation + int init = std::accumulate(stfirst, stthis, 0); + init = int(init * ostepd); // scale the init portion - // scaling for the sub operations - double d = *stthis / (objcount * 100.0); + // scaling for the sub operations + double d = *stthis / (objcount * 100.0); - ctl.statuscb = [this, init, d](unsigned st, const std::string& msg) - { - //FIXME this status line scaling does not seem to be correct. - // How does it account for an increasing object index? - report_status(*this, int(init + st*d), msg); - }; + ctl.statuscb = [this, init, d](unsigned st, const std::string& msg) + { + //FIXME this status line scaling does not seem to be correct. + // How does it account for an increasing object index? + report_status(*this, int(init + st*d), msg); + }; - ctl.stopcondition = [this](){ return canceled(); }; - ctl.cancelfn = [this]() { throw_if_canceled(); }; + ctl.stopcondition = [this](){ return canceled(); }; + ctl.cancelfn = [this]() { throw_if_canceled(); }; - po.m_supportdata->support_tree_ptr.reset( - new SLASupportTree(sla::to_point_set(po.m_supportdata->support_points), - po.m_supportdata->emesh, scfg, ctl)); + po.m_supportdata->support_tree_ptr.reset( + new SLASupportTree(po.m_supportdata->support_points, + po.m_supportdata->emesh, scfg, ctl)); - // Create the unified mesh - auto rc = SlicingStatus::RELOAD_SCENE; + throw_if_canceled(); - // This is to prevent "Done." being displayed during merged_mesh() - report_status(*this, -1, L("Visualizing supports")); - po.m_supportdata->support_tree_ptr->merged_mesh(); + // Create the unified mesh + auto rc = SlicingStatus::RELOAD_SCENE; - BOOST_LOG_TRIVIAL(debug) << "Processed support point count " - << po.m_supportdata->support_points.size(); + // This is to prevent "Done." being displayed during merged_mesh() + report_status(*this, -1, L("Visualizing supports")); + po.m_supportdata->support_tree_ptr->merged_mesh(); - // Check the mesh for later troubleshooting. - if(po.support_mesh().empty()) - BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; + BOOST_LOG_TRIVIAL(debug) << "Processed support point count " + << po.m_supportdata->support_points.size(); + + // Check the mesh for later troubleshooting. + if(po.support_mesh().empty()) + BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; + + report_status(*this, -1, L("Visualizing supports"), rc); - report_status(*this, -1, L("Visualizing supports"), rc); - } catch(sla::SLASupportsStoppedException&) { - // no need to rethrow - // throw_if_canceled(); - } }; // This step generates the sla base pad @@ -812,85 +863,36 @@ void SLAPrint::process() // Slicing the support geometries similarly to the model slicing procedure. // If the pad had been added previously (see step "base_pool" than it will // be part of the slices) - auto slice_supports = [ilh](SLAPrintObject& po) { + auto slice_supports = [](SLAPrintObject& po) { auto& sd = po.m_supportdata; + + if(sd) sd->support_slices.clear(); + if(sd && sd->support_tree_ptr) { - auto lh = float(po.m_config.layer_height.getFloat()); - sd->support_slices = sd->support_tree_ptr->slice(lh, ilh); + + std::vector heights; heights.reserve(po.m_slice_index.size()); + + for(auto& rec : po.m_slice_index) { + heights.emplace_back(rec.slice_level()); + } + + sd->support_slices = sd->support_tree_ptr->slice( + heights, float(po.config().slice_closing_radius.value)); + } + + for(size_t i = 0; + i < sd->support_slices.size() && i < po.m_slice_index.size(); + ++i) + { + po.m_slice_index[i].set_support_slice_idx(i); } }; // We have the layer polygon collection but we need to unite them into // an index where the key is the height level in discrete levels (clipper) - auto index_slices = [ilhd](SLAPrintObject& po) { - po.m_slice_index.clear(); - auto sih = LevelID(scale_(ilhd)); - - // Establish the slice grid boundaries - auto bb = po.transformed_mesh().bounding_box(); - double modelgnd = bb.min(Z); - double elevation = po.get_elevation(); - double lh = po.m_config.layer_height.getFloat(); - double minZ = modelgnd - elevation; - - // scaled values: - auto sminZ = LevelID(scale_(minZ)); - auto smaxZ = LevelID(scale_(bb.max(Z))); - auto smodelgnd = LevelID(scale_(modelgnd)); - auto slh = LevelID(scale_(lh)); - - // It is important that the next levels match the levels in - // model_slice method. Only difference is that here it works with - // scaled coordinates - po.m_level_ids.clear(); - for(LevelID h = sminZ + sih; h < smaxZ; h += slh) - if(h >= smodelgnd) po.m_level_ids.emplace_back(h); - - std::vector& oslices = po.m_model_slices; - - // If everything went well this code should not run at all, but - // let's be robust... - // assert(levelids.size() == oslices.size()); - if(po.m_level_ids.size() < oslices.size()) { // extend the levels until... - - BOOST_LOG_TRIVIAL(warning) - << "Height level mismatch at rasterization!\n"; - - LevelID lastlvl = po.m_level_ids.back(); - while(po.m_level_ids.size() < oslices.size()) { - lastlvl += slh; - po.m_level_ids.emplace_back(lastlvl); - } - } - - for(size_t i = 0; i < oslices.size(); ++i) { - LevelID h = po.m_level_ids[i]; - - float fh = float(double(h) * SCALING_FACTOR); - - // now for the public slice index: - SLAPrintObject::SliceRecord& sr = po.m_slice_index[fh]; - // There should be only one slice layer for each print object - assert(sr.model_slices_idx == SLAPrintObject::SliceRecord::NONE); - sr.model_slices_idx = i; - } - - if(po.m_supportdata) { // deal with the support slices if present - std::vector& sslices = po.m_supportdata->support_slices; - po.m_supportdata->level_ids.clear(); - po.m_supportdata->level_ids.reserve(sslices.size()); - - for(int i = 0; i < int(sslices.size()); ++i) { - LevelID h = sminZ + sih + i * slh; - po.m_supportdata->level_ids.emplace_back(h); - - float fh = float(double(h) * SCALING_FACTOR); - - SLAPrintObject::SliceRecord& sr = po.m_slice_index[fh]; - assert(sr.support_slices_idx == SLAPrintObject::SliceRecord::NONE); - sr.support_slices_idx = SLAPrintObject::SliceRecord::Idx(i); - } - } + auto index_slices = [this/*, ilhd*/](SLAPrintObject& /*po*/) { + // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update status to the 3D preview to load the SLA slices. + report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW); }; // Rasterizing the model objects, and their supports @@ -901,30 +903,18 @@ void SLAPrint::process() m_printer_input.clear(); for(SLAPrintObject * o : m_objects) { - auto& po = *o; - std::vector& oslices = po.m_model_slices; + LevelID gndlvl = o->get_slice_index().front().key(); + for(auto& slicerecord : o->get_slice_index()) { + auto& lyrs = m_printer_input[slicerecord.key() - gndlvl]; - // We need to adjust the min Z level of the slices to be zero - LevelID smfirst = - po.m_supportdata && !po.m_supportdata->level_ids.empty() ? - po.m_supportdata->level_ids.front() : 0; - LevelID mfirst = po.m_level_ids.empty()? 0 : po.m_level_ids.front(); - LevelID gndlvl = -(std::min(smfirst, mfirst)); + const ExPolygons& objslices = o->get_slices_from_record(slicerecord, soModel); + const ExPolygons& supslices = o->get_slices_from_record(slicerecord, soSupport); - // now merge this object's support and object slices with the rest - // of the print object slices + if(!objslices.empty()) + lyrs.emplace_back(objslices, o->instances()); - for(size_t i = 0; i < oslices.size(); ++i) { - auto& lyrs = m_printer_input[gndlvl + po.m_level_ids[i]]; - lyrs.emplace_back(oslices[i], po.m_instances); - } - - if(!po.m_supportdata) continue; - std::vector& sslices = po.m_supportdata->support_slices; - for(size_t i = 0; i < sslices.size(); ++i) { - LayerRefs& lyrs = - m_printer_input[gndlvl + po.m_supportdata->level_ids[i]]; - lyrs.emplace_back(sslices[i], po.m_instances); + if(!supslices.empty()) + lyrs.emplace_back(supslices, o->instances()); } } @@ -1227,13 +1217,13 @@ void SLAPrint::fill_statistics() // find highest object // Which is a better bet? To compare by max_z or by number of layers in the index? - double max_z = 0.; + float max_z = 0.; size_t max_layers_cnt = 0; size_t highest_obj_idx = 0; for (SLAPrintObject *&po : m_objects) { const SLAPrintObject::SliceIndex& slice_index = po->get_slice_index(); if (! slice_index.empty()) { - double z = (-- slice_index.end())->first; + float z = (-- slice_index.end())->slice_level(); size_t cnt = slice_index.size(); //if (z > max_z) { if (cnt > max_layers_cnt) { @@ -1253,7 +1243,7 @@ void SLAPrint::fill_statistics() int sliced_layer_cnt = 0; for (const auto& layer : highest_obj_slice_index) { - const double l_height = (layer.first == highest_obj_slice_index.begin()->first) ? init_layer_height : layer_height; + const double l_height = (layer.key() == highest_obj_slice_index.begin()->key()) ? init_layer_height : layer_height; // Calculation of the consumed material @@ -1262,21 +1252,22 @@ void SLAPrint::fill_statistics() for (SLAPrintObject * po : m_objects) { - const SLAPrintObject::SliceRecord *record = nullptr; + const SLAPrintObject::_SliceRecord *record = nullptr; { const SLAPrintObject::SliceIndex& index = po->get_slice_index(); - auto key = layer.first; - const SLAPrintObject::SliceIndex::const_iterator it_key = index.lower_bound(key - float(EPSILON)); - if (it_key == index.end() || it_key->first > key + EPSILON) + auto it = po->search_slice_index(layer.slice_level() - float(EPSILON)); + if (it == index.end() || it->slice_level() > layer.slice_level() + float(EPSILON)) continue; - record = &it_key->second; + record = &(*it); } - if (record->model_slices_idx != SLAPrintObject::SliceRecord::NONE) - append(model_polygons, get_all_polygons(po->get_model_slices()[record->model_slices_idx], po->instances())); - - if (record->support_slices_idx != SLAPrintObject::SliceRecord::NONE) - append(supports_polygons, get_all_polygons(po->get_support_slices()[record->support_slices_idx], po->instances())); + const ExPolygons &modelslices = po->get_slices_from_record(*record, soModel); + if (!modelslices.empty()) + append(model_polygons, get_all_polygons(modelslices, po->instances())); + + const ExPolygons &supportslices = po->get_slices_from_record(*record, soSupport); + if (!supportslices.empty()) + append(supports_polygons, get_all_polygons(supportslices, po->instances())); } model_polygons = union_(model_polygons); @@ -1372,11 +1363,15 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector EMPTY_SLICES; const TriangleMesh EMPTY_MESH; +const ExPolygons EMPTY_SLICE; } const std::vector& SLAPrintObject::get_support_points() const @@ -1486,6 +1477,72 @@ const std::vector& SLAPrintObject::get_support_points() const return m_supportdata->support_points; } +SLAPrintObject::SliceIndex::iterator +SLAPrintObject::search_slice_index(float slice_level) +{ + _SliceRecord query(0, slice_level, 0); + auto it = std::lower_bound(m_slice_index.begin(), m_slice_index.end(), + query, + [](const _SliceRecord& r1, const _SliceRecord& r2) + { + return r1.slice_level() < r2.slice_level(); + }); + + return it; +} + +SLAPrintObject::SliceIndex::const_iterator +SLAPrintObject::search_slice_index(float slice_level) const +{ + _SliceRecord query(0, slice_level, 0); + auto it = std::lower_bound(m_slice_index.cbegin(), m_slice_index.cend(), + query, + [](const _SliceRecord& r1, const _SliceRecord& r2) + { + return r1.slice_level() < r2.slice_level(); + }); + + return it; +} + +SLAPrintObject::SliceIndex::iterator +SLAPrintObject::search_slice_index(SLAPrintObject::_SliceRecord::Key key, + bool exact) +{ + _SliceRecord query(key, 0.f, 0.f); + auto it = std::lower_bound(m_slice_index.begin(), m_slice_index.end(), + query, + [](const _SliceRecord& r1, const _SliceRecord& r2) + { + return r1.key() < r2.key(); + }); + + // Return valid iterator only if the keys really match + if(exact && it != m_slice_index.end() && it->key() != key) + it = m_slice_index.end(); + + return it; +} + +SLAPrintObject::SliceIndex::const_iterator +SLAPrintObject::search_slice_index(SLAPrintObject::_SliceRecord::Key key, + bool exact) const +{ + _SliceRecord query(key, 0.f, 0.f); + auto it = std::lower_bound(m_slice_index.cbegin(), m_slice_index.cend(), + query, + [](const _SliceRecord& r1, const _SliceRecord& r2) + { + return r1.key() < r2.key(); + }); + + // Return valid iterator only if the keys really match + if(exact && it != m_slice_index.end() && it->key() != key) + it = m_slice_index.end(); + + return it; +} + const std::vector &SLAPrintObject::get_support_slices() const { // assert(is_step_done(slaposSliceSupports)); @@ -1493,7 +1550,30 @@ const std::vector &SLAPrintObject::get_support_slices() const return m_supportdata->support_slices; } -const SLAPrintObject::SliceIndex &SLAPrintObject::get_slice_index() const +const ExPolygons &SLAPrintObject::get_slices_from_record( + const _SliceRecord &rec, + SliceOrigin o) const +{ + size_t idx = o == soModel ? rec.get_model_slice_idx() : + rec.get_support_slice_idx(); + + const std::vector& v = o == soModel? get_model_slices() : + get_support_slices(); + + if(idx >= v.size()) return EMPTY_SLICE; + + return idx >= v.size() ? EMPTY_SLICE : v[idx]; +} + +const ExPolygons &SLAPrintObject::get_slices_from_record( + SLAPrintObject::SliceRecordConstIterator it, SliceOrigin o) const +{ + if(it.is_end()) return EMPTY_SLICE; + return get_slices_from_record(*it, o); +} + +const std::vector& +SLAPrintObject::get_slice_index() const { // assert(is_step_done(slaposIndexSlices)); return m_slice_index; diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 4ac17f7b5..fbe2821b2 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -6,6 +6,7 @@ #include "PrintExport.hpp" #include "Point.hpp" #include "MTUtils.hpp" +#include namespace Slic3r { @@ -35,12 +36,19 @@ using _SLAPrintObjectBase = // the printer (rasterizer) in the SLAPrint class. using LevelID = long long; +enum SliceOrigin { soSupport, soModel }; + class SLAPrintObject : public _SLAPrintObjectBase { private: // Prevents erroneous use by other classes. using Inherited = _SLAPrintObjectBase; public: + + // I refuse to grantee copying (Tamas) + SLAPrintObject(const SLAPrintObject&) = delete; + SLAPrintObject& operator=(const SLAPrintObject&) = delete; + const SLAPrintObjectConfig& config() const { return m_config; } const Transform3d& trafo() const { return m_trafo; } @@ -82,40 +90,146 @@ public: // pad is not, then without the pad, otherwise the full value is returned. double get_current_elevation() const; - // These two methods should be callable on the client side (e.g. UI thread) - // when the appropriate steps slaposObjectSlice and slaposSliceSupports - // are ready. All the print objects are processed before slapsRasterize so - // it is safe to call them during and/or after slapsRasterize. - const std::vector& get_model_slices() const; - const std::vector& get_support_slices() const; - // This method returns the support points of this SLAPrintObject. const std::vector& get_support_points() const; + // The public Slice record structure. It corresponds to one printable layer. + // To get the sliced polygons, use SLAPrintObject::get_slices_from_record + class SliceRecord { + public: + using Key = LevelID; + + private: + Key m_print_z = 0; // Top of the layer + float m_slice_z = 0.f; // Exact level of the slice + float m_height = 0.f; // Height of the sliced layer + + protected: + SliceRecord(Key key, float slicez, float height): + m_print_z(key), m_slice_z(slicez), m_height(height) {} + + public: + + // The key will be the integer height level of the top of the layer. + inline Key key() const { return m_print_z; } + + // Returns the exact floating point Z coordinate of the slice + inline float slice_level() const { return m_slice_z; } + + // Returns the current layer height + inline float layer_height() const { return m_height; } + }; + +private: + // An index record referencing the slices // (get_model_slices(), get_support_slices()) where the keys are the height // levels of the model in scaled-clipper coordinates. The levels correspond // to the z coordinate of the object coordinate system. - struct SliceRecord { - using Key = float; + class _SliceRecord: public SliceRecord { + public: + static const size_t NONE = size_t(-1); // this will be the max limit of size_t + private: + size_t m_model_slices_idx = NONE; + size_t m_support_slices_idx = NONE; - using Idx = size_t; - static const Idx NONE = Idx(-1); // this will be the max limit of size_t + public: + _SliceRecord(Key key, float slicez, float height): + SliceRecord(key, slicez, height) {} - Idx model_slices_idx = NONE; - Idx support_slices_idx = NONE; + // Methods for setting the indices into the slice vectors. + void set_model_slice_idx(size_t id) { m_model_slices_idx = id; } + void set_support_slice_idx(size_t id) { m_support_slices_idx = id; } + + inline size_t get_model_slice_idx() const { return m_model_slices_idx; } + inline size_t get_support_slice_idx() const { return m_support_slices_idx; } }; - using SliceIndex = std::map; + // Slice index will be a plain vector sorted by the integer height levels + using SliceIndex = std::vector<_SliceRecord>; // Retrieve the slice index which is readable only after slaposIndexSlices // is done. const SliceIndex& get_slice_index() const; - // I refuse to grantee copying (Tamas) - SLAPrintObject(const SLAPrintObject&) = delete; - SLAPrintObject& operator=(const SLAPrintObject&) = delete; + // Search slice index for the closest slice to the given level + SliceIndex::iterator search_slice_index(float slice_level); + SliceIndex::const_iterator search_slice_index(float slice_level) const; + // Search the slice index for a particular level in integer coordinates. + // If no such layer is present, it will return m_slice_index.end() + // This behavior can be suppressed by the second parameter. If it is true + // the method will return the closest (non-equal) record + SliceIndex::iterator search_slice_index(_SliceRecord::Key key, bool exact = false); + SliceIndex::const_iterator search_slice_index(_SliceRecord::Key key, bool = false) const; + + const std::vector& get_model_slices() const; + const std::vector& get_support_slices() const; + +public: + + // Should work as a polymorphic bidirectional iterator to the slice records + using SliceRecordConstIterator = + IndexBasedIterator; + + // ///////////////////////////////////////////////////////////////////////// + // + // These two methods should be callable on the client side (e.g. UI thread) + // when the appropriate steps slaposObjectSlice and slaposSliceSupports + // are ready. All the print objects are processed before slapsRasterize so + // it is safe to call them during and/or after slapsRasterize. + // + // ///////////////////////////////////////////////////////////////////////// + + // Get the slice records from a range of slice levels (inclusive). Floating + // point keys are the levels where the model was sliced with the mesh + // slicer. Integral keys are the keys of the slice records, which + // correspond to the top of each layer.. The end() method of the returned + // range points *after* the last valid element. This is for being + // consistent with std and makeing range based for loops work. use + // std::prev(range.end()) or --range.end() to get the last element. + template Range + get_slice_records(Key from, Key to = std::numeric_limits::max()) const + { + SliceIndex::const_iterator it_from, it_to; + if(std::is_integral::value) { + it_from = search_slice_index(SliceRecord::Key(from)); + it_to = search_slice_index(SliceRecord::Key(to)); + } else if(std::is_floating_point::value) { + it_from = search_slice_index(float(from)); + it_to = search_slice_index(float(to)); + } else return { + SliceRecordConstIterator(m_slice_index, _SliceRecord::NONE ), + SliceRecordConstIterator(m_slice_index, _SliceRecord::NONE ), + }; + + auto start = m_slice_index.begin(); + + size_t bidx = it_from == m_slice_index.end() ? _SliceRecord::NONE : + size_t(it_from - start); + + size_t eidx = it_to == m_slice_index.end() ? _SliceRecord::NONE : + size_t(it_to - start) + 1; + + return { + SliceRecordConstIterator(m_slice_index, bidx), + SliceRecordConstIterator(m_slice_index, eidx), + }; + } + + // Get all the slice records as a range. + inline Range get_slice_records() const { + return { + SliceRecordConstIterator(m_slice_index, 0), + SliceRecordConstIterator(m_slice_index, m_slice_index.size()) + }; + } + + const ExPolygons& get_slices_from_record(SliceRecordConstIterator it, + SliceOrigin o) const; + + const ExPolygons& get_slices_from_record(const _SliceRecord& rec, + SliceOrigin o) const; protected: // to be called from SLAPrint only. friend class SLAPrint; @@ -145,8 +259,10 @@ protected: private: // Object specific configuration, pulled from the configuration layer. SLAPrintObjectConfig m_config; + // Translation in Z + Rotation by Y and Z + Scaling / Mirroring. Transform3d m_trafo = Transform3d::Identity(); + std::vector m_instances; // Individual 2d slice polygons from lower z to higher z levels @@ -154,11 +270,9 @@ private: // Exact (float) height levels mapped to the slices. Each record contains // the index to the model and the support slice vectors. - SliceIndex m_slice_index; + std::vector<_SliceRecord> m_slice_index; - // The height levels corrected and scaled up in integer values. This will - // be used at rasterization. - std::vector m_level_ids; + std::vector m_model_height_levels; // Caching the transformed (m_trafo) raw mesh of the object mutable CachedObject m_transformed_rmesh; @@ -236,10 +350,17 @@ public: } const PrintObjects& objects() const { return m_objects; } + const SLAPrintConfig& print_config() const { return m_print_config; } + const SLAPrinterConfig& printer_config() const { return m_printer_config; } + const SLAMaterialConfig& material_config() const { return m_material_config; } + + std::string output_filename() const override; const SLAPrintStatistics& print_statistics() const { return m_print_statistics; } + std::string validate() const override; + private: using SLAPrinter = FilePrinter; using SLAPrinterPtr = std::unique_ptr; @@ -270,8 +391,6 @@ private: lref(std::cref(lyr)), copies(std::cref(cp)) {} }; - std::vector calculate_heights(const BoundingBoxf3& bb, float elevation, float initial_layer_height, float layer_height) const; - // One level may contain multiple slices from multiple objects and their // supports using LayerRefs = std::vector; diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 7712c0efb..21d42b4f2 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -150,6 +150,7 @@ SlicingParameters SlicingParameters::create_from_config( params.object_print_z_max += print_z; } + params.valid = true; return params; } diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index cd38193cc..7a7b7f84c 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -42,6 +42,8 @@ struct SlicingParameters // 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; } + bool valid; + // Number of raft layers. size_t base_raft_layers; // Number of interface layers including the contact layer. @@ -101,6 +103,8 @@ static_assert(IsTriviallyCopyable::value, "SlicingParameters // 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) { + assert(sp1.valid); + assert(sp2.valid); 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 && diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index d556e586b..bbb0354a9 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -2864,7 +2864,7 @@ void modulate_extrusion_by_overlapping_layers( 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 - pt_current).squaredNorm(); + //const double d2 = (pt_end - pt_current).cast.squaredNorm(); //assert(d2 < coordf_t(search_radius * search_radius)); // End of the path. break; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 755ca5ffa..ea3d87888 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -20,9 +20,8 @@ // Disable synchronization of unselected instances #define DISABLE_INSTANCES_SYNCH (0 && ENABLE_1_42_0_ALPHA1) -// Scene's GUI made using imgui library -#define ENABLE_IMGUI (1 && ENABLE_1_42_0_ALPHA1) -#define DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI (1 && ENABLE_IMGUI) +// Disable imgui dialog for move, rotate and scale gizmos +#define DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI (1 && ENABLE_1_42_0_ALPHA1) // Use wxDataViewRender instead of wxDataViewCustomRenderer #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1) @@ -34,23 +33,12 @@ // Changed algorithm to extract euler angles from rotation matrix #define ENABLE_NEW_EULER_ANGLES (1 && ENABLE_1_42_0_ALPHA4) -// Added minimum threshold for click and drag movements -#define ENABLE_MOVE_MIN_THRESHOLD (1 && ENABLE_1_42_0_ALPHA4) // Modified initial default placement of generic subparts #define ENABLE_GENERIC_SUBPARTS_PLACEMENT (1 && ENABLE_1_42_0_ALPHA4) // Bunch of fixes related to volumes centering #define ENABLE_VOLUMES_CENTERING_FIXES (1 && ENABLE_1_42_0_ALPHA4) -//==================== -// 1.42.0.alpha5 techs -//==================== -#define ENABLE_1_42_0_ALPHA5 1 - -// Toolbar items hidden/shown in dependence of the user mode -#define ENABLE_MODE_AWARE_TOOLBAR_ITEMS (1 && ENABLE_1_42_0_ALPHA5) - - //==================== // 1.42.0.alpha7 techs //==================== @@ -59,4 +47,14 @@ // Printbed textures generated from svg files #define ENABLE_TEXTURES_FROM_SVG (1 && ENABLE_1_42_0_ALPHA7) + +//==================== +// 1.42.0.alpha8 techs +//==================== +#define ENABLE_1_42_0_ALPHA8 1 + +// Toolbars and Gizmos use icons imported from svg files +#define ENABLE_SVG_ICONS (1 && ENABLE_1_42_0_ALPHA8 && ENABLE_TEXTURES_FROM_SVG) + + #endif // _technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index c5ffe8614..d4719125d 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -247,7 +247,7 @@ bool TriangleMesh::needed_repair() const || this->stl.stats.backwards_edges > 0; } -void TriangleMesh::WriteOBJFile(char* output_file) +void TriangleMesh::WriteOBJFile(const char* output_file) { stl_generate_shared_vertices(&stl); stl_write_obj(&stl, output_file); @@ -619,10 +619,11 @@ void TriangleMesh::require_shared_vertices() const int *vertices = stl.v_indices[facet_idx].vertex; for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) { int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx]; - //if (nbr_face != -1) { - // assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3]); - // assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]); - //} + if (nbr_face != -1) { + assert( + (stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3] && stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]) || + (stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[(nbr_idx + 1) % 3] && stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[nbr_idx])); + } } } #endif /* _DEBUG */ @@ -1498,9 +1499,16 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // Try to close gaps. // Do it in two rounds, first try to connect in the same direction only, // then try to connect the open polylines in reversed order as well. +#if 0 + for (double max_gap : { EPSILON, 0.001, 0.1, 1., 2. }) { + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); + } +#else const double max_gap = 2.; //mm chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); +#endif #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 736706cb2..6a68e0796 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -36,7 +36,7 @@ public: float volume(); void check_topology(); bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; } - void WriteOBJFile(char* output_file); + void WriteOBJFile(const char* output_file); void scale(float factor); void scale(const Vec3d &versor); void translate(float x, float y, float z); diff --git a/src/slic3r.cpp b/src/slic3r.cpp index 9a124c73c..3046e3b79 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -31,22 +31,456 @@ #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/3mf.hpp" +#include "libslic3r/Format/STL.hpp" +#include "libslic3r/Format/OBJ.hpp" #include "libslic3r/Utils.hpp" +#include "slic3r.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" using namespace Slic3r; -/// utility function for displaying CLI usage -void printUsage(); +PrinterTechnology get_printer_technology(const DynamicConfig &config) +{ + const ConfigOptionEnum *opt = config.option>("printer_technology"); + return (opt == nullptr) ? ptUnknown : opt->value; +} -#ifdef _MSC_VER -int slic3r_main_(int argc, char **argv) +int CLI::run(int argc, char **argv) +{ + if (! this->setup(argc, argv)) + return 1; + + m_extra_config.apply(m_config, true); + m_extra_config.normalize(); + + bool start_gui = m_actions.empty() && + // cutting transformations are setting an "export" action. + std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && + std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && + std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); + PrinterTechnology printer_technology = get_printer_technology(m_extra_config); + const std::vector &load_configs = m_config.option("load", true)->values; + + // load config files supplied via --load + for (auto const &file : load_configs) { + if (! boost::filesystem::exists(file)) { + if (m_config.opt_bool("ignore_nonexistent_config")) { + continue; + } else { + boost::nowide::cerr << "No such file: " << file << std::endl; + return 1; + } + } + DynamicPrintConfig config; + try { + config.load(file); + } catch (std::exception &ex) { + boost::nowide::cerr << "Error while reading config file: " << ex.what() << std::endl; + return 1; + } + config.normalize(); + PrinterTechnology other_printer_technology = get_printer_technology(config); + if (printer_technology == ptUnknown) { + printer_technology = other_printer_technology; + } else if (printer_technology != other_printer_technology) { + boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; + return 1; + } + m_print_config.apply(config); + } + + // Read input file(s) if any. + for (const std::string &file : m_input_files) { + if (! boost::filesystem::exists(file)) { + boost::nowide::cerr << "No such file: " << file << std::endl; + exit(1); + } + Model model; + try { + // When loading an AMF or 3MF, config is imported as well, including the printer technology. + model = Model::read_from_file(file, &m_print_config, true); + PrinterTechnology other_printer_technology = get_printer_technology(m_print_config); + if (printer_technology == ptUnknown) { + printer_technology = other_printer_technology; + } else if (printer_technology != other_printer_technology) { + boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; + return 1; + } + } catch (std::exception &e) { + boost::nowide::cerr << file << ": " << e.what() << std::endl; + return 1; + } + if (model.objects.empty()) { + boost::nowide::cerr << "Error: file is empty: " << file << std::endl; + continue; + } + m_models.push_back(model); + } + + // Apply command line options to a more specific DynamicPrintConfig which provides normalize() + // (command line options override --load files) + m_print_config.apply(m_extra_config, true); + // Normalizing after importing the 3MFs / AMFs + m_print_config.normalize(); + + if (printer_technology == ptUnknown) + printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA; + + // Initialize full print configs for both the FFF and SLA technologies. + FullPrintConfig fff_print_config; +// SLAFullPrintConfig sla_print_config; + fff_print_config.apply(m_print_config, true); +// sla_print_config.apply(m_print_config, true); + + // Loop through transform options. + for (auto const &opt_key : m_transforms) { + if (opt_key == "merge") { + Model m; + for (auto &model : m_models) + for (ModelObject *o : model.objects) + m.add_object(*o); + // Rearrange instances unless --dont-arrange is supplied + if (! m_config.opt_bool("dont_arrange")) { + m.add_default_instances(); + const BoundingBoxf &bb = fff_print_config.bed_shape.values; + m.arrange_objects( + fff_print_config.min_object_distance(), + // If we are going to use the merged model for printing, honor + // the configured print bed for arranging, otherwise do it freely. + this->has_print_action() ? &bb : nullptr + ); + } + m_models.clear(); + m_models.emplace_back(std::move(m)); + } else if (opt_key == "duplicate") { + const BoundingBoxf &bb = fff_print_config.bed_shape.values; + for (auto &model : m_models) { + const bool all_objects_have_instances = std::none_of( + model.objects.begin(), model.objects.end(), + [](ModelObject* o){ return o->instances.empty(); } + ); + if (all_objects_have_instances) { + // if all input objects have defined position(s) apply duplication to the whole model + model.duplicate(m_config.opt_int("duplicate"), fff_print_config.min_object_distance(), &bb); + } else { + model.add_default_instances(); + model.duplicate_objects(m_config.opt_int("duplicate"), fff_print_config.min_object_distance(), &bb); + } + } + } else if (opt_key == "duplicate_grid") { + std::vector &ints = m_config.option("duplicate_grid")->values; + const int x = ints.size() > 0 ? ints.at(0) : 1; + const int y = ints.size() > 1 ? ints.at(1) : 1; + const double distance = fff_print_config.duplicate_distance.value; + for (auto &model : m_models) + model.duplicate_objects_grid(x, y, (distance > 0) ? distance : 6); // TODO: this is not the right place for setting a default + } else if (opt_key == "center") { + for (auto &model : m_models) { + model.add_default_instances(); + // this affects instances: + model.center_instances_around_point(m_config.option("center")->value); + // this affects volumes: + //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? + //model.align_to_ground(); + BoundingBoxf3 bbox; + for (ModelObject *model_object : model.objects) + // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. + bbox.merge(model_object->instance_bounding_box(0, false)); + for (ModelObject *model_object : model.objects) + for (ModelInstance *model_instance : model_object->instances) + model_instance->set_offset(Z, model_instance->get_offset(Z) - bbox.min.z()); + } + } else if (opt_key == "align_xy") { + const Vec2d &p = m_config.option("align_xy")->value; + for (auto &model : m_models) { + BoundingBoxf3 bb = model.bounding_box(); + // this affects volumes: + model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z()); + } + } else if (opt_key == "dont_arrange") { + // do nothing - this option alters other transform options + } else if (opt_key == "rotate") { + for (auto &model : m_models) + for (auto &o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Z); + } else if (opt_key == "rotate_x") { + for (auto &model : m_models) + for (auto &o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), X); + } else if (opt_key == "rotate_y") { + for (auto &model : m_models) + for (auto &o : model.objects) + // this affects volumes: + o->rotate(Geometry::deg2rad(m_config.opt_float(opt_key)), Y); + } else if (opt_key == "scale") { + for (auto &model : m_models) + for (auto &o : model.objects) + // this affects volumes: + o->scale(m_config.get_abs_value(opt_key, 1)); + } else if (opt_key == "scale_to_fit") { + const Vec3d &opt = m_config.opt(opt_key)->value; + if (opt.x() <= 0 || opt.y() <= 0 || opt.z() <= 0) { + boost::nowide::cerr << "--scale-to-fit requires a positive volume" << std::endl; + return 1; + } + for (auto &model : m_models) + for (auto &o : model.objects) + // this affects volumes: + o->scale_to_fit(opt); + } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") { + std::vector new_models; + for (auto &model : m_models) { + model.repair(); + model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0 + size_t num_objects = model.objects.size(); + for (size_t i = 0; i < num_objects; ++ i) { + +#if 0 + if (opt_key == "cut_x") { + o->cut(X, m_config.opt_float("cut_x"), &out); + } else if (opt_key == "cut_y") { + o->cut(Y, m_config.opt_float("cut_y"), &out); + } else if (opt_key == "cut") { + o->cut(Z, m_config.opt_float("cut"), &out); + } #else -int main(int argc, char **argv) + model.objects.front()->cut(0, m_config.opt_float("cut"), true, true, true); #endif + model.delete_object(size_t(0)); + } + } + + // TODO: copy less stuff around using pointers + m_models = new_models; + + if (m_actions.empty()) + m_actions.push_back("export_stl"); + } +#if 0 + else if (opt_key == "cut_grid") { + std::vector new_models; + for (auto &model : m_models) { + TriangleMesh mesh = model.mesh(); + mesh.repair(); + + TriangleMeshPtrs meshes = mesh.cut_by_grid(m_config.option("cut_grid")->value); + size_t i = 0; + for (TriangleMesh* m : meshes) { + Model out; + auto o = out.add_object(); + o->add_volume(*m); + o->input_file += "_" + std::to_string(i++); + delete m; + } + } + + // TODO: copy less stuff around using pointers + m_models = new_models; + + if (m_actions.empty()) + m_actions.push_back("export_stl"); + } +#endif + else if (opt_key == "split") { + for (Model &model : m_models) { + size_t num_objects = model.objects.size(); + for (size_t i = 0; i < num_objects; ++ i) { + model.objects.front()->split(nullptr); + model.delete_object(size_t(0)); + } + } + } else if (opt_key == "repair") { + for (auto &model : m_models) + model.repair(); + } else { + boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; + return 1; + } + } + + // loop through action options + for (auto const &opt_key : m_actions) { + if (opt_key == "help") { + this->print_help(); + } else if (opt_key == "help_fff") { + this->print_help(true, ptFFF); + } else if (opt_key == "help_sla") { + this->print_help(true, ptSLA); + } else if (opt_key == "save") { + //FIXME check for mixing the FFF / SLA parameters. + // or better save fff_print_config vs. sla_print_config + m_print_config.save(m_config.opt_string("save")); + } else if (opt_key == "info") { + // --info works on unrepaired model + for (Model &model : m_models) { + model.add_default_instances(); + model.print_info(); + } + } else if (opt_key == "export_stl") { + for (auto &model : m_models) + model.add_default_instances(); + if (! this->export_models(IO::STL)) + return 1; + } else if (opt_key == "export_obj") { + for (auto &model : m_models) + model.add_default_instances(); + if (! this->export_models(IO::OBJ)) + return 1; + } else if (opt_key == "export_amf") { + if (! this->export_models(IO::AMF)) + return 1; + } else if (opt_key == "export_3mf") { + if (! this->export_models(IO::TMF)) + return 1; + } else if (opt_key == "export_gcode" || opt_key == "export_sla" || opt_key == "slice") { + if (opt_key == "export_gcode" && printer_technology == ptSLA) { + boost::nowide::cerr << "error: cannot export G-code for an FFF configuration" << std::endl; + return 1; + } else if (opt_key == "export_sla" && printer_technology == ptFFF) { + boost::nowide::cerr << "error: cannot export SLA slices for a SLA configuration" << std::endl; + return 1; + } + // Make a copy of the model if the current action is not the last action, as the model may be + // modified by the centering and such. + Model model_copy; + bool make_copy = &opt_key != &m_actions.back(); + for (Model &model_in : m_models) { + if (make_copy) + model_copy = model_in; + Model &model = make_copy ? model_copy : model_in; + // If all objects have defined instances, their relative positions will be + // honored when printing (they will be only centered, unless --dont-arrange + // is supplied); if any object has no instances, it will get a default one + // and all instances will be rearranged (unless --dont-arrange is supplied). + std::string outfile = m_config.opt_string("output"); + Print fff_print; + SLAPrint sla_print; + PrintBase *print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); + if (! m_config.opt_bool("dont_arrange")) { + //FIXME make the min_object_distance configurable. + model.arrange_objects(fff_print.config().min_object_distance()); + model.center_instances_around_point(m_config.option("center")->value); + } + if (printer_technology == ptFFF) { + for (auto* mo : model.objects) + fff_print.auto_assign_extruders(mo); + } + print->apply(model, m_print_config); + std::string err = print->validate(); + if (! err.empty()) { + boost::nowide::cerr << err << std::endl; + return 1; + } + if (print->empty()) + boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl; + else + try { + std::string outfile_final; + print->process(); + if (printer_technology == ptFFF) { + // The outfile is processed by a PlaceholderParser. + outfile = fff_print.export_gcode(outfile, nullptr); + outfile_final = fff_print.print_statistics().finalize_output_path(outfile); + } else { + outfile = sla_print.output_filepath(outfile); + //FIXME Tamas, please port it to miniz + // sla_print.export_raster(outfile); + outfile_final = sla_print.print_statistics().finalize_output_path(outfile); + } + if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final) != 0) { + boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; + return 1; + } + boost::nowide::cout << "Slicing result exported to " << outfile << std::endl; + } catch (const std::exception &ex) { + boost::nowide::cerr << ex.what() << std::endl; + return 1; + } +/* + print.center = ! m_config.has("center") + && ! m_config.has("align_xy") + && ! m_config.opt_bool("dont_arrange"); + print.set_model(model); + + // start chronometer + typedef std::chrono::high_resolution_clock clock_; + typedef std::chrono::duration > second_; + std::chrono::time_point t0{ clock_::now() }; + + const std::string outfile = this->output_filepath(model, IO::Gcode); + try { + print.export_gcode(outfile); + } catch (std::runtime_error &e) { + boost::nowide::cerr << e.what() << std::endl; + return 1; + } + boost::nowide::cout << "G-code exported to " << outfile << std::endl; + + // output some statistics + double duration { std::chrono::duration_cast(clock_::now() - t0).count() }; + boost::nowide::cout << std::fixed << std::setprecision(0) + << "Done. Process took " << (duration/60) << " minutes and " + << std::setprecision(3) + << std::fmod(duration, 60.0) << " seconds." << std::endl + << std::setprecision(2) + << "Filament required: " << print.total_used_filament() << "mm" + << " (" << print.total_extruded_volume()/1000 << "cm3)" << std::endl; +*/ + } + } else { + boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl; + return 1; + } + } + + if (start_gui) { +#if 1 +// #ifdef USE_WX + GUI::GUI_App *gui = new GUI::GUI_App(); +// gui->autosave = m_config.opt_string("autosave"); + GUI::GUI_App::SetInstance(gui); + gui->CallAfter([gui, this, &load_configs] { + if (!gui->initialized()) { + return; + } +#if 0 + // Load the cummulative config over the currently active profiles. + //FIXME if multiple configs are loaded, only the last one will have an effect. + // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). + // As of now only the full configs are supported here. + if (!m_print_config.empty()) + gui->mainframe->load_config(m_print_config); +#endif + if (! load_configs.empty()) + // Load the last config to give it a name at the UI. The name of the preset may be later + // changed by loading an AMF or 3MF. + //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. + gui->mainframe->load_config_file(load_configs.back()); + // If loading a 3MF file, the config is loaded from the last one. + if (! m_input_files.empty()) + gui->plater()->load_files(m_input_files, true, true); + if (! m_extra_config.empty()) + gui->mainframe->load_config(m_extra_config); + }); + return wxEntry(argc, argv); +#else + // No GUI support. Just print out a help. + this->print_help(false); + // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). + return (argc == 0) ? 0 : 1; +#endif + } + + return 0; +} + +bool CLI::setup(int argc, char **argv) { { const char *loglevel = boost::nowide::getenv("SLIC3R_LOGLEVEL"); @@ -58,15 +492,6 @@ int main(int argc, char **argv) } } - // parse all command line options into a DynamicConfig - DynamicPrintAndCLIConfig all_config; - t_config_option_keys input_files; - // if any option is unsupported, print usage and abort immediately - if (! all_config.read_cli(argc, argv, &input_files)) { - printUsage(); - return 0; - } - boost::filesystem::path path_to_binary = boost::filesystem::system_complete(argv[0]); // Path from the Slic3r binary to its resources. @@ -94,207 +519,114 @@ int main(int argc, char **argv) set_var_dir((path_resources / "icons").string()); set_local_dir((path_resources / "localization").string()); - // apply command line options to a more handy CLIConfig - CLIConfig cli_config; -#ifdef __APPLE__ - // Enable the GUI mode by default, to support drag & drop. - cli_config.gui.value = true; -#endif /* __APPLE__ */ + // Parse all command line options into a DynamicConfig. + // If any option is unsupported, print usage and abort immediately. + t_config_option_keys opt_order; + if (! m_config.read_cli(argc, argv, &m_input_files, &opt_order)) { + // Separate error message reported by the CLI parser from the help. + boost::nowide::cerr << std::endl; + this->print_help(); + return false; + } + // Parse actions and transform options. + for (auto const &opt_key : opt_order) { + if (cli_actions_config_def.has(opt_key)) + m_actions.emplace_back(opt_key); + else if (cli_transform_config_def.has(opt_key)) + m_transforms.emplace_back(opt_key); + } - cli_config.apply(all_config, true); - set_data_dir(cli_config.datadir.value); - - // Load the extra config values. - DynamicPrintConfig extra_config; - extra_config.apply(all_config, true); - - // load config files supplied via --load - DynamicPrintConfig print_config; - 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); + { + const ConfigOptionInt *opt_loglevel = m_config.opt("loglevel"); + if (opt_loglevel != 0) + set_logging_level(opt_loglevel->value); } - if ((input_files.empty() || cli_config.gui.value) && ! cli_config.no_gui.value && ! cli_config.help.value && cli_config.save.value.empty()) { -#if 1 - GUI::GUI_App *gui = new GUI::GUI_App(); - GUI::GUI_App::SetInstance(gui); - gui->CallAfter([gui, &input_files, &cli_config, &extra_config, &print_config] { - if (! gui->initialized()) { - return; - } -#if 0 - // Load the cummulative config over the currently active profiles. - //FIXME if multiple configs are loaded, only the last one will have an effect. - // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). - // As of now only the full configs are supported here. - if (! print_config.empty()) - gui->mainframe->load_config(print_config); -#endif - if (! cli_config.load.values.empty()) - // Load the last config to give it a name at the UI. The name of the preset may be later - // changed by loading an AMF or 3MF. - //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. - gui->mainframe->load_config_file(cli_config.load.values.back()); - // If loading a 3MF file, the config is loaded from the last one. - gui->plater()->load_files(input_files, true, true); - if (! extra_config.empty()) - gui->mainframe->load_config(extra_config); - }); - return wxEntry(argc, argv); -#else - std::cout << "GUI support has not been built." << "\n"; - return -1; -#endif - } + // Initialize with defaults. + for (const t_optiondef_map *options : { &cli_actions_config_def.options, &cli_transform_config_def.options, &cli_misc_config_def.options }) + for (const std::pair &optdef : *options) + m_config.optptr(optdef.first, true); - // apply command line options to a more specific DynamicPrintConfig which provides normalize() - // (command line options override --load files) - print_config.apply(extra_config, true); - - // write config if requested - if (! cli_config.save.value.empty()) { - print_config.normalize(); - print_config.save(cli_config.save.value); - } + set_data_dir(m_config.opt_string("datadir")); - if (cli_config.help) { - printUsage(); - return 0; - } - - // 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, &print_config, true); - } 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_3mf) { - std::string outfile = cli_config.output.value; - if (outfile.empty()) outfile = model.objects.front()->input_file; - // Check if the file is already a 3mf. - if(outfile.substr(outfile.find_last_of('.'), outfile.length()) == ".3mf") - outfile = outfile.substr(0, outfile.find_last_of('.')) + "_2" + ".3mf"; - else - // Remove the previous extension and add .3mf extention. - outfile = outfile.substr(0, outfile.find_last_of('.')) + ".3mf"; - store_3mf(outfile.c_str(), &model, nullptr); - boost::nowide::cout << "File file exported to " << outfile << std::endl; - } else if (cli_config.cut > 0) { - model.repair(); - model.translate(0, 0, - model.bounding_box().min(2)); - if (! model.objects.empty()) { - // XXX - // Model out; - // model.objects.front()->cut(cli_config.cut, &out); - // ModelObject &upper = *out.objects[0]; - // ModelObject &lower = *out.objects[1]; - // // Use the input name and trim off the extension. - // std::string outfile = cli_config.output.value; - // if (outfile.empty()) - // outfile = model.objects.front()->input_file; - // outfile = outfile.substr(0, outfile.find_last_of('.')); - // std::cerr << outfile << "\n"; - // if (upper.facets_count() > 0) - // upper.mesh().write_binary((outfile + "_upper.stl").c_str()); - // if (lower.facets_count() > 0) - // lower.mesh().write_binary((outfile + "_lower.stl").c_str()); - } - } else if (cli_config.slice) { - PrinterTechnology printer_technology = print_config.option>("printer_technology", true)->value; - std::string outfile = cli_config.output.value; - Print fff_print; - SLAPrint sla_print; - PrintBase *print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); - if (! cli_config.dont_arrange) { - //FIXME make the min_object_distance configurable. - model.arrange_objects(fff_print.config().min_object_distance()); - model.center_instances_around_point(cli_config.print_center); - } - if (printer_technology == ptFFF) { - for (auto* mo : model.objects) - fff_print.auto_assign_extruders(mo); - } - print_config.normalize(); - print->apply(model, print_config); - std::string err = print->validate(); - if (err.empty()) { - if (printer_technology == ptFFF) { - // The outfile is processed by a PlaceholderParser. - fff_print.export_gcode(outfile, nullptr); - } else { - assert(printer_technology == ptSLA); - //FIXME add the output here - } - } else - std::cerr << err << "\n"; - } else { - boost::nowide::cerr << "error: command not supported" << std::endl; - return 1; - } - } - - return 0; + return true; } -void printUsage() +void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const { - std::cout << "Slic3r " << SLIC3R_VERSION << " is a STL-to-GCODE translator for RepRap 3D printers" << "\n" - << "written by Alessandro Ranellucci - http://slic3r.org/ - https://github.com/slic3r/Slic3r" << "\n" -// << "Git Version " << BUILD_COMMIT << "\n\n" - << "Usage: ./slic3r [ OPTIONS ] [ file.stl ] [ file2.stl ] ..." << "\n"; - // CLI Options - std::cout << "** CLI OPTIONS **\n"; - print_cli_options(boost::nowide::cout); - std::cout << "****\n"; - // Print options - std::cout << "** PRINT OPTIONS **\n"; - print_print_options(boost::nowide::cout); - std::cout << "****\n"; + boost::nowide::cout + << "Slic3r Prusa Edition " << SLIC3R_BUILD << std::endl + << "https://github.com/prusa3d/Slic3r" << std::endl << std::endl + << "Usage: slic3r [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl + << std::endl + << "Actions:" << std::endl; + cli_actions_config_def.print_cli_help(boost::nowide::cout, false); + + boost::nowide::cout + << std::endl + << "Transform options:" << std::endl; + cli_transform_config_def.print_cli_help(boost::nowide::cout, false); + + boost::nowide::cout + << std::endl + << "Other options:" << std::endl; + cli_misc_config_def.print_cli_help(boost::nowide::cout, false); + + if (include_print_options) { + boost::nowide::cout << std::endl; + print_config_def.print_cli_help(boost::nowide::cout, true, [printer_technology](const ConfigOptionDef &def) + { return (printer_technology & def.printer_technology) != 0; }); + } else { + boost::nowide::cout + << std::endl + << "Run --help-fff / --help-sla to see the full listing of print options." << std::endl; + } +} + +bool CLI::export_models(IO::ExportFormat format) +{ + for (Model &model : m_models) { + const std::string path = this->output_filepath(model, format); + bool success = false; + switch (format) { + case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr); break; + case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break; + case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break; + case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr); break; + default: assert(false); break; + } + if (success) + std::cout << "File exported to " << path << std::endl; + else { + std::cerr << "File export to " << path << " failed" << std::endl; + return false; + } + } + return true; +} + +std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) const +{ + std::string ext; + switch (format) { + case IO::AMF: ext = ".amf"; break; + case IO::OBJ: ext = ".obj"; break; + case IO::STL: ext = ".stl"; break; + case IO::TMF: ext = ".3mf"; break; + default: assert(false); break; + }; + auto proposed_path = boost::filesystem::path(model.propose_export_file_name_and_path(ext)); + // use --output when available + std::string cmdline_param = m_config.opt_string("output"); + if (! cmdline_param.empty()) { + // if we were supplied a directory, use it and append our automatically generated filename + boost::filesystem::path cmdline_path(cmdline_param); + if (boost::filesystem::is_directory(cmdline_path)) + proposed_path = cmdline_path / proposed_path.filename(); + else + proposed_path = cmdline_path; + } + return proposed_path.string(); } #ifdef _MSC_VER @@ -309,7 +641,12 @@ extern "C" { for (size_t i = 0; i < argc; ++ i) argv_ptrs[i] = const_cast(argv_narrow[i].data()); // Call the UTF8 main. - return slic3r_main_(argc, argv_ptrs.data()); + return CLI().run(argc, argv_ptrs.data()); } } +#else /* _MSC_VER */ +int main(int argc, char **argv) +{ + return CLI().run(argc, argv); +} #endif /* _MSC_VER */ diff --git a/src/slic3r.hpp b/src/slic3r.hpp new file mode 100644 index 000000000..a775e7b9d --- /dev/null +++ b/src/slic3r.hpp @@ -0,0 +1,48 @@ +#ifndef SLIC3R_HPP +#define SLIC3R_HPP + +#include "libslic3r/Config.hpp" +#include "libslic3r/Model.hpp" + +namespace Slic3r { + +namespace IO { + enum ExportFormat : int { + AMF, + OBJ, + STL, + // SVG, + TMF, + Gcode + }; +} + +class CLI { +public: + int run(int argc, char **argv); + +private: + DynamicPrintAndCLIConfig m_config; + DynamicPrintConfig m_print_config; + DynamicPrintConfig m_extra_config; + std::vector m_input_files; + std::vector m_actions; + std::vector m_transforms; + std::vector m_models; + + bool setup(int argc, char **argv); + + /// Prints usage of the CLI. + void print_help(bool include_print_options = false, PrinterTechnology printer_technology = ptFFF | ptSLA | ptSLS) const; + + /// Exports loaded models to a file of the specified format, according to the options affecting output filename. + bool export_models(IO::ExportFormat format); + + bool has_print_action() const { return m_config.opt_bool("export_gcode") || m_config.opt_bool("export_sla"); } + + std::string output_filepath(const Model &model, IO::ExportFormat format) const; +}; + +} + +#endif diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 157fc9011..ef60ab3fa 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -28,8 +28,22 @@ set(SLIC3R_GUI_SOURCES GUI/GLCanvas3D.cpp GUI/GLCanvas3DManager.hpp GUI/GLCanvas3DManager.cpp - GUI/GLGizmo.hpp - GUI/GLGizmo.cpp + GUI/Selection.hpp + GUI/Selection.cpp + GUI/Gizmos/GLGizmoBase.cpp + GUI/Gizmos/GLGizmoBase.hpp + GUI/Gizmos/GLGizmoMove.cpp + GUI/Gizmos/GLGizmoMove.hpp + GUI/Gizmos/GLGizmoRotate.cpp + GUI/Gizmos/GLGizmoRotate.hpp + GUI/Gizmos/GLGizmoScale.cpp + GUI/Gizmos/GLGizmoScale.hpp + GUI/Gizmos/GLGizmoSlaSupports.cpp + GUI/Gizmos/GLGizmoSlaSupports.hpp + GUI/Gizmos/GLGizmoFlatten.cpp + GUI/Gizmos/GLGizmoFlatten.hpp + GUI/Gizmos/GLGizmoCut.cpp + GUI/Gizmos/GLGizmoCut.hpp GUI/GLTexture.hpp GUI/GLTexture.cpp GUI/GLToolbar.hpp @@ -76,6 +90,8 @@ set(SLIC3R_GUI_SOURCES GUI/2DBed.hpp GUI/3DBed.cpp GUI/3DBed.hpp + GUI/Camera.cpp + GUI/Camera.hpp GUI/wxExtensions.cpp GUI/wxExtensions.hpp GUI/WipeTowerDialog.cpp diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 3bf54043b..328f0d8be 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -249,6 +249,7 @@ GLVolume::GLVolume(float r, float g, float b, float a) , is_wipe_tower(false) , is_extrusion_path(false) , force_transparent(false) + , force_native_color(false) , tverts_range(0, size_t(-1)) , qverts_range(0, size_t(-1)) { @@ -280,16 +281,20 @@ void GLVolume::set_render_color(const float* rgba, unsigned int size) void GLVolume::set_render_color() { - if (selected) - set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4); - else if (hover) - set_render_color(HOVER_COLOR, 4); - else if (disabled) - set_render_color(DISABLED_COLOR, 4); - else if (is_outside && shader_outside_printer_detection_enabled) - set_render_color(OUTSIDE_COLOR, 4); - else + if (force_native_color) set_render_color(color, 4); + else { + if (selected) + set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4); + else if (hover) + set_render_color(HOVER_COLOR, 4); + else if (disabled) + set_render_color(DISABLED_COLOR, 4); + else if (is_outside && shader_outside_printer_detection_enabled) + set_render_color(OUTSIDE_COLOR, 4); + else + set_render_color(color, 4); + } if (force_transparent) render_color[3] = color[3]; @@ -834,6 +839,8 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M ModelInstance::EPrintVolumeState state = ModelInstance::PVS_Inside; bool all_contained = true; + bool contained_min_one = false; + for (GLVolume* volume : this->volumes) { if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled)) @@ -843,6 +850,9 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M bool contained = print_volume.contains(bb); all_contained &= contained; + if (contained) + contained_min_one = true; + volume->is_outside = !contained; if ((state == ModelInstance::PVS_Inside) && volume->is_outside) @@ -855,7 +865,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M if (out_state != nullptr) *out_state = state; - return all_contained; + return /*all_contained*/ contained_min_one; // #ys_FIXME_delete_after_testing } void GLVolumeCollection::reset_outside_state() @@ -1998,9 +2008,9 @@ std::string _3DScene::get_gl_info(bool format_as_html, bool extensions) return s_canvas_mgr.get_gl_info(format_as_html, extensions); } -bool _3DScene::add_canvas(wxGLCanvas* canvas) +bool _3DScene::add_canvas(wxGLCanvas* canvas, GUI::Bed3D& bed, GUI::Camera& camera, GUI::GLToolbar& view_toolbar) { - return s_canvas_mgr.add(canvas); + return s_canvas_mgr.add(canvas, bed, camera, view_toolbar); } bool _3DScene::remove_canvas(wxGLCanvas* canvas) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 805383ef4..00f0b9994 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -25,6 +25,11 @@ inline void glAssertRecentCall() { } #endif namespace Slic3r { +namespace GUI { +class Bed3D; +struct Camera; +class GLToolbar; +} // namespace GUI class Print; class PrintObject; @@ -298,6 +303,8 @@ public: bool is_extrusion_path; // Wheter or not to always render this volume using its own alpha bool force_transparent; + // Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE) + bool force_native_color; // Interleaved triangles & normals with indexed triangles & quads. GLIndexedVertexArray indexed_vertex_array; @@ -360,7 +367,7 @@ public: void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } - Vec3d get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } + const Vec3d& get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); } void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } @@ -564,7 +571,7 @@ class _3DScene public: static std::string get_gl_info(bool format_as_html, bool extensions); - static bool add_canvas(wxGLCanvas* canvas); + static bool add_canvas(wxGLCanvas* canvas, GUI::Bed3D& bed, GUI::Camera& camera, GUI::GLToolbar& view_toolbar); static bool remove_canvas(wxGLCanvas* canvas); static void remove_all_canvases(); diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 6b1795b66..8ff74f326 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -92,8 +92,8 @@ AboutDialog::AboutDialog() "" "" "" - "Copyright © 2018 Durand Rémi.
" - "Copyright © 2016-2018 Prusa Research.
" + "Copyright © 2018-2019 Durand Rémi.
" + "Copyright © 2016-2019 Prusa Research.
" "Copyright © 2011-2017 Alessandro Ranellucci.
" "Slic3r is licensed under the " "GNU Affero General Public License, version 3." diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index b62825102..62cced4c8 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -53,6 +53,7 @@ bool BackgroundSlicingProcess::select_technology(PrinterTechnology tech) switch (tech) { case ptFFF: m_print = m_fff_print; break; case ptSLA: m_print = m_sla_print; break; + case ptSLS: m_print = m_fff_print; break; } changed = true; } @@ -186,6 +187,7 @@ void BackgroundSlicingProcess::thread_proc() switch(m_print->technology()) { case ptFFF: this->process_fff(); break; case ptSLA: this->process_sla(); break; + case ptSLS: this->process_fff(); break; default: m_print->process(); break; } } catch (CanceledException & /* ex */) { diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index a2299e7bf..9ea20163d 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -123,7 +123,7 @@ public: // This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs), // and it does not account for the OctoPrint scheduling. bool finished() const { return m_print->finished(); } - + private: void thread_proc(); void thread_proc_safe(); diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index 68e353ebe..2373eb87a 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -10,8 +10,10 @@ #include #include #include +#include #include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/Utils/Bonjour.hpp" @@ -49,31 +51,36 @@ struct LifetimeGuard LifetimeGuard(BonjourDialog *dialog) : dialog(dialog) {} }; - -BonjourDialog::BonjourDialog(wxWindow *parent) : - wxDialog(parent, wxID_ANY, _(L("Network lookup"))), - list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxSize(800, 300))), - replies(new ReplySet), - label(new wxStaticText(this, wxID_ANY, "")), - timer(new wxTimer()), - timer_state(0) +BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech) + : wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxRESIZE_BORDER) + , list(new wxListView(this, wxID_ANY)) + , replies(new ReplySet) + , label(new wxStaticText(this, wxID_ANY, "")) + , timer(new wxTimer()) + , timer_state(0) + , tech(tech) { + const int em = GUI::wxGetApp().em_unit(); + list->SetMinSize(wxSize(80 * em, 30 * em)); + wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL); - vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10); + vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em); list->SetSingleStyle(wxLC_SINGLE_SEL); list->SetSingleStyle(wxLC_SORT_DESCENDING); - list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 50); - list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 100); - list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 200); - list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 50); + list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 5 * em); + list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 10 * em); + list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 20 * em); + if (tech == ptFFF) { + list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 5 * em); + } - vsizer->Add(list, 1, wxEXPAND | wxALL, 10); + vsizer->Add(list, 1, wxEXPAND | wxALL, em); wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL); - button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, 10); - button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, 10); + button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, em); + button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, em); // ^ Note: The Ok/Cancel labels are translated by wxWidgets vsizer->Add(button_sizer, 0, wxALIGN_CENTER); @@ -110,7 +117,11 @@ bool BonjourDialog::show_and_lookup() // so that both threads can access it safely. auto dguard = std::make_shared(this); + // Note: More can be done here when we support discovery of hosts other than Octoprint and SL1 + Bonjour::TxtKeys txt_keys { "version", "model" }; + bonjour = std::move(Bonjour("octoprint") + .set_txt_keys(std::move(txt_keys)) .set_retries(3) .set_timeout(4) .on_reply([dguard](BonjourReply &&reply) { @@ -157,9 +168,20 @@ void BonjourDialog::on_reply(BonjourReplyEvent &e) return; } + // Filter replies based on selected technology + const auto model = e.reply.txt_data.find("model"); + const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1"; + if (tech != ptSLA && sl1 || tech == ptSLA && !sl1) { + return; + } + replies->insert(std::move(e.reply)); auto selected = get_selected(); + + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; + list->DeleteAllItems(); // The whole list is recreated so that we benefit from it already being sorted in the set. @@ -168,12 +190,20 @@ void BonjourDialog::on_reply(BonjourReplyEvent &e) auto item = list->InsertItem(0, reply.full_address); list->SetItem(item, 1, reply.hostname); list->SetItem(item, 2, reply.service_name); - list->SetItem(item, 3, reply.version); + + if (tech == ptFFF) { + const auto it = reply.txt_data.find("version"); + if (it != reply.txt_data.end()) { + list->SetItem(item, 3, GUI::from_u8(it->second)); + } + } } - for (int i = 0; i < 4; i++) { - this->list->SetColumnWidth(i, wxLIST_AUTOSIZE); - if (this->list->GetColumnWidth(i) < 100) { this->list->SetColumnWidth(i, 100); } + const int em = GUI::wxGetApp().em_unit(); + + for (int i = 0; i < list->GetColumnCount(); i++) { + list->SetColumnWidth(i, wxLIST_AUTOSIZE); + if (list->GetColumnWidth(i) < 10 * em) { list->SetColumnWidth(i, 10 * em); } } if (!selected.IsEmpty()) { diff --git a/src/slic3r/GUI/BonjourDialog.hpp b/src/slic3r/GUI/BonjourDialog.hpp index e3f53790b..a9a33d522 100644 --- a/src/slic3r/GUI/BonjourDialog.hpp +++ b/src/slic3r/GUI/BonjourDialog.hpp @@ -5,6 +5,8 @@ #include +#include "libslic3r/PrintConfig.hpp" + class wxListView; class wxStaticText; class wxTimer; @@ -21,7 +23,7 @@ class ReplySet; class BonjourDialog: public wxDialog { public: - BonjourDialog(wxWindow *parent); + BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology); BonjourDialog(BonjourDialog &&) = delete; BonjourDialog(const BonjourDialog &) = delete; BonjourDialog &operator=(BonjourDialog &&) = delete; @@ -37,6 +39,7 @@ private: std::shared_ptr bonjour; std::unique_ptr timer; unsigned timer_state; + Slic3r::PrinterTechnology tech; void on_reply(BonjourReplyEvent &); void on_timer(wxTimerEvent &); diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp new file mode 100644 index 000000000..c748efa64 --- /dev/null +++ b/src/slic3r/GUI/Camera.cpp @@ -0,0 +1,62 @@ +#include "libslic3r/libslic3r.h" + +#include "Camera.hpp" + +static const float GIMBALL_LOCK_THETA_MAX = 180.0f; + +namespace Slic3r { +namespace GUI { + +Camera::Camera() + : type(Ortho) + , zoom(1.0f) + , phi(45.0f) +// , distance(0.0f) + , requires_zoom_to_bed(false) + , m_theta(45.0f) + , m_target(Vec3d::Zero()) +{ +} + +std::string Camera::get_type_as_string() const +{ + switch (type) + { + default: + case Unknown: + return "unknown"; +// case Perspective: +// return "perspective"; + case Ortho: + return "ortho"; + }; +} + +void Camera::set_target(const Vec3d& target) +{ + m_target = target; + m_target(0) = clamp(m_scene_box.min(0), m_scene_box.max(0), m_target(0)); + m_target(1) = clamp(m_scene_box.min(1), m_scene_box.max(1), m_target(1)); + m_target(2) = clamp(m_scene_box.min(2), m_scene_box.max(2), m_target(2)); +} + +void Camera::set_theta(float theta, bool apply_limit) +{ + if (apply_limit) + m_theta = clamp(0.0f, GIMBALL_LOCK_THETA_MAX, theta); + else + { + m_theta = fmod(theta, 360.0f); + if (m_theta < 0.0f) + m_theta += 360.0f; + } +} + +void Camera::set_scene_box(const BoundingBoxf3& box) +{ + m_scene_box = box; +} + +} // GUI +} // Slic3r + diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp new file mode 100644 index 000000000..d50dc6e4d --- /dev/null +++ b/src/slic3r/GUI/Camera.hpp @@ -0,0 +1,50 @@ +#ifndef slic3r_Camera_hpp_ +#define slic3r_Camera_hpp_ + +#include "libslic3r/BoundingBox.hpp" + +namespace Slic3r { +namespace GUI { + +struct Camera +{ + enum EType : unsigned char + { + Unknown, +// Perspective, + Ortho, + Num_types + }; + + EType type; + float zoom; + float phi; +// float distance; + bool requires_zoom_to_bed; + +private: + Vec3d m_target; + float m_theta; + + BoundingBoxf3 m_scene_box; + +public: + Camera(); + + std::string get_type_as_string() const; + + const Vec3d& get_target() const { return m_target; } + void set_target(const Vec3d& target); + + float get_theta() const { return m_theta; } + void set_theta(float theta, bool apply_limit); + + const BoundingBoxf3& get_scene_box() const { return m_scene_box; } + void set_scene_box(const BoundingBoxf3& box); +}; + +} // GUI +} // Slic3r + +#endif // slic3r_Camera_hpp_ + diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index 422f683b3..205e84f57 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -38,7 +38,7 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve text += wxString("") + (snapshot_active ? _(L("Active: ")) : "") + Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason); if (! snapshot.comment.empty()) - text += " (" + snapshot.comment + ")"; + text += " (" + wxString::FromUTF8(snapshot.comment.data()) + ")"; text += "
"; // End of row header. text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "
"; diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index a17d6c805..e6da79a7b 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -2,6 +2,7 @@ #include "GUI_App.hpp" #include "I18N.hpp" #include "Field.hpp" +#include "wxExtensions.hpp" #include "libslic3r/PrintConfig.hpp" @@ -36,7 +37,9 @@ void Field::PostInitialize() m_Undo_to_sys_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); if (wxMSW) { m_Undo_btn->SetBackgroundColour(color); + m_Undo_btn->SetBackgroundStyle(wxBG_STYLE_PAINT); m_Undo_to_sys_btn->SetBackgroundColour(color); + m_Undo_to_sys_btn->SetBackgroundStyle(wxBG_STYLE_PAINT); } m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_back_to_initial_value(); })); m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_back_to_sys_value(); })); @@ -257,6 +260,12 @@ void TextCtrl::BUILD() { const long style = m_opt.multiline ? wxTE_MULTILINE : wxTE_PROCESS_ENTER/*0*/; auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, style); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + + if (! m_opt.multiline) + // Only disable background refresh for single line input fields, as they are completely painted over by the edit control. + // This does not apply to the multi-line edit field, where the last line and a narrow frame around the text is not cleared. + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); #ifdef __WXOSX__ temp->OSXDisableAllSmartSubstitutions(); #endif // __WXOSX__ @@ -372,6 +381,8 @@ void CheckBox::BUILD() { false; auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); temp->SetValue(check_value); if (m_opt.readonly) temp->Disable(); @@ -429,6 +440,8 @@ void SpinCtrl::BUILD() { auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, 0|wxTE_PROCESS_ENTER, min_val, max_val, default_value); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); #ifndef __WXOSX__ // #ys_FIXME_KILL_FOCUS @@ -464,8 +477,14 @@ void SpinCtrl::BUILD() { // # 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 std::string value = e.GetString().utf8_str().data(); - if (is_matched(value, "^\\-?\\d+$")) - tmp_value = std::stoi(value); + if (is_matched(value, "^\\-?\\d+$")) { + try { + tmp_value = std::stoi(value); + } + catch (const std::exception & /* e */) { + tmp_value = -9999; + } + } else tmp_value = -9999; #ifdef __WXOSX__ propagate_value(); @@ -487,15 +506,31 @@ void SpinCtrl::propagate_value() } void Choice::BUILD() { - auto size = wxSize(wxDefaultSize); + wxSize size(15 * wxGetApp().em_unit(), -1); if (m_opt.height >= 0) size.SetHeight(m_opt.height); if (m_opt.width >= 0) size.SetWidth(m_opt.width); - wxComboBox* temp; - if (!m_opt.gui_type.empty() && m_opt.gui_type.compare("select_open") != 0) - temp = new wxComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size); - else - temp = new wxComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, NULL, wxCB_READONLY); + wxBitmapComboBox* temp; + if (!m_opt.gui_type.empty() && m_opt.gui_type.compare("select_open") != 0) { + m_is_editable = true; + temp = new wxBitmapComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size); + } + else { +#ifdef __WXOSX__ + /* wxBitmapComboBox with wxCB_READONLY style return NULL for GetTextCtrl(), + * so ToolTip doesn't shown. + * Next workaround helps to solve this problem + */ + temp = new wxBitmapComboBox(); + temp->SetTextCtrlStyle(wxTE_READONLY); + temp->Create(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr); +#else + temp = new wxBitmapComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr, wxCB_READONLY); +#endif //__WXOSX__ + } + + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); // recast as a wxWindow to fit the calling convention window = dynamic_cast(temp); @@ -505,19 +540,22 @@ void Choice::BUILD() { else{ for (auto el : m_opt.enum_labels.empty() ? m_opt.enum_values : m_opt.enum_labels) { const wxString& str = _(el);//m_opt_id == "support" ? _(el) : el; - temp->Append(str); + //FIXME Vojtech: Why is the single column empty icon necessary? It is a workaround of some kind, but what for? + // Please document such workarounds by comments! + // temp->Append(str, create_scaled_bitmap("empty_icon.png")); + temp->Append(str, wxNullBitmap); } set_selection(); } // temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId()); temp->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId()); - if (temp->GetWindowStyle() != wxCB_READONLY) { + if (m_is_editable) { temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { e.Skip(); if (m_opt.type == coStrings) return; double old_val = !m_value.empty() ? boost::any_cast(m_value) : -99999; - if (is_defined_input_value(window, m_opt.type)) { + if (is_defined_input_value(window, m_opt.type)) { if (fabs(old_val - boost::any_cast(get_value())) <= 0.0001) return; else @@ -534,6 +572,8 @@ void Choice::BUILD() { void Choice::set_selection() { wxString text_value = wxString(""); + + wxBitmapComboBox* field = dynamic_cast(window); switch (m_opt.type) { case coFloat: case coPercent: { @@ -548,13 +588,13 @@ void Choice::set_selection() } // if (m_opt.type == coPercent) text_value += "%"; idx == m_opt.enum_values.size() ? - dynamic_cast(window)->SetValue(text_value) : - dynamic_cast(window)->SetSelection(idx); + field->SetValue(text_value) : + field->SetSelection(idx); break; } case coEnum:{ int id_value = static_cast*>(m_opt.default_value)->value; //!! - dynamic_cast(window)->SetSelection(id_value); + field->SetSelection(id_value); break; } case coInt:{ @@ -568,8 +608,8 @@ void Choice::set_selection() ++idx; } idx == m_opt.enum_values.size() ? - dynamic_cast(window)->SetValue(text_value) : - dynamic_cast(window)->SetSelection(idx); + field->SetValue(text_value) : + field->SetSelection(idx); break; } case coStrings:{ @@ -583,8 +623,8 @@ void Choice::set_selection() ++idx; } idx == m_opt.enum_values.size() ? - dynamic_cast(window)->SetValue(text_value) : - dynamic_cast(window)->SetSelection(idx); + field->SetValue(text_value) : + field->SetSelection(idx); break; } } @@ -602,9 +642,10 @@ void Choice::set_value(const std::string& value, bool change_event) //! Redunda ++idx; } + wxBitmapComboBox* field = dynamic_cast(window); idx == m_opt.enum_values.size() ? - dynamic_cast(window)->SetValue(value) : - dynamic_cast(window)->SetSelection(idx); + field->SetValue(value) : + field->SetSelection(idx); m_disable_change_event = false; } @@ -613,6 +654,8 @@ void Choice::set_value(const boost::any& value, bool change_event) { m_disable_change_event = !change_event; + wxBitmapComboBox* field = dynamic_cast(window); + switch (m_opt.type) { case coInt: case coFloat: @@ -634,11 +677,11 @@ void Choice::set_value(const boost::any& value, bool change_event) if (idx == m_opt.enum_values.size()) { // For editable Combobox under OSX is needed to set selection to -1 explicitly, // otherwise selection doesn't be changed - dynamic_cast(window)->SetSelection(-1); - dynamic_cast(window)->SetValue(text_value); + field->SetSelection(-1); + field->SetValue(text_value); } else - dynamic_cast(window)->SetSelection(idx); + field->SetSelection(idx); break; } case coEnum: { @@ -689,7 +732,7 @@ void Choice::set_value(const boost::any& value, bool change_event) } else val = 3; } - dynamic_cast(window)->SetSelection(val); + field->SetSelection(val); break; } default: @@ -708,7 +751,7 @@ void Choice::set_values(const std::vector& values) // # it looks that Clear() also clears the text field in recent wxWidgets versions, // # but we want to preserve it - auto ww = dynamic_cast(window); + auto ww = dynamic_cast(window); auto value = ww->GetValue(); ww->Clear(); ww->Append(""); @@ -721,8 +764,9 @@ void Choice::set_values(const std::vector& values) boost::any& Choice::get_value() { -// boost::any m_value; - wxString ret_str = static_cast(window)->GetValue(); + wxBitmapComboBox* field = dynamic_cast(window); + + wxString ret_str = field->GetValue(); // options from right panel std::vector right_panel_options{ "support", "scale_unit" }; @@ -732,8 +776,8 @@ boost::any& Choice::get_value() if (m_opt.type == coEnum) { - int ret_enum = static_cast(window)->GetSelection(); - if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "solid_fill_pattern" || m_opt_id == "support_material_interface_pattern") + int ret_enum = field->GetSelection(); + if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "solid_fill_pattern" || m_opt_id == "support_material_interface_pattern") { if (!m_opt.enum_values.empty()) { std::string key = m_opt.enum_values[ret_enum]; @@ -777,8 +821,9 @@ boost::any& Choice::get_value() m_value = static_cast(ret_enum); } else if (m_opt.gui_type == "f_enum_open") { - const int ret_enum = static_cast(window)->GetSelection(); - if (ret_enum < 0 || m_opt.enum_values.empty() || m_opt.type == coStrings) + const int ret_enum = field->GetSelection(); + if (ret_enum < 0 || m_opt.enum_values.empty() || m_opt.type == coStrings || + ret_str != m_opt.enum_values[ret_enum] && ret_str != m_opt.enum_labels[ret_enum] ) // modifies ret_string! get_value_by_opt_type(ret_str); else @@ -805,6 +850,7 @@ void ColourPicker::BUILD() } auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); // // recast as a wxWindow to fit the calling convention window = dynamic_cast(temp); @@ -839,10 +885,21 @@ void PointCtrl::BUILD() x_textctrl = new wxTextCtrl(m_parent, wxID_ANY, X, wxDefaultPosition, field_size, wxTE_PROCESS_ENTER); y_textctrl = new wxTextCtrl(m_parent, wxID_ANY, Y, wxDefaultPosition, field_size, wxTE_PROCESS_ENTER); + x_textctrl->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + x_textctrl->SetBackgroundStyle(wxBG_STYLE_PAINT); + y_textctrl->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + y_textctrl->SetBackgroundStyle(wxBG_STYLE_PAINT); - temp->Add(new wxStaticText(m_parent, wxID_ANY, "x : "), 0, wxALIGN_CENTER_VERTICAL, 0); + auto static_text_x = new wxStaticText(m_parent, wxID_ANY, "x : "); + auto static_text_y = new wxStaticText(m_parent, wxID_ANY, " y : "); + static_text_x->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + static_text_x->SetBackgroundStyle(wxBG_STYLE_PAINT); + static_text_y->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + static_text_y->SetBackgroundStyle(wxBG_STYLE_PAINT); + + temp->Add(static_text_x, 0, wxALIGN_CENTER_VERTICAL, 0); temp->Add(x_textctrl); - temp->Add(new wxStaticText(m_parent, wxID_ANY, " y : "), 0, wxALIGN_CENTER_VERTICAL, 0); + temp->Add(static_text_y, 0, wxALIGN_CENTER_VERTICAL, 0); temp->Add(y_textctrl); // x_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), x_textctrl->GetId()); @@ -911,6 +968,8 @@ void StaticText::BUILD() const wxString legend(static_cast(m_opt.default_value)->value); auto temp = new wxStaticText(m_parent, wxID_ANY, legend, wxDefaultPosition, size, wxST_ELLIPSIZE_MIDDLE); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); temp->SetFont(wxGetApp().bold_font()); // // recast as a wxWindow to fit the calling convention @@ -934,10 +993,14 @@ void SliderCtrl::BUILD() m_slider = new wxSlider(m_parent, wxID_ANY, def_val * m_scale, min * m_scale, max * m_scale, wxDefaultPosition, size); + m_slider->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + m_slider->SetBackgroundStyle(wxBG_STYLE_PAINT); wxSize field_size(40, -1); m_textctrl = new wxTextCtrl(m_parent, wxID_ANY, wxString::Format("%d", m_slider->GetValue()/m_scale), wxDefaultPosition, field_size); + m_textctrl->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + m_textctrl->SetBackgroundStyle(wxBG_STYLE_PAINT); temp->Add(m_slider, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, 0); temp->Add(m_textctrl, 0, wxALIGN_CENTER_VERTICAL, 0); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 8391a111b..128d60d47 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include "libslic3r/libslic3r.h" @@ -351,14 +352,19 @@ public: wxWindow* window{ nullptr }; void BUILD() override; + /* Under OSX: wxBitmapComboBox->GetWindowStyle() returns some weard value, + * so let use a flag, which has TRUE value for a control without wxCB_READONLY style + */ + bool m_is_editable { false }; + void set_selection(); void set_value(const std::string& value, bool change_event = false); void set_value(const boost::any& value, bool change_event = false); void set_values(const std::vector &values); boost::any& get_value() override; - void enable() override { dynamic_cast(window)->Enable(); }; - void disable() override{ dynamic_cast(window)->Disable(); }; + void enable() override { dynamic_cast(window)->Enable(); }; + void disable() override{ dynamic_cast(window)->Disable(); }; wxWindow* getWindow() override { return window; } }; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5f7ef3c55..3f97eb76f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1,9 +1,9 @@ -#include "slic3r/GUI/GLGizmo.hpp" +#include "libslic3r/libslic3r.h" +#include "slic3r/GUI/Gizmos/GLGizmos.hpp" #include "GLCanvas3D.hpp" #include "admesh/stl.h" #include "polypartition.h" -#include "libslic3r/libslic3r.h" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/GCode/PreviewData.hpp" @@ -16,7 +16,6 @@ #include "slic3r/GUI/GLShader.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/PresetBundle.hpp" -//#include "slic3r/GUI/GLGizmo.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" @@ -35,6 +34,7 @@ #include #include #include +#include // Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx. #include "libslic3r/Print.hpp" @@ -54,7 +54,6 @@ #include static const float TRACKBALLSIZE = 0.8f; -static const float GIMBALL_LOCK_THETA_MAX = 180.0f; static const float GROUND_Z = -0.02f; // phi / theta angles to orient the camera. @@ -78,8 +77,7 @@ static const float DEFAULT_BG_DARK_COLOR[3] = { 0.478f, 0.478f, 0.478f }; static const float DEFAULT_BG_LIGHT_COLOR[3] = { 0.753f, 0.753f, 0.753f }; static const float ERROR_BG_DARK_COLOR[3] = { 0.478f, 0.192f, 0.039f }; static const float ERROR_BG_LIGHT_COLOR[3] = { 0.753f, 0.192f, 0.039f }; -static const float UNIFORM_SCALE_COLOR[3] = { 1.0f, 0.38f, 0.0f }; -static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; +//static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; namespace Slic3r { namespace GUI { @@ -183,61 +181,6 @@ void Rect::set_bottom(float bottom) m_bottom = bottom; } -GLCanvas3D::Camera::Camera() - : type(Ortho) - , zoom(1.0f) - , phi(45.0f) -// , distance(0.0f) - , m_theta(45.0f) - , m_target(Vec3d::Zero()) -{ -} - -std::string GLCanvas3D::Camera::get_type_as_string() const -{ - switch (type) - { - default: - case Unknown: - return "unknown"; -// case Perspective: -// return "perspective"; - case Ortho: - return "ortho"; - }; -} - -void GLCanvas3D::Camera::set_theta(float theta, bool apply_limit) -{ - if (apply_limit) - m_theta = clamp(0.0f, GIMBALL_LOCK_THETA_MAX, theta); - else - { - m_theta = fmod(theta, 360.0f); - if (m_theta < 0.0f) - m_theta += 360.0f; - } -} - -void GLCanvas3D::Camera::set_target(const Vec3d& target, GLCanvas3D& canvas) -{ - m_target = target; - m_target(0) = clamp(m_scene_box.min(0), m_scene_box.max(0), m_target(0)); - m_target(1) = clamp(m_scene_box.min(1), m_scene_box.max(1), m_target(1)); - m_target(2) = clamp(m_scene_box.min(2), m_scene_box.max(2), m_target(2)); - if (!m_target.isApprox(target)) - canvas.viewport_changed(); -} - -void GLCanvas3D::Camera::set_scene_box(const BoundingBoxf3& box, GLCanvas3D& canvas) -{ - if (m_scene_box != box) - { - m_scene_box = box; - canvas.viewport_changed(); - } -} - #if !ENABLE_TEXTURES_FROM_SVG GLCanvas3D::Shader::Shader() : m_shader(nullptr) @@ -773,1724 +716,68 @@ float GLCanvas3D::LayersEditing::reset_button_height(const GLCanvas3D &canvas) const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX); const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX); -#if ENABLE_MOVE_MIN_THRESHOLD const int GLCanvas3D::Mouse::Drag::MoveThresholdPx = 5; -#endif // ENABLE_MOVE_MIN_THRESHOLD GLCanvas3D::Mouse::Drag::Drag() : start_position_2D(Invalid_2D_Point) , start_position_3D(Invalid_3D_Point) , move_volume_idx(-1) -#if ENABLE_MOVE_MIN_THRESHOLD , move_requires_threshold(false) , move_start_threshold_position_2D(Invalid_2D_Point) -#endif // ENABLE_MOVE_MIN_THRESHOLD { } GLCanvas3D::Mouse::Mouse() : dragging(false) - , left_down(false) , position(DBL_MAX, DBL_MAX) , scene_position(DBL_MAX, DBL_MAX, DBL_MAX) , ignore_up_event(false) { } -GLCanvas3D::Selection::VolumeCache::TransformCache::TransformCache() - : position(Vec3d::Zero()) - , rotation(Vec3d::Zero()) - , scaling_factor(Vec3d::Ones()) - , mirror(Vec3d::Ones()) - , rotation_matrix(Transform3d::Identity()) - , scale_matrix(Transform3d::Identity()) - , mirror_matrix(Transform3d::Identity()) -{ -} - -GLCanvas3D::Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) - : position(transform.get_offset()) - , rotation(transform.get_rotation()) - , scaling_factor(transform.get_scaling_factor()) - , mirror(transform.get_mirror()) -{ - rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); - scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); - mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); -} - -GLCanvas3D::Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) - : m_volume(volume_transform) - , m_instance(instance_transform) -{ -} - -GLCanvas3D::Selection::Selection() - : m_volumes(nullptr) - , m_model(nullptr) - , m_mode(Instance) - , m_type(Empty) - , m_valid(false) - , m_bounding_box_dirty(true) - , m_curved_arrow(16) - , m_scale_factor(1.0f) -{ -#if ENABLE_RENDER_SELECTION_CENTER - m_quadric = ::gluNewQuadric(); - if (m_quadric != nullptr) - ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -#endif // ENABLE_RENDER_SELECTION_CENTER -} - -#if ENABLE_RENDER_SELECTION_CENTER -GLCanvas3D::Selection::~Selection() -{ - if (m_quadric != nullptr) - ::gluDeleteQuadric(m_quadric); -} -#endif // ENABLE_RENDER_SELECTION_CENTER - -void GLCanvas3D::Selection::set_volumes(GLVolumePtrs* volumes) -{ - m_volumes = volumes; - _update_valid(); -} - -bool GLCanvas3D::Selection::init(bool useVBOs) -{ - if (!m_arrow.init(useVBOs)) - return false; - - m_arrow.set_scale(5.0 * Vec3d::Ones()); - - if (!m_curved_arrow.init(useVBOs)) - return false; - - m_curved_arrow.set_scale(5.0 * Vec3d::Ones()); - return true; -} - -void GLCanvas3D::Selection::set_model(Model* model) -{ - m_model = model; - _update_valid(); -} - -void GLCanvas3D::Selection::add(unsigned int volume_idx, bool as_single_selection) -{ - if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) - return; - - const GLVolume* volume = (*m_volumes)[volume_idx]; - // wipe tower is already selected - if (is_wipe_tower() && volume->is_wipe_tower) - return; - - // resets the current list if needed - bool needs_reset = as_single_selection; - needs_reset |= volume->is_wipe_tower; - needs_reset |= is_wipe_tower() && !volume->is_wipe_tower; - needs_reset |= !is_modifier() && volume->is_modifier; - needs_reset |= is_modifier() && !volume->is_modifier; - - if (needs_reset) - clear(); - - m_mode = volume->is_modifier ? Volume : Instance; - - switch (m_mode) - { - case Volume: - { - if (volume->volume_idx() >= 0 && (is_empty() || (volume->instance_idx() == get_instance_idx()))) - _add_volume(volume_idx); - - break; - } - case Instance: - { - _add_instance(volume->object_idx(), volume->instance_idx()); - break; - } - } - - _update_type(); - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::remove(unsigned int volume_idx) -{ - if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) - return; - - GLVolume* volume = (*m_volumes)[volume_idx]; - - switch (m_mode) - { - case Volume: - { - _remove_volume(volume_idx); - break; - } - case Instance: - { - _remove_instance(volume->object_idx(), volume->instance_idx()); - break; - } - } - - _update_type(); - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::add_object(unsigned int object_idx, bool as_single_selection) -{ - if (!m_valid) - return; - - // resets the current list if needed - if (as_single_selection) - clear(); - - m_mode = Instance; - - _add_object(object_idx); - - _update_type(); - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::remove_object(unsigned int object_idx) -{ - if (!m_valid) - return; - - _remove_object(object_idx); - - _update_type(); - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, bool as_single_selection) -{ - if (!m_valid) - return; - - // resets the current list if needed - if (as_single_selection) - clear(); - - m_mode = Instance; - - _add_instance(object_idx, instance_idx); - - _update_type(); - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::remove_instance(unsigned int object_idx, unsigned int instance_idx) -{ - if (!m_valid) - return; - - _remove_instance(object_idx, instance_idx); - - _update_type(); - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection) -{ - if (!m_valid) - return; - - // resets the current list if needed - if (as_single_selection) - clear(); - - m_mode = Volume; - - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == object_idx) && (v->volume_idx() == volume_idx)) - { - if ((instance_idx != -1) && (v->instance_idx() == instance_idx)) - _add_volume(i); - } - } - - _update_type(); - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) -{ - if (!m_valid) - return; - - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == object_idx) && (v->volume_idx() == volume_idx)) - _remove_volume(i); - } - - _update_type(); - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::add_all() -{ - if (!m_valid) - return; - - m_mode = Instance; - clear(); - - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - if (!(*m_volumes)[i]->is_wipe_tower) - _add_volume(i); - } - - _update_type(); - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::clear() -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) - { - (*m_volumes)[i]->selected = false; - } - - m_list.clear(); - - _update_type(); - m_bounding_box_dirty = true; - - // resets the cache in the sidebar - wxGetApp().obj_manipul()->reset_cache(); -} - -// Update the selection based on the map from old indices to new indices after m_volumes changed. -// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances. -void GLCanvas3D::Selection::volumes_changed(const std::vector &map_volume_old_to_new) -{ - assert(m_valid); - - // 1) Update the selection set. - IndicesList list_new; - std::vector> model_instances; - for (unsigned int idx : m_list) { - if (map_volume_old_to_new[idx] != size_t(-1)) { - unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx]; - list_new.insert(new_idx); - if (m_mode == Instance) { - // Save the object_idx / instance_idx pair of selected old volumes, - // so we may add the newly added volumes of the same object_idx / instance_idx pair - // to the selection. - const GLVolume *volume = (*m_volumes)[new_idx]; - model_instances.emplace_back(volume->object_idx(), volume->instance_idx()); - } - } - } - m_list = std::move(list_new); - - if (! model_instances.empty()) { - // Instance selection mode. Add the newly added volumes of the same object_idx / instance_idx pair - // to the selection. - assert(m_mode == Instance); - sort_remove_duplicates(model_instances); - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) { - const GLVolume* volume = (*m_volumes)[i]; - for (const std::pair &model_instance : model_instances) - if (volume->object_idx() == model_instance.first && volume->instance_idx() == model_instance.second) - this->_add_volume(i); - } - } - - _update_type(); - m_bounding_box_dirty = true; -} - -bool GLCanvas3D::Selection::is_single_full_instance() const -{ - if (m_type == SingleFullInstance) - return true; - - if (m_type == SingleFullObject) - return get_instance_idx() != -1; - - if (m_list.empty() || m_volumes->empty()) - return false; - - int object_idx = m_valid ? get_object_idx() : -1; - if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) - return false; - - int instance_idx = (*m_volumes)[*m_list.begin()]->instance_idx(); - - std::set volumes_idxs; - for (unsigned int i : m_list) - { - const GLVolume* v = (*m_volumes)[i]; - int volume_idx = v->volume_idx(); - if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx) && (volume_idx >= 0)) - volumes_idxs.insert(volume_idx); - } - - return m_model->objects[object_idx]->volumes.size() == volumes_idxs.size(); -} - -bool GLCanvas3D::Selection::is_from_single_object() const -{ - int idx = get_object_idx(); - return (0 <= idx) && (idx < 1000); -} - -bool GLCanvas3D::Selection::requires_uniform_scale() const -{ - if (is_single_full_instance() || is_single_modifier() || is_single_volume()) - return false; - - return true; -} - -int GLCanvas3D::Selection::get_object_idx() const -{ - return (m_cache.content.size() == 1) ? m_cache.content.begin()->first : -1; -} - -int GLCanvas3D::Selection::get_instance_idx() const -{ - if (m_cache.content.size() == 1) - { - const InstanceIdxsList& idxs = m_cache.content.begin()->second; - if (idxs.size() == 1) - return *idxs.begin(); - } - - return -1; -} - -const GLCanvas3D::Selection::InstanceIdxsList& GLCanvas3D::Selection::get_instance_idxs() const -{ - assert(m_cache.content.size() == 1); - return m_cache.content.begin()->second; -} - -const GLVolume* GLCanvas3D::Selection::get_volume(unsigned int volume_idx) const -{ - return (m_valid && (volume_idx < (unsigned int)m_volumes->size())) ? (*m_volumes)[volume_idx] : nullptr; -} - -const BoundingBoxf3& GLCanvas3D::Selection::get_bounding_box() const -{ - if (m_bounding_box_dirty) - _calc_bounding_box(); - - return m_bounding_box; -} - -void GLCanvas3D::Selection::start_dragging() -{ - if (!m_valid) - return; - - _set_caches(); -} - -void GLCanvas3D::Selection::translate(const Vec3d& displacement, bool local) -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) - { - if ((m_mode == Volume) || (*m_volumes)[i]->is_wipe_tower) - { - if (local) - (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); - else - { - Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; - (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); - } - } - else if (m_mode == Instance) - (*m_volumes)[i]->set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); - } - -#if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) - _synchronize_unselected_instances(SYNC_ROTATION_NONE); - else if (m_mode == Volume) - _synchronize_unselected_volumes(); -#endif // !DISABLE_INSTANCES_SYNCH - - m_bounding_box_dirty = true; -} - -static Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) -{ - return - // From the current coordinate system to world. - Eigen::AngleAxisd(rot_xyz_to(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(rot_xyz_to(1), Vec3d::UnitY()) * Eigen::AngleAxisd(rot_xyz_to(0), Vec3d::UnitX()) * - // From world to the initial coordinate system. - Eigen::AngleAxisd(-rot_xyz_from(0), Vec3d::UnitX()) * Eigen::AngleAxisd(-rot_xyz_from(1), Vec3d::UnitY()) * Eigen::AngleAxisd(-rot_xyz_from(2), Vec3d::UnitZ()); -} - -// This should only be called if it is known, that the two rotations only differ in rotation around the Z axis. -static double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) -{ - Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); - Vec3d axis = angle_axis.axis(); - double angle = angle_axis.angle(); -#ifndef NDEBUG - if (std::abs(angle) > 1e-8) { - assert(std::abs(axis.x()) < 1e-8); - assert(std::abs(axis.y()) < 1e-8); - } -#endif /* NDEBUG */ - return (axis.z() < 0) ? -angle : angle; -} - -// Rotate an object around one of the axes. Only one rotation component is expected to be changing. -void GLCanvas3D::Selection::rotate(const Vec3d& rotation, GLCanvas3D::TransformationType transformation_type) -{ - if (!m_valid) - return; - - // Only relative rotation values are allowed in the world coordinate system. - assert(! transformation_type.world() || transformation_type.relative()); - - int rot_axis_max; - //FIXME this does not work for absolute rotations (transformation_type.absolute() is true) - rotation.cwiseAbs().maxCoeff(&rot_axis_max); - - // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. - std::vector object_instance_first(m_model->objects.size(), -1); - auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { - int first_volume_idx = object_instance_first[volume.object_idx()]; - if (rot_axis_max != 2 && first_volume_idx != -1) { - // Generic rotation, but no rotation around the Z axis. - // Always do a local rotation (do not consider the selection to be a rigid body). - assert(is_approx(rotation.z(), 0.0)); - const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; - const Vec3d &rotation = first_volume.get_instance_rotation(); - double z_diff = rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); - volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); - } else { - // extracts rotations from the composed transformation - Vec3d new_rotation = transformation_type.world() ? - Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : - transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); - if (rot_axis_max == 2 && transformation_type.joint()) { - // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. - double z_diff = rotation_diff_z(new_rotation, m_cache.volumes_data[i].get_instance_rotation()); - volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - } - volume.set_instance_rotation(new_rotation); - object_instance_first[volume.object_idx()] = i; - } - }; - - for (unsigned int i : m_list) - { - GLVolume &volume = *(*m_volumes)[i]; - if (is_single_full_instance()) - rotate_instance(volume, i); - else if (is_single_volume() || is_single_modifier()) - { - if (transformation_type.independent()) - volume.set_volume_rotation(volume.get_volume_rotation() + rotation); - else - { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - volume.set_volume_rotation(new_rotation); - } - } - else - { - if (m_mode == Instance) - rotate_instance(volume, i); - else if (m_mode == Volume) - { - // extracts rotations from the composed transformation - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - if (transformation_type.joint()) - { - Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); - volume.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); - } - volume.set_volume_rotation(new_rotation); - } - } - } - -#if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) - _synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); - else if (m_mode == Volume) - _synchronize_unselected_volumes(); -#endif // !DISABLE_INSTANCES_SYNCH - - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::flattening_rotate(const Vec3d& normal) -{ - // We get the normal in untransformed coordinates. We must transform it using the instance matrix, find out - // how to rotate the instance so it faces downwards and do the rotation. All that for all selected instances. - // The function assumes that is_from_single_object() holds. - - if (!m_valid) - return; - - for (unsigned int i : m_list) - { - Transform3d wst = m_cache.volumes_data[i].get_instance_scale_matrix(); - Vec3d scaling_factor = Vec3d(1./wst(0,0), 1./wst(1,1), 1./wst(2,2)); - - Transform3d wmt = m_cache.volumes_data[i].get_instance_mirror_matrix(); - Vec3d mirror(wmt(0,0), wmt(1,1), wmt(2,2)); - - Vec3d rotation = Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix()); - Vec3d transformed_normal = Geometry::assemble_transform(Vec3d::Zero(), rotation, scaling_factor, mirror) * normal; - transformed_normal.normalize(); - - Vec3d axis = transformed_normal(2) > 0.999f ? Vec3d(1., 0., 0.) : Vec3d(transformed_normal.cross(Vec3d(0., 0., -1.))); - axis.normalize(); - - Transform3d extra_rotation = Transform3d::Identity(); - extra_rotation.rotate(Eigen::AngleAxisd(acos(-transformed_normal(2)), axis)); - - Vec3d new_rotation = Geometry::extract_euler_angles(extra_rotation * m_cache.volumes_data[i].get_instance_rotation_matrix() ); - (*m_volumes)[i]->set_instance_rotation(new_rotation); - } - -#if !DISABLE_INSTANCES_SYNCH - // we want to synchronize z-rotation as well, otherwise the flattening behaves funny - // when applied on one of several identical instances - if (m_mode == Instance) - _synchronize_unselected_instances(SYNC_ROTATION_FULL); -#endif // !DISABLE_INSTANCES_SYNCH - - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::scale(const Vec3d& scale, bool local) -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) - { - if (is_single_full_instance()) - (*m_volumes)[i]->set_instance_scaling_factor(scale); - else if (is_single_volume() || is_single_modifier()) - (*m_volumes)[i]->set_volume_scaling_factor(scale); - else - { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); - if (m_mode == Instance) - { - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); - // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (!local) - (*m_volumes)[i]->set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - - (*m_volumes)[i]->set_instance_scaling_factor(new_scale); - } - else if (m_mode == Volume) - { - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); - // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (!local) - { - Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); - (*m_volumes)[i]->set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); - } - (*m_volumes)[i]->set_volume_scaling_factor(new_scale); - } - } - } - -#if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) - _synchronize_unselected_instances(SYNC_ROTATION_NONE); - else if (m_mode == Volume) - _synchronize_unselected_volumes(); -#endif // !DISABLE_INSTANCES_SYNCH - - _ensure_on_bed(); - - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::mirror(Axis axis) -{ - if (!m_valid) - return; - - bool single_full_instance = is_single_full_instance(); - - for (unsigned int i : m_list) - { - if (single_full_instance) - (*m_volumes)[i]->set_instance_mirror(axis, -(*m_volumes)[i]->get_instance_mirror(axis)); - else if (m_mode == Volume) - (*m_volumes)[i]->set_volume_mirror(axis, -(*m_volumes)[i]->get_volume_mirror(axis)); - } - -#if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) - _synchronize_unselected_instances(SYNC_ROTATION_NONE); - else if (m_mode == Volume) - _synchronize_unselected_volumes(); -#endif // !DISABLE_INSTANCES_SYNCH - - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::translate(unsigned int object_idx, const Vec3d& displacement) -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) - { - GLVolume* v = (*m_volumes)[i]; - if (v->object_idx() == object_idx) - v->set_instance_offset(v->get_instance_offset() + displacement); - } - - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) - { - if (done.size() == m_volumes->size()) - break; - - int object_idx = (*m_volumes)[i]->object_idx(); - if (object_idx >= 1000) - continue; - - // Process unselected volumes of the object. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume* v = (*m_volumes)[j]; - if (v->object_idx() != object_idx) - continue; - - v->set_instance_offset(v->get_instance_offset() + displacement); - done.insert(j); - } - } - - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) - { - GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) - v->set_instance_offset(v->get_instance_offset() + displacement); - } - - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) - { - if (done.size() == m_volumes->size()) - break; - - int object_idx = (*m_volumes)[i]->object_idx(); - if (object_idx >= 1000) - continue; - - // Process unselected volumes of the object. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume* v = (*m_volumes)[j]; - if ((v->object_idx() != object_idx) || (v->instance_idx() != instance_idx)) - continue; - - v->set_instance_offset(v->get_instance_offset() + displacement); - done.insert(j); - } - } - - m_bounding_box_dirty = true; -} - -void GLCanvas3D::Selection::erase() -{ - if (!m_valid) - return; - - if (is_single_full_object()) - wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itObject, get_object_idx(), 0); - else if (is_multiple_full_object()) - { - std::vector items; - items.reserve(m_cache.content.size()); - for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) - { - items.emplace_back(ItemType::itObject, it->first, 0); - } - wxGetApp().obj_list()->delete_from_model_and_list(items); - } - else if (is_multiple_full_instance()) - { - std::set> instances_idxs; - for (ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.begin(); obj_it != m_cache.content.end(); ++obj_it) - { - for (InstanceIdxsList::reverse_iterator inst_it = obj_it->second.rbegin(); inst_it != obj_it->second.rend(); ++inst_it) - { - instances_idxs.insert(std::make_pair(obj_it->first, *inst_it)); - } - } - - std::vector items; - items.reserve(instances_idxs.size()); - for (const std::pair& i : instances_idxs) - { - items.emplace_back(ItemType::itInstance, i.first, i.second); - } - wxGetApp().obj_list()->delete_from_model_and_list(items); - } - else if (is_single_full_instance()) - wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itInstance, get_object_idx(), get_instance_idx()); - else if (is_mixed()) - { - std::set items_set; - std::map volumes_in_obj; - - for (auto i : m_list) { - const auto gl_vol = (*m_volumes)[i]; - const auto glv_obj_idx = gl_vol->object_idx(); - const auto model_object = m_model->objects[glv_obj_idx]; - - if (model_object->instances.size() == 1) { - if (model_object->volumes.size() == 1) - items_set.insert(ItemForDelete(ItemType::itObject, glv_obj_idx, -1)); - else { - items_set.insert(ItemForDelete(ItemType::itVolume, glv_obj_idx, gl_vol->volume_idx())); - int idx = (volumes_in_obj.find(glv_obj_idx) == volumes_in_obj.end()) ? 0 : volumes_in_obj.at(glv_obj_idx); - volumes_in_obj[glv_obj_idx] = ++idx; - } - continue; - } - - const auto glv_ins_idx = gl_vol->instance_idx(); - - for (auto obj_ins : m_cache.content) { - if (obj_ins.first == glv_obj_idx) { - if (obj_ins.second.find(glv_ins_idx) != obj_ins.second.end()) { - if (obj_ins.second.size() == model_object->instances.size()) - items_set.insert(ItemForDelete(ItemType::itVolume, glv_obj_idx, gl_vol->volume_idx())); - else - items_set.insert(ItemForDelete(ItemType::itInstance, glv_obj_idx, glv_ins_idx)); - - break; - } - } - } - } - - std::vector items; - items.reserve(items_set.size()); - for (const ItemForDelete& i : items_set) { - if (i.type == ItemType::itVolume ) { - const int vol_in_obj_cnt = volumes_in_obj.find(i.obj_idx) == volumes_in_obj.end() ? 0 : volumes_in_obj.at(i.obj_idx); - if (vol_in_obj_cnt == m_model->objects[i.obj_idx]->volumes.size()) { - if (i.sub_obj_idx == vol_in_obj_cnt - 1) - items.emplace_back(ItemType::itObject, i.obj_idx, 0); - continue; - } - } - items.emplace_back(i.type, i.obj_idx, i.sub_obj_idx); - } - - wxGetApp().obj_list()->delete_from_model_and_list(items); - } - else - { - std::set> volumes_idxs; - for (unsigned int i : m_list) - { - const GLVolume* v = (*m_volumes)[i]; - // Only remove volumes associated with ModelVolumes from the object list. - // Temporary meshes (SLA supports or pads) are not managed by the object list. - if (v->volume_idx() >= 0) - volumes_idxs.insert(std::make_pair(v->object_idx(), v->volume_idx())); - } - - std::vector items; - items.reserve(volumes_idxs.size()); - for (const std::pair& v : volumes_idxs) - { - items.emplace_back(ItemType::itVolume, v.first, v.second); - } - - wxGetApp().obj_list()->delete_from_model_and_list(items); - } -} - -void GLCanvas3D::Selection::render(float scale_factor) const -{ - if (!m_valid || is_empty()) - return; - - m_scale_factor = scale_factor; - - // render cumulative bounding box of selected volumes - _render_selected_volumes(); - _render_synchronized_volumes(); -} - -#if ENABLE_RENDER_SELECTION_CENTER -void GLCanvas3D::Selection::render_center() const -{ - if (!m_valid || is_empty() || (m_quadric == nullptr)) - return; - - const Vec3d& center = get_bounding_box().center(); - - ::glDisable(GL_DEPTH_TEST); - - ::glEnable(GL_LIGHTING); - - ::glColor3f(1.0f, 1.0f, 1.0f); - ::glPushMatrix(); - ::glTranslated(center(0), center(1), center(2)); - ::gluSphere(m_quadric, 0.75, 32, 32); - ::glPopMatrix(); - - ::glDisable(GL_LIGHTING); -} -#endif // ENABLE_RENDER_SELECTION_CENTER - -void GLCanvas3D::Selection::render_sidebar_hints(const std::string& sidebar_field) const -{ - if (sidebar_field.empty()) - return; - - ::glClear(GL_DEPTH_BUFFER_BIT); - ::glEnable(GL_DEPTH_TEST); - - ::glEnable(GL_LIGHTING); - - ::glPushMatrix(); - - const Vec3d& center = get_bounding_box().center(); - - if (is_single_full_instance()) - { - ::glTranslated(center(0), center(1), center(2)); - if (!boost::starts_with(sidebar_field, "position")) - { - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - ::glMultMatrixd(orient_matrix.data()); - } - } - else if (is_single_volume() || is_single_modifier()) - { - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true) * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); - ::glTranslated(center(0), center(1), center(2)); - ::glMultMatrixd(orient_matrix.data()); - } - else - { - ::glTranslated(center(0), center(1), center(2)); - if (requires_local_axes()) - { - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - ::glMultMatrixd(orient_matrix.data()); - } - } - - if (boost::starts_with(sidebar_field, "position")) - _render_sidebar_position_hints(sidebar_field); - else if (boost::starts_with(sidebar_field, "rotation")) - _render_sidebar_rotation_hints(sidebar_field); - else if (boost::starts_with(sidebar_field, "scale")) - _render_sidebar_scale_hints(sidebar_field); - else if (boost::starts_with(sidebar_field, "size")) - _render_sidebar_size_hints(sidebar_field); - - ::glPopMatrix(); - - ::glDisable(GL_LIGHTING); -} - -bool GLCanvas3D::Selection::requires_local_axes() const -{ - return (m_mode == Volume) && is_from_single_instance(); -} - -void GLCanvas3D::Selection::_update_valid() -{ - m_valid = (m_volumes != nullptr) && (m_model != nullptr); -} - -void GLCanvas3D::Selection::_update_type() -{ - m_cache.content.clear(); - m_type = Mixed; - - for (unsigned int i : m_list) - { - const GLVolume* volume = (*m_volumes)[i]; - int obj_idx = volume->object_idx(); - int inst_idx = volume->instance_idx(); - ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.find(obj_idx); - if (obj_it == m_cache.content.end()) - obj_it = m_cache.content.insert(ObjectIdxsToInstanceIdxsMap::value_type(obj_idx, InstanceIdxsList())).first; - - obj_it->second.insert(inst_idx); - } - - bool requires_disable = false; - - if (!m_valid) - m_type = Invalid; - else - { - if (m_list.empty()) - m_type = Empty; - else if (m_list.size() == 1) - { - const GLVolume* first = (*m_volumes)[*m_list.begin()]; - if (first->is_wipe_tower) - m_type = WipeTower; - else if (first->is_modifier) - { - m_type = SingleModifier; - requires_disable = true; - } - else - { - const ModelObject* model_object = m_model->objects[first->object_idx()]; - unsigned int volumes_count = (unsigned int)model_object->volumes.size(); - unsigned int instances_count = (unsigned int)model_object->instances.size(); - if (volumes_count * instances_count == 1) - { - m_type = SingleFullObject; - // ensures the correct mode is selected - m_mode = Instance; - } - else if (volumes_count == 1) // instances_count > 1 - { - m_type = SingleFullInstance; - // ensures the correct mode is selected - m_mode = Instance; - } - else - { - m_type = SingleVolume; - requires_disable = true; - } - } - } - else - { - if (m_cache.content.size() == 1) // single object - { - const ModelObject* model_object = m_model->objects[m_cache.content.begin()->first]; - unsigned int model_volumes_count = (unsigned int)model_object->volumes.size(); - unsigned int sla_volumes_count = 0; - for (unsigned int i : m_list) - { - if ((*m_volumes)[i]->volume_idx() < 0) - ++sla_volumes_count; - } - unsigned int volumes_count = model_volumes_count + sla_volumes_count; - unsigned int instances_count = (unsigned int)model_object->instances.size(); - unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); - if (volumes_count * instances_count == (unsigned int)m_list.size()) - { - m_type = SingleFullObject; - // ensures the correct mode is selected - m_mode = Instance; - } - else if (selected_instances_count == 1) - { - if (volumes_count == (unsigned int)m_list.size()) - { - m_type = SingleFullInstance; - // ensures the correct mode is selected - m_mode = Instance; - } - else - { - unsigned int modifiers_count = 0; - for (unsigned int i : m_list) - { - if ((*m_volumes)[i]->is_modifier) - ++modifiers_count; - } - - if (modifiers_count == 0) - { - m_type = MultipleVolume; - requires_disable = true; - } - else if (modifiers_count == (unsigned int)m_list.size()) - { - m_type = MultipleModifier; - requires_disable = true; - } - } - } - else if ((selected_instances_count > 1) && (selected_instances_count * volumes_count == (unsigned int)m_list.size())) - { - m_type = MultipleFullInstance; - // ensures the correct mode is selected - m_mode = Instance; - } - } - else - { - int sels_cntr = 0; - for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) - { - const ModelObject* model_object = m_model->objects[it->first]; - unsigned int volumes_count = (unsigned int)model_object->volumes.size(); - unsigned int instances_count = (unsigned int)model_object->instances.size(); - sels_cntr += volumes_count * instances_count; - } - if (sels_cntr == (unsigned int)m_list.size()) - { - m_type = MultipleFullObject; - // ensures the correct mode is selected - m_mode = Instance; - } - } - } - } - - int object_idx = get_object_idx(); - int instance_idx = get_instance_idx(); - for (GLVolume* v : *m_volumes) - { - v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false; - } - -#if ENABLE_SELECTION_DEBUG_OUTPUT - std::cout << "Selection: "; - std::cout << "mode: "; - switch (m_mode) - { - case Volume: - { - std::cout << "Volume"; - break; - } - case Instance: - { - std::cout << "Instance"; - break; - } - } - - std::cout << " - type: "; - - switch (m_type) - { - case Invalid: - { - std::cout << "Invalid" << std::endl; - break; - } - case Empty: - { - std::cout << "Empty" << std::endl; - break; - } - case WipeTower: - { - std::cout << "WipeTower" << std::endl; - break; - } - case SingleModifier: - { - std::cout << "SingleModifier" << std::endl; - break; - } - case MultipleModifier: - { - std::cout << "MultipleModifier" << std::endl; - break; - } - case SingleVolume: - { - std::cout << "SingleVolume" << std::endl; - break; - } - case MultipleVolume: - { - std::cout << "MultipleVolume" << std::endl; - break; - } - case SingleFullObject: - { - std::cout << "SingleFullObject" << std::endl; - break; - } - case MultipleFullObject: - { - std::cout << "MultipleFullObject" << std::endl; - break; - } - case SingleFullInstance: - { - std::cout << "SingleFullInstance" << std::endl; - break; - } - case MultipleFullInstance: - { - std::cout << "MultipleFullInstance" << std::endl; - break; - } - case Mixed: - { - std::cout << "Mixed" << std::endl; - break; - } - } -#endif // ENABLE_SELECTION_DEBUG_OUTPUT -} - -void GLCanvas3D::Selection::_set_caches() -{ - m_cache.volumes_data.clear(); - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - const GLVolume* v = (*m_volumes)[i]; - m_cache.volumes_data.emplace(i, VolumeCache(v->get_volume_transformation(), v->get_instance_transformation())); - } - m_cache.dragging_center = get_bounding_box().center(); -} - -void GLCanvas3D::Selection::_add_volume(unsigned int volume_idx) -{ - m_list.insert(volume_idx); - (*m_volumes)[volume_idx]->selected = true; -} - -void GLCanvas3D::Selection::_add_instance(unsigned int object_idx, unsigned int instance_idx) -{ - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) - _add_volume(i); - } -} - -void GLCanvas3D::Selection::_add_object(unsigned int object_idx) -{ - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - GLVolume* v = (*m_volumes)[i]; - if (v->object_idx() == object_idx) - _add_volume(i); - } -} - -void GLCanvas3D::Selection::_remove_volume(unsigned int volume_idx) -{ - IndicesList::iterator v_it = m_list.find(volume_idx); - if (v_it == m_list.end()) - return; - - m_list.erase(v_it); - - (*m_volumes)[volume_idx]->selected = false; -} - -void GLCanvas3D::Selection::_remove_instance(unsigned int object_idx, unsigned int instance_idx) -{ - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) - _remove_volume(i); - } -} - -void GLCanvas3D::Selection::_remove_object(unsigned int object_idx) -{ - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - GLVolume* v = (*m_volumes)[i]; - if (v->object_idx() == object_idx) - _remove_volume(i); - } -} - -void GLCanvas3D::Selection::_calc_bounding_box() const -{ - m_bounding_box = BoundingBoxf3(); - if (m_valid) - { - for (unsigned int i : m_list) - { - m_bounding_box.merge((*m_volumes)[i]->transformed_convex_hull_bounding_box()); - } - } - m_bounding_box_dirty = false; -} - -void GLCanvas3D::Selection::_render_selected_volumes() const -{ - float color[3] = { 1.0f, 1.0f, 1.0f }; - _render_bounding_box(get_bounding_box(), color); -} - -void GLCanvas3D::Selection::_render_synchronized_volumes() const -{ - if (m_mode == Instance) - return; - - float color[3] = { 1.0f, 1.0f, 0.0f }; - - for (unsigned int i : m_list) - { - const GLVolume* volume = (*m_volumes)[i]; - int object_idx = volume->object_idx(); - int instance_idx = volume->instance_idx(); - int volume_idx = volume->volume_idx(); - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { - if (i == j) - continue; - - const GLVolume* v = (*m_volumes)[j]; - if ((v->object_idx() != object_idx) || (v->volume_idx() != volume_idx)) - continue; - - _render_bounding_box(v->transformed_convex_hull_bounding_box(), color); - } - } -} - -void GLCanvas3D::Selection::_render_bounding_box(const BoundingBoxf3& box, float* color) const -{ - if (color == nullptr) - return; - - Vec3f b_min = box.min.cast(); - Vec3f b_max = box.max.cast(); - Vec3f size = 0.2f * box.size().cast(); - - ::glEnable(GL_DEPTH_TEST); - - ::glColor3fv(color); - ::glLineWidth(2.0f * m_scale_factor); - - ::glBegin(GL_LINES); - - ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_min(2)); - ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_min(2)); - ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1), b_min(2) + size(2)); - - ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_min(2)); - ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_min(2)); - ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1), b_min(2) + size(2)); - - ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_min(2)); - ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_min(2)); - ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1), b_min(2) + size(2)); - - ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_min(2)); - ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_min(2)); - ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1), b_min(2) + size(2)); - - ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_max(2)); - ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_max(2)); - ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1), b_max(2) - size(2)); - - ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_max(2)); - ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_max(2)); - ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1), b_max(2) - size(2)); - - ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_max(2)); - ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_max(2)); - ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1), b_max(2) - size(2)); - - ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_max(2)); - ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_max(2)); - ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1), b_max(2) - size(2)); - - ::glEnd(); -} - -void GLCanvas3D::Selection::_render_sidebar_position_hints(const std::string& sidebar_field) const -{ - if (boost::ends_with(sidebar_field, "x")) - { - ::glRotated(-90.0, 0.0, 0.0, 1.0); - _render_sidebar_position_hint(X); - } - else if (boost::ends_with(sidebar_field, "y")) - _render_sidebar_position_hint(Y); - else if (boost::ends_with(sidebar_field, "z")) - { - ::glRotated(90.0, 1.0, 0.0, 0.0); - _render_sidebar_position_hint(Z); - } -} - -void GLCanvas3D::Selection::_render_sidebar_rotation_hints(const std::string& sidebar_field) const -{ - if (boost::ends_with(sidebar_field, "x")) - { - ::glRotated(90.0, 0.0, 1.0, 0.0); - _render_sidebar_rotation_hint(X); - } - else if (boost::ends_with(sidebar_field, "y")) - { - ::glRotated(-90.0, 1.0, 0.0, 0.0); - _render_sidebar_rotation_hint(Y); - } - else if (boost::ends_with(sidebar_field, "z")) - _render_sidebar_rotation_hint(Z); -} - -void GLCanvas3D::Selection::_render_sidebar_scale_hints(const std::string& sidebar_field) const -{ - bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); - - if (boost::ends_with(sidebar_field, "x") || uniform_scale) - { - ::glPushMatrix(); - ::glRotated(-90.0, 0.0, 0.0, 1.0); - _render_sidebar_scale_hint(X); - ::glPopMatrix(); - } - - if (boost::ends_with(sidebar_field, "y") || uniform_scale) - { - ::glPushMatrix(); - _render_sidebar_scale_hint(Y); - ::glPopMatrix(); - } - - if (boost::ends_with(sidebar_field, "z") || uniform_scale) - { - ::glPushMatrix(); - ::glRotated(90.0, 1.0, 0.0, 0.0); - _render_sidebar_scale_hint(Z); - ::glPopMatrix(); - } -} - -void GLCanvas3D::Selection::_render_sidebar_size_hints(const std::string& sidebar_field) const -{ - _render_sidebar_scale_hints(sidebar_field); -} - -void GLCanvas3D::Selection::_render_sidebar_position_hint(Axis axis) const -{ - m_arrow.set_color(AXES_COLOR[axis], 3); - m_arrow.render(); -} - -void GLCanvas3D::Selection::_render_sidebar_rotation_hint(Axis axis) const -{ - m_curved_arrow.set_color(AXES_COLOR[axis], 3); - m_curved_arrow.render(); - - ::glRotated(180.0, 0.0, 0.0, 1.0); - m_curved_arrow.render(); -} - -void GLCanvas3D::Selection::_render_sidebar_scale_hint(Axis axis) const -{ - m_arrow.set_color(((requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis]), 3); - - ::glTranslated(0.0, 5.0, 0.0); - m_arrow.render(); - - ::glTranslated(0.0, -10.0, 0.0); - ::glRotated(180.0, 0.0, 0.0, 1.0); - m_arrow.render(); -} - -void GLCanvas3D::Selection::_render_sidebar_size_hint(Axis axis, double length) const -{ -} - -#ifndef NDEBUG -static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) -{ - Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); - Vec3d axis = angle_axis.axis(); - double angle = angle_axis.angle(); - if (std::abs(angle) < 1e-8) - return true; - assert(std::abs(axis.x()) < 1e-8); - assert(std::abs(axis.y()) < 1e-8); - assert(std::abs(std::abs(axis.z()) - 1.) < 1e-8); - return std::abs(axis.x()) < 1e-8 && std::abs(axis.y()) < 1e-8 && std::abs(std::abs(axis.z()) - 1.) < 1e-8; -} -static void verify_instances_rotation_synchronized(const Model &model, const GLVolumePtrs &volumes) -{ - for (size_t idx_object = 0; idx_object < model.objects.size(); ++ idx_object) { - int idx_volume_first = -1; - for (int i = 0; i < (int)volumes.size(); ++ i) { - if (volumes[i]->object_idx() == idx_object) { - idx_volume_first = i; - break; - } - } - assert(idx_volume_first != -1); // object without instances? - if (idx_volume_first == -1) - continue; - const Vec3d &rotation0 = volumes[idx_volume_first]->get_instance_rotation(); - for (int i = idx_volume_first + 1; i < (int)volumes.size(); ++ i) - if (volumes[i]->object_idx() == idx_object) { - const Vec3d &rotation = volumes[i]->get_instance_rotation(); - assert(is_rotation_xy_synchronized(rotation, rotation0)); - } - } -} -#endif /* NDEBUG */ - -void GLCanvas3D::Selection::_synchronize_unselected_instances(SyncRotationType sync_rotation_type) -{ - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) - { - if (done.size() == m_volumes->size()) - break; - - const GLVolume* volume = (*m_volumes)[i]; - int object_idx = volume->object_idx(); - if (object_idx >= 1000) - continue; - - int instance_idx = volume->instance_idx(); - const Vec3d& rotation = volume->get_instance_rotation(); - const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); - const Vec3d& mirror = volume->get_instance_mirror(); - - // Process unselected instances. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume* v = (*m_volumes)[j]; - if ((v->object_idx() != object_idx) || (v->instance_idx() == instance_idx)) - continue; - - assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); - switch (sync_rotation_type) { - case SYNC_ROTATION_NONE: - // z only rotation -> keep instance z - // The X,Y rotations should be synchronized from start to end of the rotation. - assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); - break; - case SYNC_ROTATION_FULL: - // rotation comes from place on face -> force given z - v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2))); - break; - case SYNC_ROTATION_GENERAL: - // generic rotation -> update instance z with the delta of the rotation. - double z_diff = rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); - v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); - break; - } - - v->set_instance_scaling_factor(scaling_factor); - v->set_instance_mirror(mirror); - - done.insert(j); - } - } - -#ifndef NDEBUG - verify_instances_rotation_synchronized(*m_model, *m_volumes); -#endif /* NDEBUG */ -} - -void GLCanvas3D::Selection::_synchronize_unselected_volumes() -{ - for (unsigned int i : m_list) - { - const GLVolume* volume = (*m_volumes)[i]; - int object_idx = volume->object_idx(); - if (object_idx >= 1000) - continue; - - int volume_idx = volume->volume_idx(); - const Vec3d& offset = volume->get_volume_offset(); - const Vec3d& rotation = volume->get_volume_rotation(); - const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); - const Vec3d& mirror = volume->get_volume_mirror(); - - // Process unselected volumes. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { - if (j == i) - continue; - - GLVolume* v = (*m_volumes)[j]; - if ((v->object_idx() != object_idx) || (v->volume_idx() != volume_idx)) - continue; - - v->set_volume_offset(offset); - v->set_volume_rotation(rotation); - v->set_volume_scaling_factor(scaling_factor); - v->set_volume_mirror(mirror); - } - } -} - -void GLCanvas3D::Selection::_ensure_on_bed() -{ - typedef std::map, double> InstancesToZMap; - InstancesToZMap instances_min_z; - - for (GLVolume* volume : *m_volumes) - { - if (!volume->is_wipe_tower && !volume->is_modifier) - { - double min_z = volume->transformed_convex_hull_bounding_box().min(2); - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_min_z.find(instance); - if (it == instances_min_z.end()) - it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; - - it->second = std::min(it->second, min_z); - } - } - - for (GLVolume* volume : *m_volumes) - { - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_min_z.find(instance); - if (it != instances_min_z.end()) - volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second); - } -} +#if ENABLE_SVG_ICONS +const float GLCanvas3D::Gizmos::Default_Icons_Size = 64; +#endif // ENABLE_SVG_ICONS GLCanvas3D::Gizmos::Gizmos() : m_enabled(false) +#if ENABLE_SVG_ICONS + , m_icons_texture_dirty(true) +#endif // ENABLE_SVG_ICONS , m_current(Undefined) +#if ENABLE_SVG_ICONS + , m_overlay_icons_size(Default_Icons_Size) + , m_overlay_scale(1.0f) + , m_overlay_border(5.0f) + , m_overlay_gap_y(5.0f) +{ +} +#else { set_overlay_scale(1.0); } +#endif // ENABLE_SVG_ICONS GLCanvas3D::Gizmos::~Gizmos() { - _reset(); + reset(); } bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent) { - GLGizmoBase* gizmo = new GLGizmoMove3D(parent); - if (gizmo == nullptr) - return false; +#if !ENABLE_SVG_ICONS + m_icons_texture.metadata.filename = "gizmos.png"; + m_icons_texture.metadata.icon_size = 64; - if (!gizmo->init()) - return false; - - m_gizmos.insert(GizmosMap::value_type(Move, gizmo)); - - gizmo = new GLGizmoScale3D(parent); - if (gizmo == nullptr) - return false; - - if (!gizmo->init()) - return false; - - m_gizmos.insert(GizmosMap::value_type(Scale, gizmo)); - - gizmo = new GLGizmoRotate3D(parent); - if (gizmo == nullptr) + if (!m_icons_texture.metadata.filename.empty()) { - _reset(); - return false; + if (!m_icons_texture.texture.load_from_file(resources_dir() + "/icons/" + m_icons_texture.metadata.filename, false)) + { + reset(); + return false; + } } - - if (!gizmo->init()) - { - _reset(); - return false; - } - - m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo)); - - gizmo = new GLGizmoFlatten(parent); - if (gizmo == nullptr) - return false; - - if (!gizmo->init()) { - _reset(); - return false; - } - - m_gizmos.insert(GizmosMap::value_type(Flatten, gizmo)); - - gizmo = new GLGizmoCut(parent); - if (gizmo == nullptr) - return false; - - if (!gizmo->init()) { - _reset(); - return false; - } - - m_gizmos.insert(GizmosMap::value_type(Cut, gizmo)); - - gizmo = new GLGizmoSlaSupports(parent); - if (gizmo == nullptr) - return false; - - if (!gizmo->init()) { - _reset(); - return false; - } - - m_gizmos.insert(GizmosMap::value_type(SlaSupports, gizmo)); +#endif // !ENABLE_SVG_ICONS m_background_texture.metadata.filename = "toolbar_background.png"; m_background_texture.metadata.left = 16; @@ -2502,11 +789,101 @@ bool GLCanvas3D::Gizmos::init(GLCanvas3D& parent) { if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false)) { - _reset(); + reset(); return false; } } +#if ENABLE_SVG_ICONS + GLGizmoBase* gizmo = new GLGizmoMove3D(parent, "move.svg", 0); +#else + GLGizmoBase* gizmo = new GLGizmoMove3D(parent, 0); +#endif // ENABLE_SVG_ICONS + if (gizmo == nullptr) + return false; + + if (!gizmo->init()) + return false; + + m_gizmos.insert(GizmosMap::value_type(Move, gizmo)); + +#if ENABLE_SVG_ICONS + gizmo = new GLGizmoScale3D(parent, "scale.svg", 1); +#else + gizmo = new GLGizmoScale3D(parent, 1); +#endif // ENABLE_SVG_ICONS + if (gizmo == nullptr) + return false; + + if (!gizmo->init()) + return false; + + m_gizmos.insert(GizmosMap::value_type(Scale, gizmo)); + +#if ENABLE_SVG_ICONS + gizmo = new GLGizmoRotate3D(parent, "rotate.svg", 2); +#else + gizmo = new GLGizmoRotate3D(parent, 2); +#endif // ENABLE_SVG_ICONS + if (gizmo == nullptr) + { + reset(); + return false; + } + + if (!gizmo->init()) + { + reset(); + return false; + } + + m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo)); + +#if ENABLE_SVG_ICONS + gizmo = new GLGizmoFlatten(parent, "place.svg", 3); +#else + gizmo = new GLGizmoFlatten(parent, 3); +#endif // ENABLE_SVG_ICONS + if (gizmo == nullptr) + return false; + + if (!gizmo->init()) { + reset(); + return false; + } + + m_gizmos.insert(GizmosMap::value_type(Flatten, gizmo)); + +#if ENABLE_SVG_ICONS + gizmo = new GLGizmoCut(parent, "cut.svg", 4); +#else + gizmo = new GLGizmoCut(parent, 4); +#endif // ENABLE_SVG_ICONS + if (gizmo == nullptr) + return false; + + if (!gizmo->init()) { + reset(); + return false; + } + + m_gizmos.insert(GizmosMap::value_type(Cut, gizmo)); + +#if ENABLE_SVG_ICONS + gizmo = new GLGizmoSlaSupports(parent, "sla_supports.svg", 5); +#else + gizmo = new GLGizmoSlaSupports(parent, 5); +#endif // ENABLE_SVG_ICONS + if (gizmo == nullptr) + return false; + + if (!gizmo->init()) { + reset(); + return false; + } + + m_gizmos.insert(GizmosMap::value_type(SlaSupports, gizmo)); + return true; } @@ -2520,14 +897,33 @@ void GLCanvas3D::Gizmos::set_enabled(bool enable) m_enabled = enable; } +#if ENABLE_SVG_ICONS +void GLCanvas3D::Gizmos::set_overlay_icon_size(float size) +{ + if (m_overlay_icons_size != size) + { + m_overlay_icons_size = size; + m_icons_texture_dirty = true; + } +} +#endif // ENABLE_SVG_ICONS + void GLCanvas3D::Gizmos::set_overlay_scale(float scale) { +#if ENABLE_SVG_ICONS + if (m_overlay_scale != scale) + { + m_overlay_scale = scale; + m_icons_texture_dirty = true; + } +#else m_overlay_icons_scale = scale; m_overlay_border = 5.0f * scale; m_overlay_gap_y = 5.0f * scale; +#endif // ENABLE_SVG_ICONS } -std::string GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const GLCanvas3D::Selection& selection) +std::string GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection) { std::string name = ""; @@ -2535,45 +931,73 @@ std::string GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, con return name; float cnv_h = (float)canvas.get_canvas_size().get_height(); - float height = _get_total_overlay_height(); + float height = get_total_overlay_height(); +#if ENABLE_SVG_ICONS + float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; + float scaled_border = m_overlay_border * m_overlay_scale; + float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; + float scaled_stride_y = scaled_icons_size + scaled_gap_y; + float top_y = 0.5f * (cnv_h - height) + scaled_border; +#else float top_y = 0.5f * (cnv_h - height) + m_overlay_border; + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; +#endif // ENABLE_SVG_ICONS + for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float icon_size = (float)it->second->get_textures_size() * m_overlay_icons_scale; - - bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size); +#if ENABLE_SVG_ICONS + bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); +#else + bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); +#endif // ENABLE_SVG_ICONS if (inside) name = it->second->get_name(); if (it->second->is_activable(selection) && (it->second->get_state() != GLGizmoBase::On)) it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off); - top_y += (icon_size + m_overlay_gap_y); +#if ENABLE_SVG_ICONS + top_y += scaled_stride_y; +#else + top_y += (scaled_icons_size + m_overlay_gap_y); +#endif // ENABLE_SVG_ICONS } return name; } -void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const GLCanvas3D::Selection& selection) +void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection) { if (!m_enabled) return; float cnv_h = (float)canvas.get_canvas_size().get_height(); - float height = _get_total_overlay_height(); + float height = get_total_overlay_height(); + +#if ENABLE_SVG_ICONS + float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; + float scaled_border = m_overlay_border * m_overlay_scale; + float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; + float scaled_stride_y = scaled_icons_size + scaled_gap_y; + float top_y = 0.5f * (cnv_h - height) + scaled_border; +#else float top_y = 0.5f * (cnv_h - height) + m_overlay_border; + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; +#endif // ENABLE_SVG_ICONS for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float icon_size = (float)it->second->get_textures_size() * m_overlay_icons_scale; - - bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size); +#if ENABLE_SVG_ICONS + bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); +#else + bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); +#endif // ENABLE_SVG_ICONS if (it->second->is_activable(selection) && inside) { if ((it->second->get_state() == GLGizmoBase::On)) @@ -2590,7 +1014,11 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec else it->second->set_state(GLGizmoBase::Off); - top_y += (icon_size + m_overlay_gap_y); +#if ENABLE_SVG_ICONS + top_y += scaled_stride_y; +#else + top_y += (scaled_icons_size + m_overlay_gap_y); +#endif // ENABLE_SVG_ICONS } GizmosMap::iterator it = m_gizmos.find(m_current); @@ -2661,19 +1089,36 @@ bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const return false; float cnv_h = (float)canvas.get_canvas_size().get_height(); - float height = _get_total_overlay_height(); + float height = get_total_overlay_height(); + +#if ENABLE_SVG_ICONS + float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; + float scaled_border = m_overlay_border * m_overlay_scale; + float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; + float scaled_stride_y = scaled_icons_size + scaled_gap_y; + float top_y = 0.5f * (cnv_h - height) + scaled_border; +#else float top_y = 0.5f * (cnv_h - height) + m_overlay_border; + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; +#endif // ENABLE_SVG_ICONS + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float icon_size = (float)it->second->get_textures_size() * m_overlay_icons_scale; - - if ((m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size)) +#if ENABLE_SVG_ICONS + if ((scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size)) +#else + if ((m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size)) +#endif // ENABLE_SVG_ICONS return true; - top_y += (icon_size + m_overlay_gap_y); +#if ENABLE_SVG_ICONS + top_y += scaled_stride_y; +#else + top_y += (scaled_icons_size + m_overlay_gap_y); +#endif // ENABLE_SVG_ICONS } return false; @@ -2684,7 +1129,7 @@ bool GLCanvas3D::Gizmos::grabber_contains_mouse() const if (!m_enabled) return false; - GLGizmoBase* curr = _get_current(); + GLGizmoBase* curr = get_current(); return (curr != nullptr) ? (curr->get_hover_id() != -1) : false; } @@ -2693,7 +1138,7 @@ void GLCanvas3D::Gizmos::update(const Linef3& mouse_ray, const Selection& select if (!m_enabled) return; - GLGizmoBase* curr = _get_current(); + GLGizmoBase* curr = get_current(); if (curr != nullptr) curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos, shift_down), selection); } @@ -2708,7 +1153,7 @@ bool GLCanvas3D::Gizmos::is_running() const if (!m_enabled) return false; - GLGizmoBase* curr = _get_current(); + GLGizmoBase* curr = get_current(); return (curr != nullptr) ? (curr->get_state() == GLGizmoBase::On) : false; } @@ -2758,16 +1203,16 @@ bool GLCanvas3D::Gizmos::is_dragging() const if (!m_enabled) return false; - GLGizmoBase* curr = _get_current(); + GLGizmoBase* curr = get_current(); return (curr != nullptr) ? curr->is_dragging() : false; } -void GLCanvas3D::Gizmos::start_dragging(const GLCanvas3D::Selection& selection) +void GLCanvas3D::Gizmos::start_dragging(const Selection& selection) { if (!m_enabled) return; - GLGizmoBase* curr = _get_current(); + GLGizmoBase* curr = get_current(); if (curr != nullptr) curr->start_dragging(selection); } @@ -2777,7 +1222,7 @@ void GLCanvas3D::Gizmos::stop_dragging() if (!m_enabled) return; - GLGizmoBase* curr = _get_current(); + GLGizmoBase* curr = get_current(); if (curr != nullptr) curr->stop_dragging(); } @@ -2848,7 +1293,7 @@ void GLCanvas3D::Gizmos::set_flattening_data(const ModelObject* model_object) reinterpret_cast(it->second)->set_flattening_data(model_object); } -void GLCanvas3D::Gizmos::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection) +void GLCanvas3D::Gizmos::set_sla_support_data(ModelObject* model_object, const Selection& selection) { if (!m_enabled) return; @@ -2860,61 +1305,58 @@ void GLCanvas3D::Gizmos::set_sla_support_data(ModelObject* model_object, const G // Returns true if the gizmo used the event to do something, false otherwise. -bool GLCanvas3D::Gizmos::mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down) +bool GLCanvas3D::Gizmos::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down) { if (!m_enabled) return false; GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); if (it != m_gizmos.end()) - return reinterpret_cast(it->second)->mouse_event(action, mouse_position, shift_down); + return reinterpret_cast(it->second)->gizmo_event(action, mouse_position, shift_down); return false; } -void GLCanvas3D::Gizmos::render_current_gizmo(const GLCanvas3D::Selection& selection) const +void GLCanvas3D::Gizmos::render_current_gizmo(const Selection& selection) const { if (!m_enabled) return; - _render_current_gizmo(selection); + do_render_current_gizmo(selection); } -void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const GLCanvas3D::Selection& selection) const +void GLCanvas3D::Gizmos::render_current_gizmo_for_picking_pass(const Selection& selection) const { if (!m_enabled) return; - GLGizmoBase* curr = _get_current(); + GLGizmoBase* curr = get_current(); if (curr != nullptr) curr->render_for_picking(selection); } -void GLCanvas3D::Gizmos::render_overlay(const GLCanvas3D& canvas, const GLCanvas3D::Selection& selection) const +void GLCanvas3D::Gizmos::render_overlay(const GLCanvas3D& canvas, const Selection& selection) const { if (!m_enabled) return; +#if ENABLE_SVG_ICONS + if (m_icons_texture_dirty) + generate_icons_texture(); +#endif // ENABLE_SVG_ICONS + ::glDisable(GL_DEPTH_TEST); ::glPushMatrix(); ::glLoadIdentity(); - _render_overlay(canvas, selection); + do_render_overlay(canvas, selection); ::glPopMatrix(); } -#if !ENABLE_IMGUI -void GLCanvas3D::Gizmos::create_external_gizmo_widgets(wxWindow *parent) -{ - for (auto &entry : m_gizmos) { - entry.second->create_external_gizmo_widgets(parent); - } -} -#endif // not ENABLE_IMGUI -void GLCanvas3D::Gizmos::_reset() +void GLCanvas3D::Gizmos::reset() { for (GizmosMap::value_type& gizmo : m_gizmos) { @@ -2925,27 +1367,30 @@ void GLCanvas3D::Gizmos::_reset() m_gizmos.clear(); } -void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas, const GLCanvas3D::Selection& selection) const +void GLCanvas3D::Gizmos::do_render_overlay(const GLCanvas3D& canvas, const Selection& selection) const { if (m_gizmos.empty()) return; float cnv_w = (float)canvas.get_canvas_size().get_width(); -#if ENABLE_IMGUI float cnv_h = (float)canvas.get_canvas_size().get_height(); -#endif // ENABLE_IMGUI float zoom = canvas.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - float height = _get_total_overlay_height(); + float height = get_total_overlay_height(); + float width = get_total_overlay_width(); +#if ENABLE_SVG_ICONS + float scaled_border = m_overlay_border * m_overlay_scale * inv_zoom; +#else float scaled_border = m_overlay_border * inv_zoom; +#endif // ENABLE_SVG_ICONS float top_x = (-0.5f * cnv_w) * inv_zoom; float top_y = (0.5f * height) * inv_zoom; float left = top_x; float top = top_y; - float right = left + _get_total_overlay_width() * inv_zoom; + float right = left + width * inv_zoom; float bottom = top - height * inv_zoom; // renders background @@ -3014,67 +1459,158 @@ void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas, const GLCanva } } +#if ENABLE_SVG_ICONS + top_x += scaled_border; + top_y -= scaled_border; + float scaled_gap_y = m_overlay_gap_y * m_overlay_scale * inv_zoom; + + float scaled_icons_size = m_overlay_icons_size * m_overlay_scale * inv_zoom; + float scaled_stride_y = scaled_icons_size + scaled_gap_y; + unsigned int icons_texture_id = m_icons_texture.get_id(); + unsigned int tex_width = m_icons_texture.get_width(); + unsigned int tex_height = m_icons_texture.get_height(); + float inv_tex_width = (tex_width != 0) ? 1.0f / (float)tex_width : 0.0f; + float inv_tex_height = (tex_height != 0) ? 1.0f / (float)tex_height : 0.0f; +#else top_x += m_overlay_border * inv_zoom; top_y -= m_overlay_border * inv_zoom; float scaled_gap_y = m_overlay_gap_y * inv_zoom; + + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale * inv_zoom; + unsigned int icons_texture_id = m_icons_texture.texture.get_id(); + unsigned int texture_size = m_icons_texture.texture.get_width(); + float inv_texture_size = (texture_size != 0) ? 1.0f / (float)texture_size : 0.0f; +#endif // ENABLE_SVG_ICONS + +#if ENABLE_SVG_ICONS + if ((icons_texture_id == 0) || (tex_width <= 0) || (tex_height <= 0)) + return; +#endif // ENABLE_SVG_ICONS + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float icon_size = (float)it->second->get_textures_size() * m_overlay_icons_scale * inv_zoom; - GLTexture::render_texture(it->second->get_texture_id(), top_x, top_x + icon_size, top_y - icon_size, top_y); -#if ENABLE_IMGUI - if (it->second->get_state() == GLGizmoBase::On) - it->second->render_input_window(2.0f * m_overlay_border + icon_size * zoom, 0.5f * cnv_h - top_y * zoom, selection); -#endif // ENABLE_IMGUI - top_y -= (icon_size + scaled_gap_y); + unsigned int sprite_id = it->second->get_sprite_id(); + GLGizmoBase::EState state = it->second->get_state(); + +#if ENABLE_SVG_ICONS + float u_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_width; + float v_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_height; + float v_top = sprite_id * v_icon_size; + float u_left = state * u_icon_size; + float v_bottom = v_top + v_icon_size; + float u_right = u_left + u_icon_size; +#else + float uv_icon_size = (float)m_icons_texture.metadata.icon_size * inv_texture_size; + float v_top = sprite_id * uv_icon_size; + float u_left = state * uv_icon_size; + float v_bottom = v_top + uv_icon_size; + float u_right = u_left + uv_icon_size; +#endif // ENABLE_SVG_ICONS + + GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + scaled_icons_size, top_y - scaled_icons_size, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); + if (it->second->get_state() == GLGizmoBase::On) { + float toolbar_top = (float)cnv_h - canvas.m_view_toolbar.get_height(); +#if ENABLE_SVG_ICONS + it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); +#else + it->second->render_input_window(2.0f * m_overlay_border + icon_size * zoom, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); +#endif // ENABLE_SVG_ICONS + } +#if ENABLE_SVG_ICONS + top_y -= scaled_stride_y; +#else + top_y -= (scaled_icons_size + scaled_gap_y); +#endif // ENABLE_SVG_ICONS } } -void GLCanvas3D::Gizmos::_render_current_gizmo(const GLCanvas3D::Selection& selection) const +void GLCanvas3D::Gizmos::do_render_current_gizmo(const Selection& selection) const { - GLGizmoBase* curr = _get_current(); + GLGizmoBase* curr = get_current(); if (curr != nullptr) curr->render(selection); } -float GLCanvas3D::Gizmos::_get_total_overlay_height() const +float GLCanvas3D::Gizmos::get_total_overlay_height() const { +#if ENABLE_SVG_ICONS + float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; + float scaled_border = m_overlay_border * m_overlay_scale; + float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; + float scaled_stride_y = scaled_icons_size + scaled_gap_y; + float height = 2.0f * scaled_border; +#else float height = 2.0f * m_overlay_border; + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; +#endif // ENABLE_SVG_ICONS + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - height += (float)it->second->get_textures_size() * m_overlay_icons_scale + m_overlay_gap_y; +#if ENABLE_SVG_ICONS + height += scaled_stride_y; +#else + height += (scaled_icons_size + m_overlay_gap_y); +#endif // ENABLE_SVG_ICONS } +#if ENABLE_SVG_ICONS + return height - scaled_gap_y; +#else return height - m_overlay_gap_y; +#endif // ENABLE_SVG_ICONS } -float GLCanvas3D::Gizmos::_get_total_overlay_width() const +float GLCanvas3D::Gizmos::get_total_overlay_width() const { - float max_icon_width = 0.0f; - for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) - { - if ((it->second == nullptr) || !it->second->is_selectable()) - continue; - - max_icon_width = std::max(max_icon_width, (float)it->second->get_textures_size() * m_overlay_icons_scale); - } - - return max_icon_width + 2.0f * m_overlay_border; +#if ENABLE_SVG_ICONS + return (2.0f * m_overlay_border + m_overlay_icons_size) * m_overlay_scale; +#else + return (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale + 2.0f * m_overlay_border; +#endif // ENABLE_SVG_ICONS } -GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const +GLGizmoBase* GLCanvas3D::Gizmos::get_current() const { GizmosMap::const_iterator it = m_gizmos.find(m_current); return (it != m_gizmos.end()) ? it->second : nullptr; } -const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 9, 91, 134 }; +#if ENABLE_SVG_ICONS +bool GLCanvas3D::Gizmos::generate_icons_texture() const +{ + std::string path = resources_dir() + "/icons/"; + std::vector filenames; + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if (it->second != nullptr) + { + const std::string& icon_filename = it->second->get_icon_filename(); + if (!icon_filename.empty()) + filenames.push_back(path + icon_filename); + } + } + + std::vector> states; + states.push_back(std::make_pair(1, false)); + states.push_back(std::make_pair(0, false)); + states.push_back(std::make_pair(0, true)); + + bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_overlay_icons_size * m_overlay_scale)); + if (res) + m_icons_texture_dirty = false; + + return res; +} +#endif // ENABLE_SVG_ICONS + +const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 120, 120, 120 };//{ 9, 91, 134 }; const unsigned char GLCanvas3D::WarningTexture::Opacity = 255; GLCanvas3D::WarningTexture::WarningTexture() @@ -3108,22 +1644,85 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool // Look at the end of our vector and generate proper texture. std::string text; + bool red_colored = false; switch (m_warnings.back()) { case ObjectOutside : text = L("Detected object outside print volume"); break; case ToolpathOutside : text = L("Detected toolpath outside print volume"); break; case SomethingNotShown : text = L("Some objects are not visible when editing supports"); break; + case ObjectClashed: { + text = L("Detected object outside print volume\n" + "Resolve a clash to continue slicing/export process correctly"); + red_colored = true; + break; + } } - _generate(text, canvas); // GUI::GLTexture::reset() is called at the beginning of generate(...) + _generate(text, canvas, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...) } -bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanvas3D& canvas) + +#ifdef __WXMSW__ +static bool is_font_cleartype(const wxFont &font) +{ + // Native font description: on MSW, it is a version number plus the content of LOGFONT, separated by semicolon. + wxString font_desc = font.GetNativeFontInfoDesc(); + // Find the quality field. + wxString sep(";"); + size_t startpos = 0; + for (size_t i = 0; i < 12; ++ i) + startpos = font_desc.find(sep, startpos + 1); + ++ startpos; + size_t endpos = font_desc.find(sep, startpos); + int quality = wxAtoi(font_desc(startpos, endpos - startpos)); + return quality == CLEARTYPE_QUALITY; +} + +// ClearType produces renders, which are difficult to convert into an alpha blended OpenGL texture. +// Therefore it is better to disable it, though Vojtech found out, that the font returned with ClearType +// disabled is signifcantly thicker than the default ClearType font. +// This function modifies the font provided. +static void msw_disable_cleartype(wxFont &font) +{ + // Native font description: on MSW, it is a version number plus the content of LOGFONT, separated by semicolon. + wxString font_desc = font.GetNativeFontInfoDesc(); + // Find the quality field. + wxString sep(";"); + size_t startpos_weight = 0; + for (size_t i = 0; i < 5; ++ i) + startpos_weight = font_desc.find(sep, startpos_weight + 1); + ++ startpos_weight; + size_t endpos_weight = font_desc.find(sep, startpos_weight); + // Parse the weight field. + unsigned int weight = atoi(font_desc(startpos_weight, endpos_weight - startpos_weight)); + size_t startpos = endpos_weight; + for (size_t i = 0; i < 6; ++ i) + startpos = font_desc.find(sep, startpos + 1); + ++ startpos; + size_t endpos = font_desc.find(sep, startpos); + int quality = wxAtoi(font_desc(startpos, endpos - startpos)); + if (quality == CLEARTYPE_QUALITY) { + // Replace the weight with a smaller value to compensate the weight of non ClearType font. + wxString sweight = std::to_string(weight * 2 / 4); + size_t len_weight = endpos_weight - startpos_weight; + wxString squality = std::to_string(ANTIALIASED_QUALITY); + font_desc.replace(startpos_weight, len_weight, sweight); + font_desc.replace(startpos + sweight.size() - len_weight, endpos - startpos, squality); + font.SetNativeFontInfo(font_desc); + wxString font_desc2 = font.GetNativeFontInfoDesc(); + } + wxString font_desc2 = font.GetNativeFontInfoDesc(); +} +#endif /* __WXMSW__ */ + +bool GLCanvas3D::WarningTexture::_generate(const std::string& msg_utf8, const GLCanvas3D& canvas, const bool red_colored/* = false*/) { reset(); - if (msg.empty()) + if (msg_utf8.empty()) return false; + wxString msg = GUI::from_u8(msg_utf8); + wxMemoryDC memDC; // select default font const float scale = canvas.get_canvas_size().get_scale_factor(); @@ -3134,44 +1733,47 @@ bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanva // calculates texture size wxCoord w, h; - memDC.GetTextExtent(msg, &w, &h); - - int pow_of_two_size = next_highest_power_of_2(std::max(w, h)); + memDC.GetMultiLineTextExtent(msg, &w, &h); m_original_width = (int)w; m_original_height = (int)h; - m_width = pow_of_two_size; - m_height = pow_of_two_size; + m_width = (int)next_highest_power_of_2((uint32_t)w); + m_height = (int)next_highest_power_of_2((uint32_t)h); // generates bitmap wxBitmap bitmap(m_width, m_height); memDC.SelectObject(bitmap); - memDC.SetBackground(wxBrush(wxColour(Background_Color[0], Background_Color[1], Background_Color[2]))); + memDC.SetBackground(wxBrush(*wxBLACK)); memDC.Clear(); // draw message - memDC.SetTextForeground(*wxWHITE); - memDC.DrawText(msg, 0, 0); + memDC.SetTextForeground(*wxRED); + memDC.DrawLabel(msg, wxRect(0,0, m_original_width, m_original_height), wxALIGN_CENTER); memDC.SelectObject(wxNullBitmap); // Convert the bitmap into a linear data ready to be loaded into the GPU. wxImage image = bitmap.ConvertToImage(); - image.SetMaskColour(Background_Color[0], Background_Color[1], Background_Color[2]); // prepare buffer std::vector data(4 * m_width * m_height, 0); + const unsigned char *src = image.GetData(); for (int h = 0; h < m_height; ++h) { - int hh = h * m_width; - unsigned char* px_ptr = data.data() + 4 * hh; + unsigned char* dst = data.data() + 4 * h * m_width; for (int w = 0; w < m_width; ++w) { - *px_ptr++ = image.GetRed(w, h); - *px_ptr++ = image.GetGreen(w, h); - *px_ptr++ = image.GetBlue(w, h); - *px_ptr++ = image.IsTransparent(w, h) ? 0 : Opacity; + *dst++ = 255; + if (red_colored) { + *dst++ = 72; // 204 + *dst++ = 65; // 204 + } else { + *dst++ = 255; + *dst++ = 255; + } + *dst++ = (unsigned char)std::min(255, *src); + src += 3; } } @@ -3182,7 +1784,7 @@ bool GLCanvas3D::WarningTexture::_generate(const std::string& msg, const GLCanva ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); ::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); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); ::glBindTexture(GL_TEXTURE_2D, 0); return true; @@ -3265,7 +1867,7 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePrevie } } -bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas, bool use_error_colors) +bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas) { reset(); @@ -3295,7 +1897,15 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c const int scaled_border = Px_Border * scale; // select default font - const wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Scale(scale_gl); + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Scale(scale_gl); +#ifdef __WXMSW__ + // Disabling ClearType works, but the font returned is very different (much thicker) from the default. +// msw_disable_cleartype(font); + bool cleartype = is_font_cleartype(font); +#else + bool cleartype = false; +#endif /* __WXMSW__ */ + memDC.SetFont(font); mask_memDC.SetFont(font); @@ -3319,10 +1929,8 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c if (items_count > 1) m_original_height += (items_count - 1) * scaled_square_contour; - int pow_of_two_size = (int)next_highest_power_of_2(std::max(m_original_width, m_original_height)); - - m_width = pow_of_two_size; - m_height = pow_of_two_size; + m_width = (int)next_highest_power_of_2((uint32_t)m_original_width); + m_height = (int)next_highest_power_of_2((uint32_t)m_original_height); // generates bitmap wxBitmap bitmap(m_width, m_height); @@ -3331,24 +1939,21 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c memDC.SelectObject(bitmap); mask_memDC.SelectObject(mask); - memDC.SetBackground(wxBrush(use_error_colors ? *wxWHITE : *wxBLACK)); + memDC.SetBackground(wxBrush(*wxBLACK)); mask_memDC.SetBackground(wxBrush(*wxBLACK)); memDC.Clear(); mask_memDC.Clear(); // draw title - memDC.SetTextForeground(use_error_colors ? *wxWHITE : *wxBLACK); - mask_memDC.SetTextForeground(*wxWHITE); + memDC.SetTextForeground(*wxWHITE); + mask_memDC.SetTextForeground(*wxRED); int title_x = scaled_border; int title_y = scaled_border; memDC.DrawText(title, title_x, title_y); mask_memDC.DrawText(title, title_x, title_y); - mask_memDC.SetPen(wxPen(*wxWHITE)); - mask_memDC.SetBrush(wxBrush(*wxWHITE)); - // draw icons contours as background int squares_contour_x = scaled_border; int squares_contour_y = scaled_border + title_height + scaled_title_offset; @@ -3363,7 +1968,6 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c memDC.SetPen(pen); memDC.SetBrush(brush); memDC.DrawRectangle(wxRect(squares_contour_x, squares_contour_y, squares_contour_width, squares_contour_height)); - mask_memDC.DrawRectangle(wxRect(squares_contour_x, squares_contour_y, squares_contour_width, squares_contour_height)); // draw items (colored icon + text) int icon_x = squares_contour_x + scaled_square_contour; @@ -3399,7 +2003,6 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c memDC.DrawRectangle(wxRect(icon_x_inner, icon_y + 1, px_inner_square, px_inner_square)); // draw text - memDC.DrawText(GUI::from_u8(item.text), text_x, icon_y + text_y_offset); mask_memDC.DrawText(GUI::from_u8(item.text), text_x, icon_y + text_y_offset); // update y @@ -3415,16 +2018,34 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c // prepare buffer std::vector data(4 * m_width * m_height, 0); - for (int h = 0; h < m_height; ++h) + const unsigned char *src_image = image.GetData(); + const unsigned char *src_mask = mask_image.GetData(); + for (int h = 0; h < m_height; ++h) { int hh = h * m_width; unsigned char* px_ptr = data.data() + 4 * hh; for (int w = 0; w < m_width; ++w) { - *px_ptr++ = image.GetRed(w, h); - *px_ptr++ = image.GetGreen(w, h); - *px_ptr++ = image.GetBlue(w, h); - *px_ptr++ = (mask_image.GetRed(w, h) + mask_image.GetGreen(w, h) + mask_image.GetBlue(w, h)) / 3; + if (w >= squares_contour_x && w < squares_contour_x + squares_contour_width && + h >= squares_contour_y && h < squares_contour_y + squares_contour_height) { + // Color palette, use the color verbatim. + *px_ptr++ = *src_image++; + *px_ptr++ = *src_image++; + *px_ptr++ = *src_image++; + *px_ptr++ = 255; + } else { + // Text or background + unsigned char alpha = *src_mask; + // Compensate the white color for the 50% opacity reduction at the character edges. + //unsigned char color = (unsigned char)floor(alpha * 255.f / (128.f + 0.5f * alpha)); + unsigned char color = alpha; + *px_ptr++ = color; + *px_ptr++ = color; // *src_mask ++; + *px_ptr++ = color; // *src_mask ++; + *px_ptr++ = 128 + (alpha / 2); // (alpha > 0) ? 255 : 128; + src_image += 3; + } + src_mask += 3; } } @@ -3435,7 +2056,7 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); ::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); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); ::glBindTexture(GL_TEXTURE_2D, 0); return true; @@ -3478,7 +2099,6 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const wxDEFINE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); -wxDEFINE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); @@ -3495,16 +2115,21 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); -GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) +GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) : m_canvas(canvas) , m_context(nullptr) #if ENABLE_RETINA_GL , m_retina_helper(nullptr) #endif , m_in_render(false) - , m_bed(nullptr) + , m_bed(bed) + , m_camera(camera) + , m_view_toolbar(view_toolbar) +#if ENABLE_SVG_ICONS + , m_toolbar(GLToolbar::Normal, "Top") +#else , m_toolbar(GLToolbar::Normal) - , m_view_toolbar(nullptr) +#endif // ENABLE_SVG_ICONS , m_use_clipping_planes(false) , m_sidebar_field("") , m_config(nullptr) @@ -3513,10 +2138,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_dirty(true) , m_initialized(false) , m_use_VBOs(false) - , m_requires_zoom_to_bed(false) , m_apply_zoom_to_volumes_filter(false) , m_hover_volume_id(-1) - , m_toolbar_action_running(false) , m_legend_texture_enabled(false) , m_picking_enabled(false) , m_moving_enabled(false) @@ -3528,9 +2151,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_color_by("volume") , m_reload_delayed(false) , m_render_sla_auxiliaries(true) -#if !ENABLE_IMGUI - , m_external_gizmo_widgets_parent(nullptr) -#endif // not ENABLE_IMGUI { if (m_canvas != nullptr) { m_timer.SetOwner(m_canvas); @@ -3553,11 +2173,6 @@ void GLCanvas3D::post_event(wxEvent &&event) wxPostEvent(m_canvas, event); } -void GLCanvas3D::viewport_changed() -{ - post_event(SimpleEvent(EVT_GLCANVAS_VIEWPORT_CHANGED)); -} - bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) { if (m_initialized) @@ -3628,13 +2243,6 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; return false; } - -#if !ENABLE_IMGUI - if (m_external_gizmo_widgets_parent != nullptr) { - m_gizmos.create_external_gizmo_widgets(m_external_gizmo_widgets_parent); - m_canvas->GetParent()->Layout(); - } -#endif // not ENABLE_IMGUI } if (!_init_toolbar()) @@ -3685,11 +2293,13 @@ int GLCanvas3D::check_volumes_outside_state() const return (int)state; } -void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible) +void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx) { for (GLVolume* vol : m_volumes.volumes) { - if (vol->composite_id.volume_id < 0) - vol->is_active = visible; + if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) + && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) + && vol->composite_id.volume_id < 0) + vol->is_active = visible; } m_render_sla_auxiliaries = visible; @@ -3699,11 +2309,13 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject { for (GLVolume* vol : m_volumes.volumes) { if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) - && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)) + && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)) { vol->is_active = visible; + vol->force_native_color = (instance_idx != -1); + } } if (visible && !mo) - toggle_sla_auxiliaries_visibility(true); + toggle_sla_auxiliaries_visibility(true, mo, instance_idx); if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1)) _set_warning_texture(WarningTexture::SomethingNotShown, true); @@ -3732,11 +2344,10 @@ void GLCanvas3D::set_model(Model* model) void GLCanvas3D::bed_shape_changed() { - m_camera.set_scene_box(scene_bounding_box(), *this); - m_requires_zoom_to_bed = true; - - m_dirty = true; - } + m_camera.set_scene_box(scene_bounding_box()); + m_camera.requires_zoom_to_bed = true; + m_dirty = true; +} void GLCanvas3D::set_color_by(const std::string& value) { @@ -3762,8 +2373,7 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const BoundingBoxf3 GLCanvas3D::scene_bounding_box() const { BoundingBoxf3 bb = volumes_bounding_box(); - if (m_bed != nullptr) - bb.merge(m_bed->get_bounding_box()); + bb.merge(m_bed.get_bounding_box()); if (m_config != nullptr) { @@ -3837,23 +2447,9 @@ void GLCanvas3D::allow_multisample(bool allow) m_multisample_allowed = allow; } -void GLCanvas3D::enable_toolbar_item(const std::string& name, bool enable) -{ - if (enable) - m_toolbar.enable_item(name); - else - m_toolbar.disable_item(name); -} - -bool GLCanvas3D::is_toolbar_item_pressed(const std::string& name) const -{ - return m_toolbar.is_item_pressed(name); -} - void GLCanvas3D::zoom_to_bed() { - if (m_bed != nullptr) - _zoom_to_bounding_box(m_bed->get_bounding_box()); + _zoom_to_bounding_box(m_bed.get_bounding_box()); } void GLCanvas3D::zoom_to_volumes() @@ -3892,63 +2488,17 @@ void GLCanvas3D::select_view(const std::string& direction) { m_camera.phi = dir_vec[0]; m_camera.set_theta(dir_vec[1], false); - - viewport_changed(); - if (m_canvas != nullptr) m_canvas->Refresh(); } } -void GLCanvas3D::set_viewport_from_scene(const GLCanvas3D& other) -{ - m_camera.phi = other.m_camera.phi; - m_camera.set_theta(other.m_camera.get_theta(), false); - m_camera.set_scene_box(other.m_camera.get_scene_box(), *this); - m_camera.set_target(other.m_camera.get_target(), *this); - m_camera.zoom = other.m_camera.zoom; - m_dirty = true; -} - void GLCanvas3D::update_volumes_colors_by_extruder() { if (m_config != nullptr) m_volumes.update_colors_by_extruder(m_config); } -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS -void GLCanvas3D::update_toolbar_items_visibility() -{ - ConfigOptionMode mode = wxGetApp().get_mode(); - m_toolbar.set_item_visible("more", mode != comSimple); - m_toolbar.set_item_visible("fewer", mode != comSimple); - m_toolbar.set_item_visible("splitvolumes", mode != comSimple); - m_dirty = true; -} -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS - -// Returns a Rect object denoting size and position of the Reset button used by a gizmo. -// Returns in either screen or viewport coords. -#if !ENABLE_IMGUI -Rect GLCanvas3D::get_gizmo_reset_rect(const GLCanvas3D& canvas, bool viewport) const -{ - const Size& cnv_size = canvas.get_canvas_size(); - float w = (viewport ? -0.5f : 0.f) * (float)cnv_size.get_width(); - float h = (viewport ? 0.5f : 1.f) * (float)cnv_size.get_height(); - float zoom = canvas.get_camera_zoom(); - float inv_zoom = viewport ? ((zoom != 0.0f) ? 1.0f / zoom : 0.0f) : 1.f; - const float gap = 30.f; - return Rect((w + gap + 80.f) * inv_zoom, (viewport ? -1.f : 1.f) * (h - GIZMO_RESET_BUTTON_HEIGHT) * inv_zoom, - (w + gap + 80.f + GIZMO_RESET_BUTTON_WIDTH) * inv_zoom, (viewport ? -1.f : 1.f) * (h * inv_zoom)); -} - -bool GLCanvas3D::gizmo_reset_rect_contains(const GLCanvas3D& canvas, float x, float y) const -{ - const Rect& rect = get_gizmo_reset_rect(canvas, false); - return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom()); -} -#endif // not ENABLE_IMGUI - void GLCanvas3D::render() { wxCHECK_RET(!m_in_render, "GLCanvas3D::render() called recursively"); @@ -3959,25 +2509,29 @@ void GLCanvas3D::render() if (m_canvas == nullptr) return; +#ifndef __WXMAC__ + // on Mac this check causes flickering when changing view if (!_is_shown_on_screen()) return; +#endif // __WXMAC__ // ensures this canvas is current and initialized if (!_set_current() || !_3DScene::init(m_canvas)) return; - if ((m_bed != nullptr) && m_bed->get_shape().empty()) + if (m_bed.get_shape().empty()) { // this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE post_event(SimpleEvent(EVT_GLCANVAS_UPDATE_BED_SHAPE)); + return; } - if (m_requires_zoom_to_bed) + if (m_camera.requires_zoom_to_bed) { zoom_to_bed(); const Size& cnv_size = get_canvas_size(); _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); - m_requires_zoom_to_bed = false; + m_camera.requires_zoom_to_bed = false; } _camera_tranform(); @@ -3992,11 +2546,7 @@ void GLCanvas3D::render() // absolute value of the rotation theta = 360.f - theta; - bool is_custom_bed = (m_bed == nullptr) || m_bed->is_custom(); - -#if ENABLE_IMGUI wxGetApp().imgui()->new_frame(); -#endif // ENABLE_IMGUI // picking pass _picking_pass(); @@ -4006,8 +2556,8 @@ void GLCanvas3D::render() _render_background(); // textured bed needs to be rendered after objects if the texture is transparent - bool early_bed_render = is_custom_bed || (theta <= 90.0f); - if (early_bed_render) + bool early_bed_render = m_bed.is_custom() || (theta <= 90.0f); + if (early_bed_render) _render_bed(theta); _render_objects(); @@ -4039,15 +2589,15 @@ void GLCanvas3D::render() _render_gizmos_overlay(); _render_warning_texture(); _render_legend_texture(); +#if !ENABLE_SVG_ICONS _resize_toolbars(); +#endif // !ENABLE_SVG_ICONS _render_toolbar(); _render_view_toolbar(); if (m_layers_editing.last_object_id >= 0) m_layers_editing.render_overlay(*this); -#if ENABLE_IMGUI wxGetApp().imgui()->render(); -#endif // ENABLE_IMGUI m_canvas->SwapBuffers(); } @@ -4375,11 +2925,18 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re const Print *print = m_process->fff_print(); float depth = print->get_wipe_tower_depth(); + + // Calculate wipe tower brim spacing. + const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + double layer_height = print_config.opt_float("layer_height"); + double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); + float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); + if (!print->is_step_done(psWipeTower)) depth = (900.f/w) * (float)(extruders_count - 1) ; int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( 1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), - print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + brim_spacing * 4.5f); if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; } @@ -4399,31 +2956,40 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (!m_volumes.empty()) { ModelInstance::EPrintVolumeState state; - bool contained = m_volumes.check_outside_state(m_config, &state); - if (!contained) - { - _set_warning_texture(WarningTexture::ObjectOutside, true); - post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, state == ModelInstance::PVS_Fully_Outside)); - } - else - { - m_volumes.reset_outside_state(); - _set_warning_texture(WarningTexture::ObjectOutside, false); - post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, !m_model->objects.empty())); - } + const bool contained_min_one = m_volumes.check_outside_state(m_config, &state); + + _set_warning_texture(WarningTexture::ObjectClashed, state == ModelInstance::PVS_Partly_Outside); + _set_warning_texture(WarningTexture::ObjectOutside, state == ModelInstance::PVS_Fully_Outside); + + post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, + contained_min_one && !m_model->objects.empty() && state != ModelInstance::PVS_Partly_Outside)); + +// #ys_FIXME_delete_after_testing +// bool contained = m_volumes.check_outside_state(m_config, &state); +// if (!contained) +// { +// _set_warning_texture(WarningTexture::ObjectOutside, true); +// post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, state == ModelInstance::PVS_Fully_Outside)); +// } +// else +// { +// m_volumes.reset_outside_state(); +// _set_warning_texture(WarningTexture::ObjectOutside, false); +// post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, !m_model->objects.empty())); +// } } else { _set_warning_texture(WarningTexture::ObjectOutside, false); + _set_warning_texture(WarningTexture::ObjectClashed, false); post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); } // restore to default value m_regenerate_volumes = true; - m_camera.set_scene_box(scene_bounding_box(), *this); - m_camera.set_target(m_camera.get_target(), *this); + m_camera.set_scene_box(scene_bounding_box()); if (m_selection.is_empty()) { @@ -4585,6 +3151,9 @@ void GLCanvas3D::on_size(wxSizeEvent& evt) void GLCanvas3D::on_idle(wxIdleEvent& evt) { + m_dirty |= m_toolbar.update_items_state(); + m_dirty |= m_view_toolbar.update_items_state(); + if (!m_dirty) return; @@ -4597,13 +3166,11 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) int keyCode = evt.GetKeyCode(); int ctrlMask = wxMOD_CONTROL; -#if ENABLE_IMGUI auto imgui = wxGetApp().imgui(); if (imgui->update_key_data(evt)) { render(); return; } -#endif // ENABLE_IMGUI //#ifdef __APPLE__ // ctrlMask |= wxMOD_RAW_CONTROL; @@ -4613,7 +3180,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'a': case 'A': case WXK_CONTROL_A: - if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::SelectAll)) // Sla gizmo selects all support points + if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.gizmo_event(SLAGizmoEventType::SelectAll)) // Sla gizmo selects all support points m_dirty = true; else post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); @@ -4633,14 +3200,14 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) { // key ESC case WXK_ESCAPE: { - if (m_gizmos.get_current_type() != Gizmos::SlaSupports || !m_gizmos.mouse_event(SLAGizmoEventType::DiscardChanges)) + if (m_gizmos.get_current_type() != Gizmos::SlaSupports || !m_gizmos.gizmo_event(SLAGizmoEventType::DiscardChanges)) m_gizmos.reset_all_states(); m_dirty = true; break; } case WXK_RETURN: { - if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::ApplyChanges)) + if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.gizmo_event(SLAGizmoEventType::ApplyChanges)) m_dirty = true; break; } @@ -4650,7 +3217,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #else /* __APPLE__ */ case WXK_DELETE: #endif /* __APPLE__ */ - if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::Delete)) + if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.gizmo_event(SLAGizmoEventType::Delete)) m_dirty = true; else post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); @@ -4669,7 +3236,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'A': case 'a': { if (m_gizmos.get_current_type() == Gizmos::SlaSupports) { - if (m_gizmos.mouse_event(SLAGizmoEventType::AutomaticGeneration)) + if (m_gizmos.gizmo_event(SLAGizmoEventType::AutomaticGeneration)) m_dirty = true; } else @@ -4686,7 +3253,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'z': { m_selection.is_empty() ? zoom_to_volumes() : zoom_to_selection(); break; } case 'M': case 'm': { - if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::ManualEditing)) { + if (m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.gizmo_event(SLAGizmoEventType::ManualEditing)) { m_dirty = true; break; } @@ -4711,18 +3278,16 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) { const int keyCode = evt.GetKeyCode(); -#if ENABLE_IMGUI auto imgui = wxGetApp().imgui(); if (imgui->update_key_data(evt)) { render(); } else -#endif // ENABLE_IMGUI if (evt.GetEventType() == wxEVT_KEY_UP) { if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) { // Enable switching between 3D and Preview with Tab // m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux post_event(SimpleEvent(EVT_GLCANVAS_TAB)); - } else if (m_gizmos.get_current_type() == Gizmos::SlaSupports && keyCode == WXK_SHIFT && m_gizmos.mouse_event(SLAGizmoEventType::ShiftUp)) { + } else if (m_gizmos.get_current_type() == Gizmos::SlaSupports && keyCode == WXK_SHIFT && m_gizmos.gizmo_event(SLAGizmoEventType::ShiftUp)) { // shift has been just released - SLA gizmo might want to close rectangular selection. m_dirty = true; } @@ -4839,7 +3404,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) Point pos(evt.GetX(), evt.GetY()); -#if ENABLE_IMGUI ImGuiWrapper *imgui = wxGetApp().imgui(); if (imgui->update_mouse_data(evt)) { m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast(); @@ -4849,7 +3413,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ return; } -#endif // ENABLE_IMGUI #ifdef __WXMSW__ bool on_enter_workaround = false; @@ -4869,6 +3432,15 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ } + bool processed_by_toolbar = m_toolbar.on_mouse(evt, *this); + processed_by_toolbar |= m_view_toolbar.on_mouse(evt, *this); + + if (processed_by_toolbar) + { + m_mouse.set_start_position_3D_as_invalid(); + return; + } + if (m_picking_enabled) _set_current(); @@ -4876,16 +3448,12 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1; m_layers_editing.select_object(*m_model, layer_editing_object_idx); bool gizmos_overlay_contains_mouse = m_gizmos.overlay_contains_mouse(*this, m_mouse.position); - int toolbar_contains_mouse = m_toolbar.contains_mouse(m_mouse.position, *this); - int view_toolbar_contains_mouse = (m_view_toolbar != nullptr) ? m_view_toolbar->contains_mouse(m_mouse.position, *this) : -1; -#if ENABLE_MOVE_MIN_THRESHOLD if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos)) { m_mouse.drag.move_requires_threshold = false; m_mouse.set_move_start_threshold_position_2D_as_invalid(); } -#endif // ENABLE_MOVE_MIN_THRESHOLD if (evt.ButtonDown() && wxWindow::FindFocus() != this->m_canvas) // Grab keyboard focus on any mouse click event. @@ -4904,11 +3472,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) auto *top_level_wnd = dynamic_cast(p); if (top_level_wnd && top_level_wnd->IsActive()) m_canvas->SetFocus(); - // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while + m_mouse.position = pos.cast(); + // 1) forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while // the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to // change the volume hover state if any is under the mouse - m_mouse.position = pos.cast(); - render(); + // 2) when switching between 3d view and preview the size of the canvas changes if the side panels are visible, + // so forces a resize to avoid multiple renders with different sizes (seen as flickering) + _refresh_if_shown_on_screen(); } m_mouse.set_start_position_2D_as_invalid(); //#endif @@ -4917,24 +3487,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { // to remove hover on objects when the mouse goes out of this canvas m_mouse.position = Vec2d(-1.0, -1.0); - // ensure m_mouse.left_down is reset (it may happen when switching canvas) - m_mouse.left_down = false; m_dirty = true; } - else if (evt.LeftDClick() && (toolbar_contains_mouse != -1)) - { - m_toolbar_action_running = true; - m_mouse.set_start_position_3D_as_invalid(); - m_toolbar.do_action((unsigned int)toolbar_contains_mouse, *this); - } else if (evt.LeftDClick() && (m_gizmos.get_current_type() != Gizmos::Undefined)) { m_mouse.ignore_up_event = true; } else if (evt.LeftDown() || evt.RightDown()) { - m_mouse.left_down = evt.LeftDown(); - // If user pressed left or right button we first check whether this happened // on a volume or not. m_layers_editing.state = LayersEditing::Unknown; @@ -4963,7 +3523,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) _update_gizmos_data(); m_dirty = true; } - else if (evt.LeftDown() && m_gizmos.get_current_type() == Gizmos::SlaSupports && evt.ShiftDown() && m_gizmos.mouse_event(SLAGizmoEventType::LeftDown, Vec2d(pos(0), pos(1)), evt.ShiftDown())) + else if (evt.LeftDown() && m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.gizmo_event(SLAGizmoEventType::LeftDown, Vec2d(pos(0), pos(1)), evt.ShiftDown())) { // the gizmo got the event and took some action, there is no need to do anything more } @@ -4982,22 +3542,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; } - else if ((selected_object_idx != -1) && evt.RightDown() && m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.mouse_event(SLAGizmoEventType::RightDown)) + else if ((selected_object_idx != -1) && evt.RightDown() && m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.gizmo_event(SLAGizmoEventType::RightDown)) { // event was taken care of by the SlaSupports gizmo } - else if (evt.LeftDown() && (view_toolbar_contains_mouse != -1)) - { - if (m_view_toolbar != nullptr) - m_view_toolbar->do_action((unsigned int)view_toolbar_contains_mouse, *this); - } - else if (evt.LeftDown() && (toolbar_contains_mouse != -1)) - { - m_toolbar_action_running = true; - m_mouse.set_start_position_3D_as_invalid(); - m_toolbar.do_action((unsigned int)toolbar_contains_mouse, *this); - m_mouse.left_down = false; - } else { // Select volume in this 3D canvas. @@ -5019,13 +3567,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { bool add_as_single = !already_selected && !shift_down; m_selection.add(m_hover_volume_id, add_as_single); -#if ENABLE_MOVE_MIN_THRESHOLD m_mouse.drag.move_requires_threshold = !already_selected; if (already_selected) m_mouse.set_move_start_threshold_position_2D_as_invalid(); else m_mouse.drag.move_start_threshold_position_2D = pos; -#endif // ENABLE_MOVE_MIN_THRESHOLD } if (curr_idxs != m_selection.get_volume_idxs()) @@ -5056,46 +3602,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_moving = true; } } - else if (evt.RightDown()) - { - m_mouse.position = pos.cast(); - // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while - // the context menu is already shown - render(); - if (m_hover_volume_id != -1) - { - // if right clicking on volume, propagate event through callback (shows context menu) - if (m_volumes.volumes[m_hover_volume_id]->hover && !m_volumes.volumes[m_hover_volume_id]->is_wipe_tower) - { - // forces the selection of the volume - if (!m_selection.is_multiple_full_instance()) - m_selection.add(m_hover_volume_id); - m_gizmos.update_on_off_state(m_selection); - post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - _update_gizmos_data(); - wxGetApp().obj_manipul()->update_settings_value(m_selection); -// // forces a frame render to update the view before the context menu is shown -// render(); - - Vec2d logical_pos = pos.cast(); -#if ENABLE_RETINA_GL - const float factor = m_retina_helper->get_scale_factor(); - logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); -#endif // ENABLE_RETINA_GL - post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, logical_pos)); - } - } - } } } } else if (evt.Dragging() && evt.LeftIsDown() && !gizmos_overlay_contains_mouse && (m_layers_editing.state == LayersEditing::Unknown) && (m_mouse.drag.move_volume_idx != -1) && m_gizmos.get_current_type() != Gizmos::SlaSupports /* don't allow dragging objects with the Sla gizmo on */) { -#if ENABLE_MOVE_MIN_THRESHOLD if (!m_mouse.drag.move_requires_threshold) { -#endif // ENABLE_MOVE_MIN_THRESHOLD m_mouse.dragging = true; Vec3d cur_pos = m_mouse.drag.start_position_3D; @@ -5119,11 +3633,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) GLfloat matrix[16]; ::glGetFloatv(GL_MODELVIEW_MATRIX, matrix); Vec3d camera_right((double)matrix[0], (double)matrix[4], (double)matrix[8]); + Vec3d camera_up((double)matrix[1], (double)matrix[5], (double)matrix[9]); - // finds projection of the vector along the camera right axis - double projection = inters_vec.dot(camera_right); + // finds projection of the vector along the camera axes + double projection_x = inters_vec.dot(camera_right); + double projection_z = inters_vec.dot(camera_up); - cur_pos = m_mouse.drag.start_position_3D + projection * camera_right; + // apply offset + cur_pos = m_mouse.drag.start_position_3D + projection_x * camera_right + projection_z * camera_up; } else { @@ -5140,9 +3657,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) wxGetApp().obj_manipul()->update_settings_value(m_selection); m_dirty = true; -#if ENABLE_MOVE_MIN_THRESHOLD } -#endif // ENABLE_MOVE_MIN_THRESHOLD } else if (evt.Dragging() && m_gizmos.is_dragging()) { @@ -5184,13 +3699,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_dirty = true; } - else if (evt.Dragging() && m_gizmos.get_current_type() == Gizmos::SlaSupports && evt.ShiftDown() && m_gizmos.mouse_event(SLAGizmoEventType::Dragging, Vec2d(pos(0), pos(1)), evt.ShiftDown())) + else if (evt.Dragging() && m_gizmos.get_current_type() == Gizmos::SlaSupports && m_gizmos.gizmo_event(SLAGizmoEventType::Dragging, Vec2d(pos(0), pos(1)), evt.ShiftDown())) { // the gizmo got the event and took some action, no need to do anything more here m_dirty = true; } // do not process dragging if the mouse is into any of the HUD elements - else if (evt.Dragging() && !gizmos_overlay_contains_mouse && (toolbar_contains_mouse == -1) && (view_toolbar_contains_mouse == -1)) + else if (evt.Dragging() && !gizmos_overlay_contains_mouse) { m_mouse.dragging = true; @@ -5200,21 +3715,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) _perform_layer_editing_action(&evt); } // do not process the dragging if the left mouse was set down in another canvas - else if (m_mouse.left_down && evt.LeftIsDown()) + else if (evt.LeftIsDown()) { // if dragging over blank area with left button, rotate -#if ENABLE_MOVE_MIN_THRESHOLD if ((m_hover_volume_id == -1) && m_mouse.is_start_position_3D_defined()) -#else - if (m_mouse.is_start_position_3D_defined()) -#endif // ENABLE_MOVE_MIN_THRESHOLD { const Vec3d& orig = m_mouse.drag.start_position_3D; m_camera.phi += (((float)pos(0) - (float)orig(0)) * TRACKBALLSIZE); m_camera.set_theta(m_camera.get_theta() - ((float)pos(1) - (float)orig(1)) * TRACKBALLSIZE, wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA); - - viewport_changed(); - m_dirty = true; } m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0); @@ -5228,10 +3736,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) float z = 0.0f; const Vec3d& cur_pos = _mouse_to_3d(pos, &z); Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z); - m_camera.set_target(m_camera.get_target() + orig - cur_pos, *this); - - viewport_changed(); - + m_camera.set_target(m_camera.get_target() + orig - cur_pos); m_dirty = true; } @@ -5247,9 +3752,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_layers_editing.accept_changes(*this); } else if (evt.LeftUp() && m_gizmos.get_current_type() == Gizmos::SlaSupports && !m_gizmos.is_dragging() - && !m_mouse.dragging && m_gizmos.mouse_event(SLAGizmoEventType::LeftUp, Vec2d(pos(0), pos(1)), evt.ShiftDown())) + && !m_mouse.dragging) { - // the gizmo got the event and took some action, no need to do anything more + // in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither + // object moving or selecting is suppressed in that case + m_gizmos.gizmo_event(SLAGizmoEventType::LeftUp, Vec2d(pos(0), pos(1)), evt.ShiftDown()); } else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging) { @@ -5261,13 +3768,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); } else if (evt.LeftUp() && !m_mouse.dragging && (m_hover_volume_id == -1) && !gizmos_overlay_contains_mouse && !m_gizmos.is_dragging() - && !is_layers_editing_enabled() && (m_gizmos.get_current_type() != Gizmos::SlaSupports || !m_gizmos.mouse_event(SLAGizmoEventType::LeftUp, Vec2d(pos(0), pos(1)), evt.ShiftDown()))) + && !is_layers_editing_enabled()) { - // SLA gizmo cannot be deselected by clicking in canvas area to avoid inadvertent unselection and losing manual changes - // that's why the mouse_event function was called so that the gizmo can refuse the deselection in manual editing mode - // deselect and propagate event through callback - if (!evt.ShiftDown() && m_picking_enabled && !m_toolbar_action_running && !m_mouse.ignore_up_event) + if (!evt.ShiftDown() && m_picking_enabled && !m_mouse.ignore_up_event) { m_selection.clear(); m_selection.set_mode(Selection::Instance); @@ -5308,8 +3812,38 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // Let the platter know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); - m_camera.set_scene_box(scene_bounding_box(), *this); - set_camera_zoom(0.0f); + m_camera.set_scene_box(scene_bounding_box()); + } + else if (evt.RightUp()) + { + m_mouse.position = pos.cast(); + // forces a frame render to ensure that m_hover_volume_id is updated even when the user right clicks while + // the context menu is already shown + render(); + if (m_hover_volume_id != -1) + { + // if right clicking on volume, propagate event through callback (shows context menu) + if (m_volumes.volumes[m_hover_volume_id]->hover + && !m_volumes.volumes[m_hover_volume_id]->is_wipe_tower // no context menu for the wipe tower + && m_gizmos.get_current_type() != Gizmos::SlaSupports) // disable context menu when the gizmo is open + { + // forces the selection of the volume + m_selection.add(m_hover_volume_id); + m_gizmos.update_on_off_state(m_selection); + post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); + _update_gizmos_data(); + wxGetApp().obj_manipul()->update_settings_value(m_selection); + // forces a frame render to update the view before the context menu is shown + render(); + + Vec2d logical_pos = pos.cast(); +#if ENABLE_RETINA_GL + const float factor = m_retina_helper->get_scale_factor(); + logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); +#endif // ENABLE_RETINA_GL + post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, logical_pos)); + } + } } m_moving = false; @@ -5317,8 +3851,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_mouse.set_start_position_3D_as_invalid(); m_mouse.set_start_position_2D_as_invalid(); m_mouse.dragging = false; - m_mouse.left_down = false; - m_toolbar_action_running = false; m_dirty = true; if (m_canvas->HasCapture()) @@ -5334,17 +3866,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_selection.is_empty()) m_gizmos.reset_all_states(); - // updates toolbar overlay if (tooltip.empty()) - tooltip = m_toolbar.update_hover_state(m_mouse.position, *this); + tooltip = m_toolbar.get_tooltip(); - // updates view toolbar overlay - if (tooltip.empty() && (m_view_toolbar != nullptr)) - { - tooltip = m_view_toolbar->update_hover_state(m_mouse.position, *this); - if (!tooltip.empty()) - m_dirty = true; - } + if (tooltip.empty()) + tooltip = m_view_toolbar.get_tooltip(); set_tooltip(tooltip); @@ -5424,12 +3950,6 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip) const } } -#if !ENABLE_IMGUI -void GLCanvas3D::set_external_gizmo_widgets_parent(wxWindow *parent) -{ - m_external_gizmo_widgets_parent = parent; -} -#endif // not ENABLE_IMGUI void GLCanvas3D::do_move() { @@ -5655,7 +4175,6 @@ void GLCanvas3D::set_camera_zoom(float zoom) zoom = std::min(zoom, 100.0f); m_camera.zoom = zoom; - viewport_changed(); _refresh_if_shown_on_screen(); } @@ -5707,16 +4226,11 @@ bool GLCanvas3D::_init_toolbar() if (!m_toolbar.is_enabled()) return true; +#if !ENABLE_SVG_ICONS ItemsIconsTexture::Metadata icons_data; icons_data.filename = "toolbar.png"; - icons_data.icon_size = 36; - icons_data.icon_border_size = 1; - icons_data.icon_gap_size = 1; - -// icons_data.filename = "toolbar141.png"; -// icons_data.icon_size = 52; -// icons_data.icon_border_size = 0; -// icons_data.icon_gap_size = 0; + icons_data.icon_size = 37; +#endif // !ENABLE_SVG_ICONS BackgroundTexture::Metadata background_data; background_data.filename = "toolbar_background.png"; @@ -5725,13 +4239,20 @@ bool GLCanvas3D::_init_toolbar() background_data.right = 16; background_data.bottom = 16; +#if ENABLE_SVG_ICONS + if (!m_toolbar.init(background_data)) +#else if (!m_toolbar.init(icons_data, background_data)) +#endif // ENABLE_SVG_ICONS { // unable to init the toolbar texture, disable it m_toolbar.set_enabled(false); return true; } +#if ENABLE_SVG_ICONS + m_toolbar.set_icons_size(40); +#endif // ENABLE_SVG_ICONS // m_toolbar.set_layout_type(GLToolbar::Layout::Vertical); m_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); m_toolbar.set_layout_orientation(GLToolbar::Layout::Top); @@ -5742,30 +4263,45 @@ bool GLCanvas3D::_init_toolbar() GLToolbarItem::Data item; item.name = "add"; +#if ENABLE_SVG_ICONS + item.icon_filename = "add.svg"; +#endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Add...") + " [" + GUI::shortkey_ctrl_prefix() + "I]"; item.sprite_id = 0; - item.action_event = EVT_GLTOOLBAR_ADD; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; if (!m_toolbar.add_item(item)) return false; item.name = "delete"; +#if ENABLE_SVG_ICONS + item.icon_filename = "remove.svg"; +#endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Delete") + " [Del]"; item.sprite_id = 1; - item.action_event = EVT_GLTOOLBAR_DELETE; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; if (!m_toolbar.add_item(item)) return false; item.name = "deleteall"; +#if ENABLE_SVG_ICONS + item.icon_filename = "delete_all.svg"; +#endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Delete all") + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; item.sprite_id = 2; - item.action_event = EVT_GLTOOLBAR_DELETE_ALL; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; if (!m_toolbar.add_item(item)) return false; item.name = "arrange"; +#if ENABLE_SVG_ICONS + item.icon_filename = "arrange.svg"; +#endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Arrange [A]"); item.sprite_id = 3; - item.action_event = EVT_GLTOOLBAR_ARRANGE; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; if (!m_toolbar.add_item(item)) return false; @@ -5773,16 +4309,26 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "more"; +#if ENABLE_SVG_ICONS + item.icon_filename = "instance_add.svg"; +#endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Add instance [+]"); item.sprite_id = 4; - item.action_event = EVT_GLTOOLBAR_MORE; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; + item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; if (!m_toolbar.add_item(item)) return false; item.name = "fewer"; +#if ENABLE_SVG_ICONS + item.icon_filename = "instance_remove.svg"; +#endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Remove instance [-]"); item.sprite_id = 5; - item.action_event = EVT_GLTOOLBAR_FEWER; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; + item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; if (!m_toolbar.add_item(item)) return false; @@ -5790,16 +4336,26 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "splitobjects"; +#if ENABLE_SVG_ICONS + item.icon_filename = "split_objects.svg"; +#endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Split to objects"); item.sprite_id = 6; - item.action_event = EVT_GLTOOLBAR_SPLIT_OBJECTS; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; + item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; if (!m_toolbar.add_item(item)) return false; item.name = "splitvolumes"; +#if ENABLE_SVG_ICONS + item.icon_filename = "split_parts.svg"; +#endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Split to parts"); - item.sprite_id = 8; - item.action_event = EVT_GLTOOLBAR_SPLIT_VOLUMES; + item.sprite_id = 7; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; + item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; if (!m_toolbar.add_item(item)) return false; @@ -5807,19 +4363,18 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "layersediting"; +#if ENABLE_SVG_ICONS + item.icon_filename = "layers.svg"; +#endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Layers editing"); - item.sprite_id = 7; + item.sprite_id = 8; item.is_toggable = true; - item.action_event = EVT_GLTOOLBAR_LAYERSEDITING; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; + item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; if (!m_toolbar.add_item(item)) return false; - enable_toolbar_item("add", true); - -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS - update_toolbar_items_visibility(); -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS - return true; } @@ -5836,14 +4391,12 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) if ((m_canvas == nullptr) && (m_context == nullptr)) return; -#if ENABLE_IMGUI wxGetApp().imgui()->set_display_size((float)w, (float)h); #if ENABLE_RETINA_GL wxGetApp().imgui()->set_style_scaling(m_retina_helper->get_scale_factor()); #else wxGetApp().imgui()->set_style_scaling(m_canvas->GetContentScaleFactor()); #endif -#endif // ENABLE_IMGUI // ensures that this canvas is current _set_current(); @@ -5911,8 +4464,7 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) BoundingBoxf3 GLCanvas3D::_max_bounding_box() const { BoundingBoxf3 bb = volumes_bounding_box(); - if (m_bed != nullptr) - bb.merge(m_bed->get_bounding_box()); + bb.merge(m_bed.get_bounding_box()); return bb; } @@ -5924,10 +4476,7 @@ void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox) { m_camera.zoom = zoom; // center view around bounding box center - m_camera.set_target(bbox.center(), *this); - - viewport_changed(); - + m_camera.set_target(bbox.center()); m_dirty = true; } } @@ -6019,7 +4568,6 @@ void GLCanvas3D::_camera_tranform() const ::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch ::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f); // yaw - Vec3d target = -m_camera.get_target(); ::glTranslated(target(0), target(1), target(2)); } @@ -6028,7 +4576,7 @@ void GLCanvas3D::_picking_pass() const { const Vec2d& pos = m_mouse.position; - if (m_picking_enabled && !m_mouse.dragging && !m_mouse.left_down && (pos != Vec2d(DBL_MAX, DBL_MAX))) + if (m_picking_enabled && !m_mouse.dragging && (pos != Vec2d(DBL_MAX, DBL_MAX))) { // Render the object for picking. // FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing. @@ -6058,7 +4606,6 @@ void GLCanvas3D::_picking_pass() const ::glReadPixels(pos(0), cnv_size.get_height() - pos(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color); volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256; } - if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) { m_hover_volume_id = volume_id; @@ -6067,7 +4614,7 @@ void GLCanvas3D::_picking_pass() const else { m_hover_volume_id = -1; - m_gizmos.set_hover_id(inside ? (254 - (int)color[2]) : -1); + m_gizmos.set_hover_id(inside && volume_id <= GLGizmoBase::BASE_ID ? (GLGizmoBase::BASE_ID - volume_id) : -1); } _update_volumes_hover_state(); @@ -6115,16 +4662,13 @@ void GLCanvas3D::_render_bed(float theta) const float scale_factor = 1.0; #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); -#endif - - if (m_bed != nullptr) - m_bed->render(theta, m_use_VBOs, scale_factor); +#endif // ENABLE_RETINA_GL + m_bed.render(theta, m_use_VBOs, scale_factor); } void GLCanvas3D::_render_axes() const { - if (m_bed != nullptr) - m_bed->render_axes(); + m_bed.render_axes(); } void GLCanvas3D::_render_objects() const @@ -6142,9 +4686,9 @@ void GLCanvas3D::_render_objects() const // Update the layer editing selection to the first object selected, update the current object maximum Z. const_cast(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); - if ((m_config != nullptr) && (m_bed != nullptr)) + if (m_config != nullptr) { - const BoundingBoxf3& bed_bb = m_bed->get_bounding_box(); + const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(); m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); m_volumes.check_outside_state(m_config, nullptr); } @@ -6298,25 +4842,92 @@ void GLCanvas3D::_render_gizmos_overlay() const void GLCanvas3D::_render_toolbar() const { +#if ENABLE_SVG_ICONS +#if ENABLE_RETINA_GL + m_toolbar.set_scale(m_retina_helper->get_scale_factor()); +#else + m_toolbar.set_scale(m_canvas->GetContentScaleFactor()); +#endif // ENABLE_RETINA_GL + + Size cnv_size = get_canvas_size(); + float zoom = get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation(); + + float top = 0.0f; + float left = 0.0f; + switch (m_toolbar.get_layout_type()) + { + default: + case GLToolbar::Layout::Horizontal: + { + // centers the toolbar on the top edge of the 3d scene + if (orientation == GLToolbar::Layout::Top) + { + top = 0.5f * (float)cnv_size.get_height() * inv_zoom; + left = -0.5f * m_toolbar.get_width() * inv_zoom; + } + else + { + top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; + left = -0.5f * m_toolbar.get_width() * inv_zoom; + } + break; + } + case GLToolbar::Layout::Vertical: + { + // centers the toolbar on the right edge of the 3d scene + if (orientation == GLToolbar::Layout::Left) + { + top = 0.5f * m_toolbar.get_height() * inv_zoom; + left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; + } + else + { + top = 0.5f * m_toolbar.get_height() * inv_zoom; + left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom; + } + break; + } + } + m_toolbar.set_position(top, left); +#else #if ENABLE_RETINA_GL m_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); #else m_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); #endif /* __WXMSW__ */ +#endif // ENABLE_SVG_ICONS m_toolbar.render(*this); } void GLCanvas3D::_render_view_toolbar() const { - if (m_view_toolbar != nullptr) { +#if ENABLE_SVG_ICONS #if ENABLE_RETINA_GL - m_view_toolbar->set_icons_scale(m_retina_helper->get_scale_factor()); + m_view_toolbar.set_scale(m_retina_helper->get_scale_factor()); #else - m_view_toolbar->set_icons_scale(m_canvas->GetContentScaleFactor()); + m_view_toolbar.set_scale(m_canvas->GetContentScaleFactor()); +#endif // ENABLE_RETINA_GL + + Size cnv_size = get_canvas_size(); + float zoom = get_camera_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + // places the toolbar on the bottom-left corner of the 3d scene + float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; + float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; + m_view_toolbar.set_position(top, left); +#else +#if ENABLE_RETINA_GL + m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); +#else + m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); #endif /* __WXMSW__ */ - m_view_toolbar->render(*this); - } +#endif // ENABLE_SVG_ICONS + m_view_toolbar.render(*this); } #if ENABLE_SHOW_CAMERA_TARGET @@ -6362,24 +4973,20 @@ void GLCanvas3D::_render_sla_slices() const { const SLAPrintObject* obj = print_objects[i]; - double shift_z = obj->get_current_elevation(); - double min_z = clip_min_z - shift_z; - double max_z = clip_max_z - shift_z; - SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); { if (it_caps_bottom == m_sla_caps[0].triangles.end()) it_caps_bottom = m_sla_caps[0].triangles.emplace(i, SlaCap::Triangles()).first; - if (! m_sla_caps[0].matches(min_z)) { - m_sla_caps[0].z = min_z; + if (! m_sla_caps[0].matches(clip_min_z)) { + m_sla_caps[0].z = clip_min_z; it_caps_bottom->second.object.clear(); it_caps_bottom->second.supports.clear(); } if (it_caps_top == m_sla_caps[1].triangles.end()) it_caps_top = m_sla_caps[1].triangles.emplace(i, SlaCap::Triangles()).first; - if (! m_sla_caps[1].matches(max_z)) { - m_sla_caps[1].z = max_z; + if (! m_sla_caps[1].matches(clip_max_z)) { + m_sla_caps[1].z = clip_max_z; it_caps_top->second.object.clear(); it_caps_top->second.supports.clear(); } @@ -6399,36 +5006,39 @@ void GLCanvas3D::_render_sla_slices() const std::vector instance_transforms; for (const SLAPrintObject::Instance& inst : instances) { - instance_transforms.push_back({ to_3d(unscale(inst.shift), shift_z), Geometry::rad2deg(inst.rotation) }); + instance_transforms.push_back({ to_3d(unscale(inst.shift), 0.), Geometry::rad2deg(inst.rotation) }); } if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) && obj->is_step_done(slaposIndexSlices)) { - const std::vector& model_slices = obj->get_model_slices(); - const std::vector& support_slices = obj->get_support_slices(); - - const SLAPrintObject::SliceIndex& index = obj->get_slice_index(); - SLAPrintObject::SliceIndex::const_iterator it_min_z = std::find_if(index.begin(), index.end(), [min_z](const SLAPrintObject::SliceIndex::value_type& id) -> bool { return std::abs(min_z - id.first) < EPSILON; }); - SLAPrintObject::SliceIndex::const_iterator it_max_z = std::find_if(index.begin(), index.end(), [max_z](const SLAPrintObject::SliceIndex::value_type& id) -> bool { return std::abs(max_z - id.first) < EPSILON; }); - - if (it_min_z != index.end()) - { + double initial_layer_height = print->material_config().initial_layer_height.value; + LevelID key_zero = obj->get_slice_records().begin()->key(); + LevelID key_low = LevelID((clip_min_z - initial_layer_height) / SCALING_FACTOR) + key_zero; + LevelID key_high = LevelID((clip_max_z - initial_layer_height) / SCALING_FACTOR) + key_zero; + auto slice_range = obj->get_slice_records(key_low - LevelID(SCALED_EPSILON), key_high - LevelID(SCALED_EPSILON)); + auto it_low = slice_range.begin(); + auto it_high = std::prev(slice_range.end()); + + if (! it_low.is_end() && it_low->key() < key_low + LevelID(SCALED_EPSILON)) { + const ExPolygons& obj_bottom = obj->get_slices_from_record(it_low, soModel); + const ExPolygons& sup_bottom = obj->get_slices_from_record(it_low, soSupport); // calculate model bottom cap - if (bottom_obj_triangles.empty() && (it_min_z->second.model_slices_idx < model_slices.size())) - bottom_obj_triangles = triangulate_expolygons_3d(model_slices[it_min_z->second.model_slices_idx], min_z, true); + if (bottom_obj_triangles.empty() && !obj_bottom.empty()) + bottom_obj_triangles = triangulate_expolygons_3d(obj_bottom, clip_min_z, true); // calculate support bottom cap - if (bottom_sup_triangles.empty() && (it_min_z->second.support_slices_idx < support_slices.size())) - bottom_sup_triangles = triangulate_expolygons_3d(support_slices[it_min_z->second.support_slices_idx], min_z, true); + if (bottom_sup_triangles.empty() && !sup_bottom.empty()) + bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z, true); } - if (it_max_z != index.end()) - { + if (! it_high.is_end() && it_high->key() < key_high + LevelID(SCALED_EPSILON)) { + const ExPolygons& obj_top = obj->get_slices_from_record(it_high, soModel); + const ExPolygons& sup_top = obj->get_slices_from_record(it_high, soSupport); // calculate model top cap - if (top_obj_triangles.empty() && (it_max_z->second.model_slices_idx < model_slices.size())) - top_obj_triangles = triangulate_expolygons_3d(model_slices[it_max_z->second.model_slices_idx], max_z, false); + if (top_obj_triangles.empty() && !obj_top.empty()) + top_obj_triangles = triangulate_expolygons_3d(obj_top, clip_max_z, false); // calculate support top cap - if (top_sup_triangles.empty() && (it_max_z->second.support_slices_idx < support_slices.size())) - top_sup_triangles = triangulate_expolygons_3d(support_slices[it_max_z->second.support_slices_idx], max_z, false); + if (top_sup_triangles.empty() && !sup_top.empty()) + top_sup_triangles = triangulate_expolygons_3d(sup_top, clip_max_z, false); } } @@ -7573,10 +6183,17 @@ void GLCanvas3D::_load_shells_fff() unsigned int extruders_count = config.nozzle_diameter.size(); if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { float depth = print->get_wipe_tower_depth(); + + // Calculate wipe tower brim spacing. + const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + double layer_height = print_config.opt_float("layer_height"); + double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); + float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); + if (!print->is_step_done(psWipeTower)) depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), brim_spacing * 4.5f); } } } @@ -7789,7 +6406,7 @@ std::vector GLCanvas3D::_parse_colors(const std::vector& col void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector& tool_colors) { - m_legend_texture.generate(preview_data, tool_colors, *this, m_dynamic_background_enabled && _is_any_volume_outside()); + m_legend_texture.generate(preview_data, tool_colors, *this); } void GLCanvas3D::_set_warning_texture(WarningTexture::Warning warning, bool state) @@ -7808,6 +6425,7 @@ bool GLCanvas3D::_is_any_volume_outside() const return false; } +#if !ENABLE_SVG_ICONS void GLCanvas3D::_resize_toolbars() const { Size cnv_size = get_canvas_size(); @@ -7836,7 +6454,7 @@ void GLCanvas3D::_resize_toolbars() const } else { - top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar->get_height()) * inv_zoom; + top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; left = -0.5f * m_toolbar.get_width() * inv_zoom; } m_toolbar.set_position(top, left); @@ -7864,17 +6482,18 @@ void GLCanvas3D::_resize_toolbars() const if (m_view_toolbar != nullptr) { #if ENABLE_RETINA_GL - m_view_toolbar->set_icons_scale(m_retina_helper->get_scale_factor()); + m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); #else - m_view_toolbar->set_icons_scale(m_canvas->GetContentScaleFactor()); + m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); #endif /* __WXMSW__ */ // places the toolbar on the bottom-left corner of the 3d scene - float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar->get_height()) * inv_zoom; + float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; - m_view_toolbar->set_position(top, left); + m_view_toolbar.set_position(top, left); } } +#endif // !ENABLE_SVG_ICONS const Print* GLCanvas3D::fff_print() const { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 06fda3e5c..29e0fbb08 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -4,11 +4,12 @@ #include #include -#include "libslic3r/Technologies.hpp" #include "3DScene.hpp" #include "GLToolbar.hpp" #include "Event.hpp" #include "3DBed.hpp" +#include "Camera.hpp" +#include "Selection.hpp" #include @@ -100,7 +101,6 @@ template using Vec3dsEvent = ArrayEvent; wxDECLARE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); -wxDECLARE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); @@ -162,41 +162,6 @@ class GLCanvas3D void reset() { first_volumes.clear(); } }; - struct Camera - { - enum EType : unsigned char - { - Unknown, -// Perspective, - Ortho, - Num_types - }; - - EType type; - float zoom; - float phi; -// float distance; - - private: - Vec3d m_target; - BoundingBoxf3 m_scene_box; - float m_theta; - - public: - Camera(); - - std::string get_type_as_string() const; - - float get_theta() const { return m_theta; } - void set_theta(float theta, bool apply_limit); - - const Vec3d& get_target() const { return m_target; } - void set_target(const Vec3d& target, GLCanvas3D& canvas); - - const BoundingBoxf3& get_scene_box() const { return m_scene_box; } - void set_scene_box(const BoundingBoxf3& box, GLCanvas3D& canvas); - }; - #if !ENABLE_TEXTURES_FROM_SVG class Shader { @@ -332,24 +297,19 @@ class GLCanvas3D { static const Point Invalid_2D_Point; static const Vec3d Invalid_3D_Point; -#if ENABLE_MOVE_MIN_THRESHOLD static const int MoveThresholdPx; -#endif // ENABLE_MOVE_MIN_THRESHOLD Point start_position_2D; Vec3d start_position_3D; int move_volume_idx; -#if ENABLE_MOVE_MIN_THRESHOLD bool move_requires_threshold; Point move_start_threshold_position_2D; -#endif // ENABLE_MOVE_MIN_THRESHOLD public: Drag(); }; bool dragging; - bool left_down; Vec2d position; Vec3d scene_position; Drag drag; @@ -359,306 +319,18 @@ class GLCanvas3D void set_start_position_2D_as_invalid() { drag.start_position_2D = Drag::Invalid_2D_Point; } void set_start_position_3D_as_invalid() { drag.start_position_3D = Drag::Invalid_3D_Point; } -#if ENABLE_MOVE_MIN_THRESHOLD void set_move_start_threshold_position_2D_as_invalid() { drag.move_start_threshold_position_2D = Drag::Invalid_2D_Point; } -#endif // ENABLE_MOVE_MIN_THRESHOLD bool is_start_position_2D_defined() const { return (drag.start_position_2D != Drag::Invalid_2D_Point); } bool is_start_position_3D_defined() const { return (drag.start_position_3D != Drag::Invalid_3D_Point); } -#if ENABLE_MOVE_MIN_THRESHOLD bool is_move_start_threshold_position_2D_defined() const { return (drag.move_start_threshold_position_2D != Drag::Invalid_2D_Point); } bool is_move_threshold_met(const Point& mouse_pos) const { return (std::abs(mouse_pos(0) - drag.move_start_threshold_position_2D(0)) > Drag::MoveThresholdPx) || (std::abs(mouse_pos(1) - drag.move_start_threshold_position_2D(1)) > Drag::MoveThresholdPx); } -#endif // ENABLE_MOVE_MIN_THRESHOLD }; public: - class TransformationType - { - public: - enum Enum { - // Transforming in a world coordinate system - World = 0, - // Transforming in a local coordinate system - Local = 1, - // Absolute transformations, allowed in local coordinate system only. - Absolute = 0, - // Relative transformations, allowed in both local and world coordinate system. - Relative = 2, - // For group selection, the transformation is performed as if the group made a single solid body. - Joint = 0, - // For group selection, the transformation is performed on each object independently. - Independent = 4, - - World_Relative_Joint = World | Relative | Joint, - World_Relative_Independent = World | Relative | Independent, - Local_Absolute_Joint = Local | Absolute | Joint, - Local_Absolute_Independent = Local | Absolute | Independent, - Local_Relative_Joint = Local | Relative | Joint, - Local_Relative_Independent = Local | Relative | Independent, - }; - - TransformationType() : m_value(World) {} - TransformationType(Enum value) : m_value(value) {} - TransformationType& operator=(Enum value) { m_value = value; return *this; } - - Enum operator()() const { return m_value; } - bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; } - - void set_world() { this->remove(Local); } - void set_local() { this->add(Local); } - void set_absolute() { this->remove(Relative); } - void set_relative() { this->add(Relative); } - void set_joint() { this->remove(Independent); } - void set_independent() { this->add(Independent); } - - bool world() const { return ! this->has(Local); } - bool local() const { return this->has(Local); } - bool absolute() const { return ! this->has(Relative); } - bool relative() const { return this->has(Relative); } - bool joint() const { return ! this->has(Independent); } - bool independent() const { return this->has(Independent); } - - private: - void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); } - void remove(Enum v) { m_value = Enum((unsigned int)m_value & (~(unsigned int)v)); } - - Enum m_value; - }; - - class Selection - { - public: - typedef std::set IndicesList; - - enum EMode : unsigned char - { - Volume, - Instance - }; - - enum EType : unsigned char - { - Invalid, - Empty, - WipeTower, - SingleModifier, - MultipleModifier, - SingleVolume, - MultipleVolume, - SingleFullObject, - MultipleFullObject, - SingleFullInstance, - MultipleFullInstance, - Mixed - }; - - private: - struct VolumeCache - { - private: - struct TransformCache - { - Vec3d position; - Vec3d rotation; - Vec3d scaling_factor; - Vec3d mirror; - Transform3d rotation_matrix; - Transform3d scale_matrix; - Transform3d mirror_matrix; - - TransformCache(); - explicit TransformCache(const Geometry::Transformation& transform); - }; - - TransformCache m_volume; - TransformCache m_instance; - - public: - VolumeCache() {} - VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform); - - const Vec3d& get_volume_position() const { return m_volume.position; } - const Vec3d& get_volume_rotation() const { return m_volume.rotation; } - const Vec3d& get_volume_scaling_factor() const { return m_volume.scaling_factor; } - const Vec3d& get_volume_mirror() const { return m_volume.mirror; } - const Transform3d& get_volume_rotation_matrix() const { return m_volume.rotation_matrix; } - const Transform3d& get_volume_scale_matrix() const { return m_volume.scale_matrix; } - const Transform3d& get_volume_mirror_matrix() const { return m_volume.mirror_matrix; } - - const Vec3d& get_instance_position() const { return m_instance.position; } - const Vec3d& get_instance_rotation() const { return m_instance.rotation; } - const Vec3d& get_instance_scaling_factor() const { return m_instance.scaling_factor; } - const Vec3d& get_instance_mirror() const { return m_instance.mirror; } - const Transform3d& get_instance_rotation_matrix() const { return m_instance.rotation_matrix; } - const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; } - const Transform3d& get_instance_mirror_matrix() const { return m_instance.mirror_matrix; } - }; - - typedef std::map VolumesCache; - typedef std::set InstanceIdxsList; - typedef std::map ObjectIdxsToInstanceIdxsMap; - - struct Cache - { - // Cache of GLVolume derived transformation matrices, valid during mouse dragging. - VolumesCache volumes_data; - // Center of the dragged selection, valid during mouse dragging. - Vec3d dragging_center; - // Map from indices of ModelObject instances in Model::objects - // to a set of indices of ModelVolume instances in ModelObject::instances - // Here the index means a position inside the respective std::vector, not ModelID. - ObjectIdxsToInstanceIdxsMap content; - }; - - // Volumes owned by GLCanvas3D. - GLVolumePtrs* m_volumes; - // Model, not owned. - Model* m_model; - - bool m_valid; - EMode m_mode; - EType m_type; - // set of indices to m_volumes - IndicesList m_list; - Cache m_cache; - mutable BoundingBoxf3 m_bounding_box; - mutable bool m_bounding_box_dirty; - -#if ENABLE_RENDER_SELECTION_CENTER - GLUquadricObj* m_quadric; -#endif // ENABLE_RENDER_SELECTION_CENTER - mutable GLArrow m_arrow; - mutable GLCurvedArrow m_curved_arrow; - - mutable float m_scale_factor; - - public: - Selection(); -#if ENABLE_RENDER_SELECTION_CENTER - ~Selection(); -#endif // ENABLE_RENDER_SELECTION_CENTER - - void set_volumes(GLVolumePtrs* volumes); - bool init(bool useVBOs); - - Model* get_model() const { return m_model; } - void set_model(Model* model); - - EMode get_mode() const { return m_mode; } - void set_mode(EMode mode) { m_mode = mode; } - - void add(unsigned int volume_idx, bool as_single_selection = true); - void remove(unsigned int volume_idx); - - void add_object(unsigned int object_idx, bool as_single_selection = true); - void remove_object(unsigned int object_idx); - - void add_instance(unsigned int object_idx, unsigned int instance_idx, bool as_single_selection = true); - void remove_instance(unsigned int object_idx, unsigned int instance_idx); - - void add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection = true); - void remove_volume(unsigned int object_idx, unsigned int volume_idx); - - void add_all(); - - // Update the selection based on the map from old indices to new indices after m_volumes changed. - // If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances. - void volumes_changed(const std::vector &map_volume_old_to_new); - void clear(); - - bool is_empty() const { return m_type == Empty; } - bool is_wipe_tower() const { return m_type == WipeTower; } - bool is_modifier() const { return (m_type == SingleModifier) || (m_type == MultipleModifier); } - bool is_single_modifier() const { return m_type == SingleModifier; } - bool is_multiple_modifier() const { return m_type == MultipleModifier; } - bool is_single_full_instance() const; - bool is_multiple_full_instance() const { return m_type == MultipleFullInstance; } - bool is_single_full_object() const { return m_type == SingleFullObject; } - bool is_multiple_full_object() const { return m_type == MultipleFullObject; } - bool is_single_volume() const { return m_type == SingleVolume; } - bool is_multiple_volume() const { return m_type == MultipleVolume; } - bool is_mixed() const { return m_type == Mixed; } - bool is_from_single_instance() const { return get_instance_idx() != -1; } - bool is_from_single_object() const; - - bool contains_volume(unsigned int volume_idx) const { return std::find(m_list.begin(), m_list.end(), volume_idx) != m_list.end(); } - bool requires_uniform_scale() const; - - // Returns the the object id if the selection is from a single object, otherwise is -1 - int get_object_idx() const; - // Returns the instance id if the selection is from a single object and from a single instance, otherwise is -1 - int get_instance_idx() const; - // Returns the indices of selected instances. - // Can only be called if selection is from a single object. - const InstanceIdxsList& get_instance_idxs() const; - - const IndicesList& get_volume_idxs() const { return m_list; } - const GLVolume* get_volume(unsigned int volume_idx) const; - - const ObjectIdxsToInstanceIdxsMap& get_content() const { return m_cache.content; } - - unsigned int volumes_count() const { return (unsigned int)m_list.size(); } - const BoundingBoxf3& get_bounding_box() const; - - void start_dragging(); - - void translate(const Vec3d& displacement, bool local = false); - void rotate(const Vec3d& rotation, TransformationType transformation_type); - void flattening_rotate(const Vec3d& normal); - void scale(const Vec3d& scale, bool local); - void mirror(Axis axis); - - void translate(unsigned int object_idx, const Vec3d& displacement); - void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); - - void erase(); - - void render(float scale_factor = 1.0) const; -#if ENABLE_RENDER_SELECTION_CENTER - void render_center() const; -#endif // ENABLE_RENDER_SELECTION_CENTER - void render_sidebar_hints(const std::string& sidebar_field) const; - - bool requires_local_axes() const; - - private: - void _update_valid(); - void _update_type(); - void _set_caches(); - void _add_volume(unsigned int volume_idx); - void _add_instance(unsigned int object_idx, unsigned int instance_idx); - void _add_object(unsigned int object_idx); - void _remove_volume(unsigned int volume_idx); - void _remove_instance(unsigned int object_idx, unsigned int instance_idx); - void _remove_object(unsigned int object_idx); - void _calc_bounding_box() const; - void _render_selected_volumes() const; - void _render_synchronized_volumes() const; - void _render_bounding_box(const BoundingBoxf3& box, float* color) const; - void _render_sidebar_position_hints(const std::string& sidebar_field) const; - void _render_sidebar_rotation_hints(const std::string& sidebar_field) const; - void _render_sidebar_scale_hints(const std::string& sidebar_field) const; - void _render_sidebar_size_hints(const std::string& sidebar_field) const; - void _render_sidebar_position_hint(Axis axis) const; - void _render_sidebar_rotation_hint(Axis axis) const; - void _render_sidebar_scale_hint(Axis axis) const; - void _render_sidebar_size_hint(Axis axis, double length) const; - enum SyncRotationType { - // Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis. - SYNC_ROTATION_NONE = 0, - // Synchronize fully. Used from "place on bed" feature. - SYNC_ROTATION_FULL = 1, - // Synchronize after rotation by an axis not parallel with Z. - SYNC_ROTATION_GENERAL = 2, - }; - void _synchronize_unselected_instances(SyncRotationType sync_rotation_type); - void _synchronize_unselected_volumes(); - void _ensure_on_bed(); - }; - class ClippingPlane { double m_data[4]; @@ -688,6 +360,10 @@ private: class Gizmos { public: +#if ENABLE_SVG_ICONS + static const float Default_Icons_Size; +#endif // ENABLE_SVG_ICONS + enum EType : unsigned char { Undefined, @@ -704,10 +380,21 @@ private: bool m_enabled; typedef std::map GizmosMap; GizmosMap m_gizmos; +#if ENABLE_SVG_ICONS + mutable GLTexture m_icons_texture; + mutable bool m_icons_texture_dirty; +#else + ItemsIconsTexture m_icons_texture; +#endif // ENABLE_SVG_ICONS BackgroundTexture m_background_texture; EType m_current; +#if ENABLE_SVG_ICONS + float m_overlay_icons_size; + float m_overlay_scale; +#else float m_overlay_icons_scale; +#endif // ENABLE_SVG_ICONS float m_overlay_border; float m_overlay_gap_y; @@ -720,6 +407,9 @@ private: bool is_enabled() const; void set_enabled(bool enable); +#if ENABLE_SVG_ICONS + void set_overlay_icon_size(float size); +#endif // ENABLE_SVG_ICONS void set_overlay_scale(float scale); std::string update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection); @@ -755,29 +445,28 @@ private: void set_flattening_data(const ModelObject* model_object); - void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection); - bool mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false); - void delete_current_grabber(bool delete_all = false); + void set_sla_support_data(ModelObject* model_object, const Selection& selection); + bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false); void render_current_gizmo(const Selection& selection) const; void render_current_gizmo_for_picking_pass(const Selection& selection) const; void render_overlay(const GLCanvas3D& canvas, const Selection& selection) const; -#if !ENABLE_IMGUI - void create_external_gizmo_widgets(wxWindow *parent); -#endif // not ENABLE_IMGUI - private: - void _reset(); + void reset(); - void _render_overlay(const GLCanvas3D& canvas, const Selection& selection) const; - void _render_current_gizmo(const Selection& selection) const; + void do_render_overlay(const GLCanvas3D& canvas, const Selection& selection) const; + void do_render_current_gizmo(const Selection& selection) const; - float _get_total_overlay_height() const; - float _get_total_overlay_width() const; + float get_total_overlay_height() const; + float get_total_overlay_width() const; - GLGizmoBase* _get_current() const; + GLGizmoBase* get_current() const; + +#if ENABLE_SVG_ICONS + bool generate_icons_texture() const; +#endif // ENABLE_SVG_ICONS }; struct SlaCap @@ -804,7 +493,8 @@ private: enum Warning { ObjectOutside, ToolpathOutside, - SomethingNotShown + SomethingNotShown, + ObjectClashed }; // Sets a warning of the given type to be active/inactive. If several warnings are active simultaneously, @@ -823,7 +513,7 @@ private: std::vector m_warnings; // Generates the texture with given text. - bool _generate(const std::string& msg, const GLCanvas3D& canvas); + bool _generate(const std::string& msg, const GLCanvas3D& canvas, const bool red_colored = false); }; class LegendTexture : public GUI::GLTexture @@ -846,7 +536,7 @@ private: void fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas, std::vector>& cp_legend_values); - bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas, bool use_error_colors); + bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas); void render(const GLCanvas3D& canvas) const; }; @@ -860,14 +550,14 @@ private: LegendTexture m_legend_texture; WarningTexture m_warning_texture; wxTimer m_timer; - Camera m_camera; - Bed3D* m_bed; + Bed3D& m_bed; + Camera& m_camera; + GLToolbar& m_view_toolbar; LayersEditing m_layers_editing; Shader m_shader; Mouse m_mouse; mutable Gizmos m_gizmos; mutable GLToolbar m_toolbar; - GLToolbar* m_view_toolbar; ClippingPlane m_clipping_planes[2]; bool m_use_clipping_planes; mutable SlaCap m_sla_caps[2]; @@ -883,10 +573,8 @@ private: bool m_dirty; bool m_initialized; bool m_use_VBOs; - bool m_requires_zoom_to_bed; bool m_apply_zoom_to_volumes_filter; mutable int m_hover_volume_id; - bool m_toolbar_action_running; bool m_warning_texture_enabled; bool m_legend_texture_enabled; bool m_picking_enabled; @@ -896,6 +584,9 @@ private: bool m_regenerate_volumes; bool m_moving; bool m_tab_down; + + // Following variable is obsolete and it should be safe to remove it. + // I just don't want to do it now before a release (Lukas Matena 24.3.2019) bool m_render_sla_auxiliaries; std::string m_color_by; @@ -904,12 +595,8 @@ private: GCodePreviewVolumeIndex m_gcode_preview_volume_index; -#if !ENABLE_IMGUI - wxWindow *m_external_gizmo_widgets_parent; -#endif // not ENABLE_IMGUI - public: - GLCanvas3D(wxGLCanvas* canvas); + GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); ~GLCanvas3D(); void set_context(wxGLContext* context) { m_context = context; } @@ -917,10 +604,6 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas; } const wxGLCanvas* get_wxglcanvas() const { return m_canvas; } - void set_bed(Bed3D* bed) { m_bed = bed; } - - void set_view_toolbar(GLToolbar* toolbar) { m_view_toolbar = toolbar; } - bool init(bool useVBOs, bool use_legacy_opengl); void post_event(wxEvent &&event); @@ -930,7 +613,7 @@ public: void reset_volumes(); int check_volumes_outside_state() const; - void toggle_sla_auxiliaries_visibility(bool visible); + void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); void set_config(const DynamicPrintConfig* config); @@ -973,26 +656,13 @@ public: void enable_dynamic_background(bool enable); void allow_multisample(bool allow); - void enable_toolbar_item(const std::string& name, bool enable); - bool is_toolbar_item_pressed(const std::string& name) const; - void zoom_to_bed(); void zoom_to_volumes(); void zoom_to_selection(); void select_view(const std::string& direction); - void set_viewport_from_scene(const GLCanvas3D& other); void update_volumes_colors_by_extruder(); -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS - void update_toolbar_items_visibility(); -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS - -#if !ENABLE_IMGUI - Rect get_gizmo_reset_rect(const GLCanvas3D& canvas, bool viewport) const; - bool gizmo_reset_rect_contains(const GLCanvas3D& canvas, float x, float y) const; -#endif // not ENABLE_IMGUI - bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; } void render(); @@ -1033,10 +703,6 @@ public: void set_tooltip(const std::string& tooltip) const; -#if !ENABLE_IMGUI - void set_external_gizmo_widgets_parent(wxWindow *parent); -#endif // not ENABLE_IMGUI - void do_move(); void do_rotate(); void do_scale(); @@ -1047,8 +713,6 @@ public: void update_gizmos_on_off_state(); - void viewport_changed(); - void handle_sidebar_focus_event(const std::string& opt_key, bool focus_on); void update_ui_from_settings(); @@ -1148,7 +812,9 @@ private: bool _is_any_volume_outside() const; +#if !ENABLE_SVG_ICONS void _resize_toolbars() const; +#endif // !ENABLE_SVG_ICONS static std::vector _parse_colors(const std::vector& colors); diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index 71299f777..e409bed0d 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -129,7 +129,7 @@ GLCanvas3DManager::~GLCanvas3DManager() } } -bool GLCanvas3DManager::add(wxGLCanvas* canvas) +bool GLCanvas3DManager::add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) { if (canvas == nullptr) return false; @@ -137,7 +137,7 @@ bool GLCanvas3DManager::add(wxGLCanvas* canvas) if (_get_canvas(canvas) != m_canvases.end()) return false; - GLCanvas3D* canvas3D = new GLCanvas3D(canvas); + GLCanvas3D* canvas3D = new GLCanvas3D(canvas, bed, camera, view_toolbar); if (canvas3D == nullptr) return false; diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 1f7c49f72..75647e6b2 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -23,6 +23,9 @@ class PrintObject; namespace GUI { class GLCanvas3D; +class Bed3D; +class GLToolbar; +struct Camera; class GLCanvas3DManager { @@ -62,7 +65,7 @@ public: GLCanvas3DManager(); ~GLCanvas3DManager(); - bool add(wxGLCanvas* canvas); + bool add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); bool remove(wxGLCanvas* canvas); void remove_all(); diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp deleted file mode 100644 index 7a64e5f45..000000000 --- a/src/slic3r/GUI/GLGizmo.cpp +++ /dev/null @@ -1,2876 +0,0 @@ -// Include GLGizmo.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. -#include "GLGizmo.hpp" - -#include -#include - -#include "libslic3r/libslic3r.h" -#include "libslic3r/Geometry.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/SLA/SLACommon.hpp" -#include "libslic3r/SLAPrint.hpp" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "Tab.hpp" -#include "GUI.hpp" -#include "GUI_Utils.hpp" -#include "GUI_App.hpp" -#include "GUI_ObjectSettings.hpp" -#include "GUI_ObjectList.hpp" -#include "I18N.hpp" -#include "PresetBundle.hpp" - -#include - -// TODO: Display tooltips quicker on Linux - -static const float DEFAULT_BASE_COLOR[3] = { 0.625f, 0.625f, 0.625f }; -static const float DEFAULT_DRAG_COLOR[3] = { 1.0f, 1.0f, 1.0f }; -static const float DEFAULT_HIGHLIGHT_COLOR[3] = { 1.0f, 0.38f, 0.0f }; - -static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; - -namespace Slic3r { -namespace GUI { - -const float GLGizmoBase::Grabber::SizeFactor = 0.025f; -const float GLGizmoBase::Grabber::MinHalfSize = 1.5f; -const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f; - -GLGizmoBase::Grabber::Grabber() - : center(Vec3d::Zero()) - , angles(Vec3d::Zero()) - , dragging(false) - , enabled(true) -{ - color[0] = 1.0f; - color[1] = 1.0f; - color[2] = 1.0f; -} - -void GLGizmoBase::Grabber::render(bool hover, float size) const -{ - float render_color[3]; - if (hover) - { - render_color[0] = 1.0f - color[0]; - render_color[1] = 1.0f - color[1]; - render_color[2] = 1.0f - color[2]; - } - else - ::memcpy((void*)render_color, (const void*)color, 3 * sizeof(float)); - - render(size, render_color, true); -} - -float GLGizmoBase::Grabber::get_half_size(float size) const -{ - return std::max(size * SizeFactor, MinHalfSize); -} - -float GLGizmoBase::Grabber::get_dragging_half_size(float size) const -{ - return std::max(size * SizeFactor * DraggingScaleFactor, MinHalfSize); -} - -void GLGizmoBase::Grabber::render(float size, const float* render_color, bool use_lighting) const -{ - float half_size = dragging ? get_dragging_half_size(size) : get_half_size(size); - - if (use_lighting) - ::glEnable(GL_LIGHTING); - - ::glColor3fv(render_color); - - ::glPushMatrix(); - ::glTranslated(center(0), center(1), center(2)); - - ::glRotated(Geometry::rad2deg(angles(2)), 0.0, 0.0, 1.0); - ::glRotated(Geometry::rad2deg(angles(1)), 0.0, 1.0, 0.0); - ::glRotated(Geometry::rad2deg(angles(0)), 1.0, 0.0, 0.0); - - // face min x - ::glPushMatrix(); - ::glTranslatef(-(GLfloat)half_size, 0.0f, 0.0f); - ::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f); - render_face(half_size); - ::glPopMatrix(); - - // face max x - ::glPushMatrix(); - ::glTranslatef((GLfloat)half_size, 0.0f, 0.0f); - ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f); - render_face(half_size); - ::glPopMatrix(); - - // face min y - ::glPushMatrix(); - ::glTranslatef(0.0f, -(GLfloat)half_size, 0.0f); - ::glRotatef(90.0f, 1.0f, 0.0f, 0.0f); - render_face(half_size); - ::glPopMatrix(); - - // face max y - ::glPushMatrix(); - ::glTranslatef(0.0f, (GLfloat)half_size, 0.0f); - ::glRotatef(-90.0f, 1.0f, 0.0f, 0.0f); - render_face(half_size); - ::glPopMatrix(); - - // face min z - ::glPushMatrix(); - ::glTranslatef(0.0f, 0.0f, -(GLfloat)half_size); - ::glRotatef(180.0f, 1.0f, 0.0f, 0.0f); - render_face(half_size); - ::glPopMatrix(); - - // face max z - ::glPushMatrix(); - ::glTranslatef(0.0f, 0.0f, (GLfloat)half_size); - render_face(half_size); - ::glPopMatrix(); - - ::glPopMatrix(); - - if (use_lighting) - ::glDisable(GL_LIGHTING); -} - -void GLGizmoBase::Grabber::render_face(float half_size) const -{ - ::glBegin(GL_TRIANGLES); - ::glNormal3f(0.0f, 0.0f, 1.0f); - ::glVertex3f(-(GLfloat)half_size, -(GLfloat)half_size, 0.0f); - ::glVertex3f((GLfloat)half_size, -(GLfloat)half_size, 0.0f); - ::glVertex3f((GLfloat)half_size, (GLfloat)half_size, 0.0f); - ::glVertex3f((GLfloat)half_size, (GLfloat)half_size, 0.0f); - ::glVertex3f(-(GLfloat)half_size, (GLfloat)half_size, 0.0f); - ::glVertex3f(-(GLfloat)half_size, -(GLfloat)half_size, 0.0f); - ::glEnd(); -} - -GLGizmoBase::GLGizmoBase(GLCanvas3D& parent) - : m_parent(parent) - , m_group_id(-1) - , m_state(Off) - , m_shortcut_key(0) - , m_hover_id(-1) - , m_dragging(false) -#if ENABLE_IMGUI - , m_imgui(wxGetApp().imgui()) -#endif // ENABLE_IMGUI -{ - ::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 3 * sizeof(float)); - ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 3 * sizeof(float)); - ::memcpy((void*)m_highlight_color, (const void*)DEFAULT_HIGHLIGHT_COLOR, 3 * sizeof(float)); -} - -void GLGizmoBase::set_hover_id(int id) -{ - if (m_grabbers.empty() || (id < (int)m_grabbers.size())) - { - m_hover_id = id; - on_set_hover_id(); - } -} - -void GLGizmoBase::set_highlight_color(const float* color) -{ - if (color != nullptr) - ::memcpy((void*)m_highlight_color, (const void*)color, 3 * sizeof(float)); -} - -void GLGizmoBase::enable_grabber(unsigned int id) -{ - if ((0 <= id) && (id < (unsigned int)m_grabbers.size())) - m_grabbers[id].enabled = true; - - on_enable_grabber(id); -} - -void GLGizmoBase::disable_grabber(unsigned int id) -{ - if ((0 <= id) && (id < (unsigned int)m_grabbers.size())) - m_grabbers[id].enabled = false; - - on_disable_grabber(id); -} - -void GLGizmoBase::start_dragging(const GLCanvas3D::Selection& selection) -{ - m_dragging = true; - - for (int i = 0; i < (int)m_grabbers.size(); ++i) - { - m_grabbers[i].dragging = (m_hover_id == i); - } - - on_start_dragging(selection); -} - -void GLGizmoBase::stop_dragging() -{ - m_dragging = false; - - for (int i = 0; i < (int)m_grabbers.size(); ++i) - { - m_grabbers[i].dragging = false; - } - - on_stop_dragging(); -} - -void GLGizmoBase::update(const UpdateData& data, const GLCanvas3D::Selection& selection) -{ - if (m_hover_id != -1) - on_update(data, selection); -} - -float GLGizmoBase::picking_color_component(unsigned int id) const -{ - int color = 254 - (int)id; - if (m_group_id > -1) - color -= m_group_id; - - return (float)color / 255.0f; -} - -void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const -{ - float size = (float)box.max_size(); - - for (int i = 0; i < (int)m_grabbers.size(); ++i) - { - if (m_grabbers[i].enabled) - m_grabbers[i].render((m_hover_id == i), size); - } -} - -void GLGizmoBase::render_grabbers(float size) const -{ - for (int i = 0; i < (int)m_grabbers.size(); ++i) - { - if (m_grabbers[i].enabled) - m_grabbers[i].render((m_hover_id == i), size); - } -} - -void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const -{ - float size = (float)box.max_size(); - - for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) - { - if (m_grabbers[i].enabled) - { - m_grabbers[i].color[0] = 1.0f; - m_grabbers[i].color[1] = 1.0f; - m_grabbers[i].color[2] = picking_color_component(i); - m_grabbers[i].render_for_picking(size); - } - } -} - -#if !ENABLE_IMGUI -void GLGizmoBase::create_external_gizmo_widgets(wxWindow *parent) {} -#endif // not ENABLE_IMGUI - -void GLGizmoBase::set_tooltip(const std::string& tooltip) const -{ - m_parent.set_tooltip(tooltip); -} - -std::string GLGizmoBase::format(float value, unsigned int decimals) const -{ - return Slic3r::string_printf("%.*f", decimals, value); -} - -const float GLGizmoRotate::Offset = 5.0f; -const unsigned int GLGizmoRotate::CircleResolution = 64; -const unsigned int GLGizmoRotate::AngleResolution = 64; -const unsigned int GLGizmoRotate::ScaleStepsCount = 72; -const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount; -const unsigned int GLGizmoRotate::ScaleLongEvery = 2; -const float GLGizmoRotate::ScaleLongTooth = 0.1f; // in percent of radius -const unsigned int GLGizmoRotate::SnapRegionsCount = 8; -const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius - -GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) - : GLGizmoBase(parent) - , m_axis(axis) - , m_angle(0.0) - , m_quadric(nullptr) - , m_center(0.0, 0.0, 0.0) - , m_radius(0.0f) - , m_snap_coarse_in_radius(0.0f) - , m_snap_coarse_out_radius(0.0f) - , m_snap_fine_in_radius(0.0f) - , m_snap_fine_out_radius(0.0f) -{ - m_quadric = ::gluNewQuadric(); - if (m_quadric != nullptr) - ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -} - -GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other) - : GLGizmoBase(other.m_parent) - , m_axis(other.m_axis) - , m_angle(other.m_angle) - , m_quadric(nullptr) - , m_center(other.m_center) - , m_radius(other.m_radius) - , m_snap_coarse_in_radius(other.m_snap_coarse_in_radius) - , m_snap_coarse_out_radius(other.m_snap_coarse_out_radius) - , m_snap_fine_in_radius(other.m_snap_fine_in_radius) - , m_snap_fine_out_radius(other.m_snap_fine_out_radius) -{ - m_quadric = ::gluNewQuadric(); - if (m_quadric != nullptr) - ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -} - -GLGizmoRotate::~GLGizmoRotate() -{ - if (m_quadric != nullptr) - ::gluDeleteQuadric(m_quadric); -} - -void GLGizmoRotate::set_angle(double angle) -{ - if (std::abs(angle - 2.0 * (double)PI) < EPSILON) - angle = 0.0; - - m_angle = angle; -} - -bool GLGizmoRotate::on_init() -{ - m_grabbers.push_back(Grabber()); - return true; -} - -void GLGizmoRotate::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - const BoundingBoxf3& box = selection.get_bounding_box(); - m_center = box.center(); - m_radius = Offset + box.radius(); - m_snap_coarse_in_radius = m_radius / 3.0f; - m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; - m_snap_fine_in_radius = m_radius; - m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; -} - -void GLGizmoRotate::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) -{ - Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, selection)); - - Vec2d orig_dir = Vec2d::UnitX(); - Vec2d new_dir = mouse_pos.normalized(); - - double theta = ::acos(clamp(-1.0, 1.0, new_dir.dot(orig_dir))); - if (cross2(orig_dir, new_dir) < 0.0) - theta = 2.0 * (double)PI - theta; - - double len = mouse_pos.norm(); - - // snap to coarse snap region - if ((m_snap_coarse_in_radius <= len) && (len <= m_snap_coarse_out_radius)) - { - double step = 2.0 * (double)PI / (double)SnapRegionsCount; - theta = step * (double)std::round(theta / step); - } - else - { - // snap to fine snap region (scale) - if ((m_snap_fine_in_radius <= len) && (len <= m_snap_fine_out_radius)) - { - double step = 2.0 * (double)PI / (double)ScaleStepsCount; - theta = step * (double)std::round(theta / step); - } - } - - if (theta == 2.0 * (double)PI) - theta = 0.0; - - m_angle = theta; -} - -void GLGizmoRotate::on_render(const GLCanvas3D::Selection& selection) const -{ - if (!m_grabbers[0].enabled) - return; - - const BoundingBoxf3& box = selection.get_bounding_box(); - - std::string axis; - switch (m_axis) - { - case X: { axis = "X"; break; } - case Y: { axis = "Y"; break; } - case Z: { axis = "Z"; break; } - } - - if (!m_dragging && (m_hover_id == 0)) - set_tooltip(axis); - else if (m_dragging) - set_tooltip(axis + ": " + format((float)Geometry::rad2deg(m_angle), 4) + "\u00B0"); - else - { - m_center = box.center(); - m_radius = Offset + box.radius(); - m_snap_coarse_in_radius = m_radius / 3.0f; - m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; - m_snap_fine_in_radius = m_radius; - m_snap_fine_out_radius = m_radius * (1.0f + ScaleLongTooth); - } - - ::glEnable(GL_DEPTH_TEST); - - ::glPushMatrix(); - transform_to_local(selection); - - ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); - ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color); - - render_circle(); - - if (m_hover_id != -1) - { - render_scale(); - render_snap_radii(); - render_reference_radius(); - } - - ::glColor3fv(m_highlight_color); - - if (m_hover_id != -1) - render_angle(); - - render_grabber(box); - render_grabber_extension(box, false); - - ::glPopMatrix(); -} - -void GLGizmoRotate::on_render_for_picking(const GLCanvas3D::Selection& selection) const -{ - ::glDisable(GL_DEPTH_TEST); - - ::glPushMatrix(); - - transform_to_local(selection); - - const BoundingBoxf3& box = selection.get_bounding_box(); - render_grabbers_for_picking(box); - render_grabber_extension(box, true); - - ::glPopMatrix(); -} - -void GLGizmoRotate::render_circle() const -{ - ::glBegin(GL_LINE_LOOP); - for (unsigned int i = 0; i < ScaleStepsCount; ++i) - { - float angle = (float)i * ScaleStepRad; - float x = ::cos(angle) * m_radius; - float y = ::sin(angle) * m_radius; - float z = 0.0f; - ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); - } - ::glEnd(); -} - -void GLGizmoRotate::render_scale() const -{ - float out_radius_long = m_snap_fine_out_radius; - float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth); - - ::glBegin(GL_LINES); - for (unsigned int i = 0; i < ScaleStepsCount; ++i) - { - float angle = (float)i * ScaleStepRad; - float cosa = ::cos(angle); - float sina = ::sin(angle); - float in_x = cosa * m_radius; - float in_y = sina * m_radius; - float in_z = 0.0f; - float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; - float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; - float out_z = 0.0f; - ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z); - ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z); - } - ::glEnd(); -} - -void GLGizmoRotate::render_snap_radii() const -{ - float step = 2.0f * (float)PI / (float)SnapRegionsCount; - - float in_radius = m_radius / 3.0f; - float out_radius = 2.0f * in_radius; - - ::glBegin(GL_LINES); - for (unsigned int i = 0; i < SnapRegionsCount; ++i) - { - float angle = (float)i * step; - float cosa = ::cos(angle); - float sina = ::sin(angle); - float in_x = cosa * in_radius; - float in_y = sina * in_radius; - float in_z = 0.0f; - float out_x = cosa * out_radius; - float out_y = sina * out_radius; - float out_z = 0.0f; - ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z); - ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z); - } - ::glEnd(); -} - -void GLGizmoRotate::render_reference_radius() const -{ - ::glBegin(GL_LINES); - ::glVertex3f(0.0f, 0.0f, 0.0f); - ::glVertex3f((GLfloat)(m_radius * (1.0f + GrabberOffset)), 0.0f, 0.0f); - ::glEnd(); -} - -void GLGizmoRotate::render_angle() const -{ - float step_angle = (float)m_angle / AngleResolution; - float ex_radius = m_radius * (1.0f + GrabberOffset); - - ::glBegin(GL_LINE_STRIP); - for (unsigned int i = 0; i <= AngleResolution; ++i) - { - float angle = (float)i * step_angle; - float x = ::cos(angle) * ex_radius; - float y = ::sin(angle) * ex_radius; - float z = 0.0f; - ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); - } - ::glEnd(); -} - -void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const -{ - double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset); - m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0); - m_grabbers[0].angles(2) = m_angle; - - ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color); - - ::glBegin(GL_LINES); - ::glVertex3f(0.0f, 0.0f, 0.0f); - ::glVertex3dv(m_grabbers[0].center.data()); - ::glEnd(); - - ::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 3 * sizeof(float)); - render_grabbers(box); -} - -void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) const -{ - if (m_quadric == nullptr) - return; - - double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size((float)box.max_size()) : (double)m_grabbers[0].get_half_size((float)box.max_size()); - - float color[3]; - ::memcpy((void*)color, (const void*)m_grabbers[0].color, 3 * sizeof(float)); - if (!picking && (m_hover_id != -1)) - { - color[0] = 1.0f - color[0]; - color[1] = 1.0f - color[1]; - color[2] = 1.0f - color[2]; - } - - if (!picking) - ::glEnable(GL_LIGHTING); - - ::glColor3fv(color); - ::glPushMatrix(); - ::glTranslated(m_grabbers[0].center(0), m_grabbers[0].center(1), m_grabbers[0].center(2)); - ::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0); - ::glRotated(90.0, 1.0, 0.0, 0.0); - ::glTranslated(0.0, 0.0, 2.0 * size); - ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); - ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); - ::gluQuadricOrientation(m_quadric, GLU_INSIDE); - ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); - ::glPopMatrix(); - ::glPushMatrix(); - ::glTranslated(m_grabbers[0].center(0), m_grabbers[0].center(1), m_grabbers[0].center(2)); - ::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0); - ::glRotated(-90.0, 1.0, 0.0, 0.0); - ::glTranslated(0.0, 0.0, 2.0 * size); - ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); - ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); - ::gluQuadricOrientation(m_quadric, GLU_INSIDE); - ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); - ::glPopMatrix(); - - if (!picking) - ::glDisable(GL_LIGHTING); -} - -void GLGizmoRotate::transform_to_local(const GLCanvas3D::Selection& selection) const -{ - ::glTranslated(m_center(0), m_center(1), m_center(2)); - - if (selection.is_single_volume() || selection.is_single_modifier()) - { - Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); - ::glMultMatrixd(orient_matrix.data()); - } - - switch (m_axis) - { - case X: - { - ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f); - ::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f); - break; - } - case Y: - { - ::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f); - ::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f); - break; - } - default: - case Z: - { - // no rotation - break; - } - } -} - -Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, const GLCanvas3D::Selection& selection) const -{ - double half_pi = 0.5 * (double)PI; - - Transform3d m = Transform3d::Identity(); - - switch (m_axis) - { - case X: - { - m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); - m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitY())); - break; - } - case Y: - { - m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitY())); - m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); - break; - } - default: - case Z: - { - // no rotation applied - break; - } - } - - if (selection.is_single_volume() || selection.is_single_modifier()) - m = m * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true).inverse(); - - m.translate(-m_center); - - return transform(mouse_ray, m).intersect_plane(0.0); -} - -GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent) - : GLGizmoBase(parent) -{ - m_gizmos.emplace_back(parent, GLGizmoRotate::X); - m_gizmos.emplace_back(parent, GLGizmoRotate::Y); - m_gizmos.emplace_back(parent, GLGizmoRotate::Z); - - for (unsigned int i = 0; i < 3; ++i) - { - m_gizmos[i].set_group_id(i); - } -} - -bool GLGizmoRotate3D::on_init() -{ - for (GLGizmoRotate& g : m_gizmos) - { - if (!g.init()) - return false; - } - - for (unsigned int i = 0; i < 3; ++i) - { - m_gizmos[i].set_highlight_color(AXES_COLOR[i]); - } - - std::string path = resources_dir() + "/icons/overlay/"; - - if (!m_textures[Off].load_from_file(path + "rotate_off.png", false)) - return false; - - if (!m_textures[Hover].load_from_file(path + "rotate_hover.png", false)) - return false; - - if (!m_textures[On].load_from_file(path + "rotate_on.png", false)) - return false; - - m_shortcut_key = WXK_CONTROL_R; - - return true; -} - -std::string GLGizmoRotate3D::on_get_name() const -{ - return L("Rotate [R]"); -} - -void GLGizmoRotate3D::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - if ((0 <= m_hover_id) && (m_hover_id < 3)) - m_gizmos[m_hover_id].start_dragging(selection); -} - -void GLGizmoRotate3D::on_stop_dragging() -{ - if ((0 <= m_hover_id) && (m_hover_id < 3)) - m_gizmos[m_hover_id].stop_dragging(); -} - -void GLGizmoRotate3D::on_render(const GLCanvas3D::Selection& selection) const -{ - ::glClear(GL_DEPTH_BUFFER_BIT); - - if ((m_hover_id == -1) || (m_hover_id == 0)) - m_gizmos[X].render(selection); - - if ((m_hover_id == -1) || (m_hover_id == 1)) - m_gizmos[Y].render(selection); - - if ((m_hover_id == -1) || (m_hover_id == 2)) - m_gizmos[Z].render(selection); -} - -#if ENABLE_IMGUI -void GLGizmoRotate3D::on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) -{ -#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI - Vec3d rotation(Geometry::rad2deg(m_gizmos[0].get_angle()), Geometry::rad2deg(m_gizmos[1].get_angle()), Geometry::rad2deg(m_gizmos[2].get_angle())); - wxString label = _(L("Rotation (deg)")); - - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->set_next_window_bg_alpha(0.5f); - m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - m_imgui->input_vec3("", rotation, 100.0f, "%.2f"); - m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI -} -#endif // ENABLE_IMGUI - -const float GLGizmoScale3D::Offset = 5.0f; - -GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent) - : GLGizmoBase(parent) - , m_scale(Vec3d::Ones()) - , m_snap_step(0.05) - , m_starting_scale(Vec3d::Ones()) -{ -} - -bool GLGizmoScale3D::on_init() -{ - std::string path = resources_dir() + "/icons/overlay/"; - - if (!m_textures[Off].load_from_file(path + "scale_off.png", false)) - return false; - - if (!m_textures[Hover].load_from_file(path + "scale_hover.png", false)) - return false; - - if (!m_textures[On].load_from_file(path + "scale_on.png", false)) - return false; - - for (int i = 0; i < 10; ++i) - { - m_grabbers.push_back(Grabber()); - } - - double half_pi = 0.5 * (double)PI; - - // x axis - m_grabbers[0].angles(1) = half_pi; - m_grabbers[1].angles(1) = half_pi; - - // y axis - m_grabbers[2].angles(0) = half_pi; - m_grabbers[3].angles(0) = half_pi; - - m_shortcut_key = WXK_CONTROL_S; - - return true; -} - -std::string GLGizmoScale3D::on_get_name() const -{ - return L("Scale [S]"); -} - -void GLGizmoScale3D::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - if (m_hover_id != -1) - { - m_starting_drag_position = m_grabbers[m_hover_id].center; - m_starting_box = selection.get_bounding_box(); - } -} - -void GLGizmoScale3D::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) -{ - if ((m_hover_id == 0) || (m_hover_id == 1)) - do_scale_x(data); - else if ((m_hover_id == 2) || (m_hover_id == 3)) - do_scale_y(data); - else if ((m_hover_id == 4) || (m_hover_id == 5)) - do_scale_z(data); - else if (m_hover_id >= 6) - do_scale_uniform(data); -} - -void GLGizmoScale3D::on_render(const GLCanvas3D::Selection& selection) const -{ - bool single_instance = selection.is_single_full_instance(); - bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); - bool single_selection = single_instance || single_volume; - - Vec3f scale = 100.0f * Vec3f::Ones(); - if (single_instance) - scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast(); - else if (single_volume) - scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast(); - - if ((single_selection && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging) - set_tooltip("X: " + format(scale(0), 4) + "%"); - else if (!m_grabbers[0].dragging && !m_grabbers[1].dragging && ((m_hover_id == 0) || (m_hover_id == 1))) - set_tooltip("X"); - else if ((single_selection && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging) - set_tooltip("Y: " + format(scale(1), 4) + "%"); - else if (!m_grabbers[2].dragging && !m_grabbers[3].dragging && ((m_hover_id == 2) || (m_hover_id == 3))) - set_tooltip("Y"); - else if ((single_selection && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging) - set_tooltip("Z: " + format(scale(2), 4) + "%"); - else if (!m_grabbers[4].dragging && !m_grabbers[5].dragging && ((m_hover_id == 4) || (m_hover_id == 5))) - set_tooltip("Z"); - else if ((single_selection && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) - || m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging) - { - std::string tooltip = "X: " + format(scale(0), 4) + "%\n"; - tooltip += "Y: " + format(scale(1), 4) + "%\n"; - tooltip += "Z: " + format(scale(2), 4) + "%"; - set_tooltip(tooltip); - } - else if (!m_grabbers[6].dragging && !m_grabbers[7].dragging && !m_grabbers[8].dragging && !m_grabbers[9].dragging && - ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) - set_tooltip("X/Y/Z"); - - ::glClear(GL_DEPTH_BUFFER_BIT); - ::glEnable(GL_DEPTH_TEST); - - BoundingBoxf3 box; - Transform3d transform = Transform3d::Identity(); - Vec3d angles = Vec3d::Zero(); - Transform3d offsets_transform = Transform3d::Identity(); - - Vec3d grabber_size = Vec3d::Zero(); - - if (single_instance) - { - // calculate bounding box in instance local reference system - const GLCanvas3D::Selection::IndicesList& idxs = selection.get_volume_idxs(); - for (unsigned int idx : idxs) - { - const GLVolume* vol = selection.get_volume(idx); - box.merge(vol->bounding_box.transformed(vol->get_volume_transformation().get_matrix())); - } - - // gets transform from first selected volume - const GLVolume* v = selection.get_volume(*idxs.begin()); - transform = v->get_instance_transformation().get_matrix(); - // gets angles from first selected volume - angles = v->get_instance_rotation(); - // consider rotation+mirror only components of the transform for offsets - offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); - grabber_size = v->get_instance_transformation().get_matrix(true, true, false, true) * box.size(); - } - else if (single_volume) - { - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); - box = v->bounding_box; - transform = v->world_matrix(); - angles = Geometry::extract_euler_angles(transform); - // consider rotation+mirror only components of the transform for offsets - offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); - grabber_size = v->get_volume_transformation().get_matrix(true, true, false, true) * box.size(); - } - else - { - box = selection.get_bounding_box(); - grabber_size = box.size(); - } - - m_box = box; - - const Vec3d& center = m_box.center(); - Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0); - Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); - Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); - - // x axis - m_grabbers[0].center = transform * Vec3d(m_box.min(0), center(1), center(2)) - offset_x; - m_grabbers[1].center = transform * Vec3d(m_box.max(0), center(1), center(2)) + offset_x; - ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); - ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); - - // y axis - m_grabbers[2].center = transform * Vec3d(center(0), m_box.min(1), center(2)) - offset_y; - m_grabbers[3].center = transform * Vec3d(center(0), m_box.max(1), center(2)) + offset_y; - ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); - ::memcpy((void*)m_grabbers[3].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); - - // z axis - m_grabbers[4].center = transform * Vec3d(center(0), center(1), m_box.min(2)) - offset_z; - m_grabbers[5].center = transform * Vec3d(center(0), center(1), m_box.max(2)) + offset_z; - ::memcpy((void*)m_grabbers[4].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); - ::memcpy((void*)m_grabbers[5].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); - - // uniform - m_grabbers[6].center = transform * Vec3d(m_box.min(0), m_box.min(1), center(2)) - offset_x - offset_y; - m_grabbers[7].center = transform * Vec3d(m_box.max(0), m_box.min(1), center(2)) + offset_x - offset_y; - m_grabbers[8].center = transform * Vec3d(m_box.max(0), m_box.max(1), center(2)) + offset_x + offset_y; - m_grabbers[9].center = transform * Vec3d(m_box.min(0), m_box.max(1), center(2)) - offset_x + offset_y; - for (int i = 6; i < 10; ++i) - { - ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 3 * sizeof(float)); - } - - // sets grabbers orientation - for (int i = 0; i < 10; ++i) - { - m_grabbers[i].angles = angles; - } - - ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); - - float grabber_max_size = (float)std::max(grabber_size(0), std::max(grabber_size(1), grabber_size(2))); - - if (m_hover_id == -1) - { - // draw connections - if (m_grabbers[0].enabled && m_grabbers[1].enabled) - { - ::glColor3fv(m_grabbers[0].color); - render_grabbers_connection(0, 1); - } - if (m_grabbers[2].enabled && m_grabbers[3].enabled) - { - ::glColor3fv(m_grabbers[2].color); - render_grabbers_connection(2, 3); - } - if (m_grabbers[4].enabled && m_grabbers[5].enabled) - { - ::glColor3fv(m_grabbers[4].color); - render_grabbers_connection(4, 5); - } - ::glColor3fv(m_base_color); - render_grabbers_connection(6, 7); - render_grabbers_connection(7, 8); - render_grabbers_connection(8, 9); - render_grabbers_connection(9, 6); - // draw grabbers - render_grabbers(grabber_max_size); - } - else if ((m_hover_id == 0) || (m_hover_id == 1)) - { - // draw connection - ::glColor3fv(m_grabbers[0].color); - render_grabbers_connection(0, 1); - // draw grabbers - m_grabbers[0].render(true, grabber_max_size); - m_grabbers[1].render(true, grabber_max_size); - } - else if ((m_hover_id == 2) || (m_hover_id == 3)) - { - // draw connection - ::glColor3fv(m_grabbers[2].color); - render_grabbers_connection(2, 3); - // draw grabbers - m_grabbers[2].render(true, grabber_max_size); - m_grabbers[3].render(true, grabber_max_size); - } - else if ((m_hover_id == 4) || (m_hover_id == 5)) - { - // draw connection - ::glColor3fv(m_grabbers[4].color); - render_grabbers_connection(4, 5); - // draw grabbers - m_grabbers[4].render(true, grabber_max_size); - m_grabbers[5].render(true, grabber_max_size); - } - else if (m_hover_id >= 6) - { - // draw connection - ::glColor3fv(m_drag_color); - render_grabbers_connection(6, 7); - render_grabbers_connection(7, 8); - render_grabbers_connection(8, 9); - render_grabbers_connection(9, 6); - // draw grabbers - for (int i = 6; i < 10; ++i) - { - m_grabbers[i].render(true, grabber_max_size); - } - } -} - -void GLGizmoScale3D::on_render_for_picking(const GLCanvas3D::Selection& selection) const -{ - ::glDisable(GL_DEPTH_TEST); - - render_grabbers_for_picking(selection.get_bounding_box()); -} - -#if ENABLE_IMGUI -void GLGizmoScale3D::on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) -{ -#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI - bool single_instance = selection.is_single_full_instance(); - wxString label = _(L("Scale (%)")); - - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->set_next_window_bg_alpha(0.5f); - m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - m_imgui->input_vec3("", m_scale * 100.f, 100.0f, "%.2f"); - m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI -} -#endif // ENABLE_IMGUI - -void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const -{ - unsigned int grabbers_count = (unsigned int)m_grabbers.size(); - if ((id_1 < grabbers_count) && (id_2 < grabbers_count)) - { - ::glBegin(GL_LINES); - ::glVertex3dv(m_grabbers[id_1].center.data()); - ::glVertex3dv(m_grabbers[id_2].center.data()); - ::glEnd(); - } -} - -void GLGizmoScale3D::do_scale_x(const UpdateData& data) -{ - double ratio = calc_ratio(data); - if (ratio > 0.0) - m_scale(0) = m_starting_scale(0) * ratio; -} - -void GLGizmoScale3D::do_scale_y(const UpdateData& data) -{ - double ratio = calc_ratio(data); - if (ratio > 0.0) - m_scale(1) = m_starting_scale(1) * ratio; -} - -void GLGizmoScale3D::do_scale_z(const UpdateData& data) -{ - double ratio = calc_ratio(data); - if (ratio > 0.0) - m_scale(2) = m_starting_scale(2) * ratio; -} - -void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) -{ - double ratio = calc_ratio(data); - if (ratio > 0.0) - m_scale = m_starting_scale * ratio; -} - -double GLGizmoScale3D::calc_ratio(const UpdateData& data) const -{ - double ratio = 0.0; - - // vector from the center to the starting position - Vec3d starting_vec = m_starting_drag_position - m_starting_box.center(); - double len_starting_vec = starting_vec.norm(); - if (len_starting_vec != 0.0) - { - Vec3d mouse_dir = data.mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; - // vector from the starting position to the found intersection - Vec3d inters_vec = inters - m_starting_drag_position; - - // finds projection of the vector along the staring direction - double proj = inters_vec.dot(starting_vec.normalized()); - - ratio = (len_starting_vec + proj) / len_starting_vec; - } - - if (data.shift_down) - ratio = m_snap_step * (double)std::round(ratio / m_snap_step); - - return ratio; -} - -const double GLGizmoMove3D::Offset = 10.0; - -GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent) - : GLGizmoBase(parent) - , m_displacement(Vec3d::Zero()) - , m_snap_step(1.0) - , m_starting_drag_position(Vec3d::Zero()) - , m_starting_box_center(Vec3d::Zero()) - , m_starting_box_bottom_center(Vec3d::Zero()) - , m_quadric(nullptr) -{ - m_quadric = ::gluNewQuadric(); - if (m_quadric != nullptr) - ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -} - -GLGizmoMove3D::~GLGizmoMove3D() -{ - if (m_quadric != nullptr) - ::gluDeleteQuadric(m_quadric); -} - -bool GLGizmoMove3D::on_init() -{ - std::string path = resources_dir() + "/icons/overlay/"; - - if (!m_textures[Off].load_from_file(path + "move_off.png", false)) - return false; - - if (!m_textures[Hover].load_from_file(path + "move_hover.png", false)) - return false; - - if (!m_textures[On].load_from_file(path + "move_on.png", false)) - return false; - - for (int i = 0; i < 3; ++i) - { - m_grabbers.push_back(Grabber()); - } - - m_shortcut_key = WXK_CONTROL_M; - - return true; -} - -std::string GLGizmoMove3D::on_get_name() const -{ - return L("Move [M]"); -} - -void GLGizmoMove3D::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - if (m_hover_id != -1) - { - m_displacement = Vec3d::Zero(); - const BoundingBoxf3& box = selection.get_bounding_box(); - m_starting_drag_position = m_grabbers[m_hover_id].center; - m_starting_box_center = box.center(); - m_starting_box_bottom_center = box.center(); - m_starting_box_bottom_center(2) = box.min(2); - } -} - -void GLGizmoMove3D::on_stop_dragging() -{ - m_displacement = Vec3d::Zero(); -} - -void GLGizmoMove3D::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) -{ - if (m_hover_id == 0) - m_displacement(0) = calc_projection(data); - else if (m_hover_id == 1) - m_displacement(1) = calc_projection(data); - else if (m_hover_id == 2) - m_displacement(2) = calc_projection(data); -} - -void GLGizmoMove3D::on_render(const GLCanvas3D::Selection& selection) const -{ - bool show_position = selection.is_single_full_instance(); - const Vec3d& position = selection.get_bounding_box().center(); - - if ((show_position && (m_hover_id == 0)) || m_grabbers[0].dragging) - set_tooltip("X: " + format(show_position ? position(0) : m_displacement(0), 2)); - else if (!m_grabbers[0].dragging && (m_hover_id == 0)) - set_tooltip("X"); - else if ((show_position && (m_hover_id == 1)) || m_grabbers[1].dragging) - set_tooltip("Y: " + format(show_position ? position(1) : m_displacement(1), 2)); - else if (!m_grabbers[1].dragging && (m_hover_id == 1)) - set_tooltip("Y"); - else if ((show_position && (m_hover_id == 2)) || m_grabbers[2].dragging) - set_tooltip("Z: " + format(show_position ? position(2) : m_displacement(2), 2)); - else if (!m_grabbers[2].dragging && (m_hover_id == 2)) - set_tooltip("Z"); - - ::glClear(GL_DEPTH_BUFFER_BIT); - ::glEnable(GL_DEPTH_TEST); - - const BoundingBoxf3& box = selection.get_bounding_box(); - const Vec3d& center = box.center(); - - // x axis - m_grabbers[0].center = Vec3d(box.max(0) + Offset, center(1), center(2)); - ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); - - // y axis - m_grabbers[1].center = Vec3d(center(0), box.max(1) + Offset, center(2)); - ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); - - // z axis - m_grabbers[2].center = Vec3d(center(0), center(1), box.max(2) + Offset); - ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); - - ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); - - if (m_hover_id == -1) - { - // draw axes - for (unsigned int i = 0; i < 3; ++i) - { - if (m_grabbers[i].enabled) - { - ::glColor3fv(AXES_COLOR[i]); - ::glBegin(GL_LINES); - ::glVertex3dv(center.data()); - ::glVertex3dv(m_grabbers[i].center.data()); - ::glEnd(); - } - } - - // draw grabbers - render_grabbers(box); - for (unsigned int i = 0; i < 3; ++i) - { - if (m_grabbers[i].enabled) - render_grabber_extension((Axis)i, box, false); - } - } - else - { - // draw axis - ::glColor3fv(AXES_COLOR[m_hover_id]); - ::glBegin(GL_LINES); - ::glVertex3dv(center.data()); - ::glVertex3dv(m_grabbers[m_hover_id].center.data()); - ::glEnd(); - - // draw grabber - m_grabbers[m_hover_id].render(true, box.max_size()); - render_grabber_extension((Axis)m_hover_id, box, false); - } -} - -void GLGizmoMove3D::on_render_for_picking(const GLCanvas3D::Selection& selection) const -{ - ::glDisable(GL_DEPTH_TEST); - - const BoundingBoxf3& box = selection.get_bounding_box(); - render_grabbers_for_picking(box); - render_grabber_extension(X, box, true); - render_grabber_extension(Y, box, true); - render_grabber_extension(Z, box, true); -} - -#if ENABLE_IMGUI -void GLGizmoMove3D::on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) -{ -#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI - bool show_position = selection.is_single_full_instance(); - const Vec3d& position = selection.get_bounding_box().center(); - - Vec3d displacement = show_position ? position : m_displacement; - wxString label = show_position ? _(L("Position (mm)")) : _(L("Displacement (mm)")); - - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->set_next_window_bg_alpha(0.5f); - m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - m_imgui->input_vec3("", displacement, 100.0f, "%.2f"); - - m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI -} -#endif // ENABLE_IMGUI - -double GLGizmoMove3D::calc_projection(const UpdateData& data) const -{ - double projection = 0.0; - - Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; - double len_starting_vec = starting_vec.norm(); - if (len_starting_vec != 0.0) - { - Vec3d mouse_dir = data.mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; - // vector from the starting position to the found intersection - Vec3d inters_vec = inters - m_starting_drag_position; - - // finds projection of the vector along the staring direction - projection = inters_vec.dot(starting_vec.normalized()); - } - - if (data.shift_down) - projection = m_snap_step * (double)std::round(projection / m_snap_step); - - return projection; -} - -void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const -{ - if (m_quadric == nullptr) - return; - - double size = m_dragging ? (double)m_grabbers[axis].get_dragging_half_size((float)box.max_size()) : (double)m_grabbers[axis].get_half_size((float)box.max_size()); - - float color[3]; - ::memcpy((void*)color, (const void*)m_grabbers[axis].color, 3 * sizeof(float)); - if (!picking && (m_hover_id != -1)) - { - color[0] = 1.0f - color[0]; - color[1] = 1.0f - color[1]; - color[2] = 1.0f - color[2]; - } - - if (!picking) - ::glEnable(GL_LIGHTING); - - ::glColor3fv(color); - ::glPushMatrix(); - ::glTranslated(m_grabbers[axis].center(0), m_grabbers[axis].center(1), m_grabbers[axis].center(2)); - if (axis == X) - ::glRotated(90.0, 0.0, 1.0, 0.0); - else if (axis == Y) - ::glRotated(-90.0, 1.0, 0.0, 0.0); - - ::glTranslated(0.0, 0.0, 2.0 * size); - ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); - ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); - ::gluQuadricOrientation(m_quadric, GLU_INSIDE); - ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); - ::glPopMatrix(); - - if (!picking) - ::glDisable(GL_LIGHTING); -} - -GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent) - : GLGizmoBase(parent) - , m_normal(Vec3d::Zero()) - , m_starting_center(Vec3d::Zero()) -{ -} - -bool GLGizmoFlatten::on_init() -{ - std::string path = resources_dir() + "/icons/overlay/"; - - if (!m_textures[Off].load_from_file(path + "layflat_off.png", false)) - return false; - - if (!m_textures[Hover].load_from_file(path + "layflat_hover.png", false)) - return false; - - if (!m_textures[On].load_from_file(path + "layflat_on.png", false)) - return false; - - m_shortcut_key = WXK_CONTROL_F; - - return true; -} - -std::string GLGizmoFlatten::on_get_name() const -{ - return L("Place on face [F]"); -} - -bool GLGizmoFlatten::on_is_activable(const GLCanvas3D::Selection& selection) const -{ - return selection.is_single_full_instance(); -} - -void GLGizmoFlatten::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - if (m_hover_id != -1) - { - assert(m_planes_valid); - m_normal = m_planes[m_hover_id].normal; - m_starting_center = selection.get_bounding_box().center(); - } -} - -void GLGizmoFlatten::on_render(const GLCanvas3D::Selection& selection) const -{ - ::glClear(GL_DEPTH_BUFFER_BIT); - - ::glEnable(GL_DEPTH_TEST); - ::glEnable(GL_BLEND); - - if (selection.is_single_full_instance()) - { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); - ::glPushMatrix(); - ::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()); - ::glMultMatrixd(m.data()); - if (this->is_plane_update_necessary()) - const_cast(this)->update_planes(); - for (int i = 0; i < (int)m_planes.size(); ++i) - { - if (i == m_hover_id) - ::glColor4f(0.9f, 0.9f, 0.9f, 0.75f); - else - ::glColor4f(0.9f, 0.9f, 0.9f, 0.5f); - - ::glBegin(GL_POLYGON); - for (const Vec3d& vertex : m_planes[i].vertices) - { - ::glVertex3dv(vertex.data()); - } - ::glEnd(); - } - ::glPopMatrix(); - } - - ::glEnable(GL_CULL_FACE); - ::glDisable(GL_BLEND); -} - -void GLGizmoFlatten::on_render_for_picking(const GLCanvas3D::Selection& selection) const -{ - ::glDisable(GL_DEPTH_TEST); - ::glDisable(GL_BLEND); - - if (selection.is_single_full_instance()) - { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); - ::glPushMatrix(); - ::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()); - ::glMultMatrixd(m.data()); - if (this->is_plane_update_necessary()) - const_cast(this)->update_planes(); - for (int i = 0; i < (int)m_planes.size(); ++i) - { - ::glColor3f(1.0f, 1.0f, picking_color_component(i)); - ::glBegin(GL_POLYGON); - for (const Vec3d& vertex : m_planes[i].vertices) - { - ::glVertex3dv(vertex.data()); - } - ::glEnd(); - } - ::glPopMatrix(); - } - - ::glEnable(GL_CULL_FACE); -} - -void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) -{ - m_starting_center = Vec3d::Zero(); - if (m_model_object != model_object) { - m_planes.clear(); - m_planes_valid = false; - } - m_model_object = model_object; -} - -void GLGizmoFlatten::update_planes() -{ - TriangleMesh ch; - for (const ModelVolume* vol : m_model_object->volumes) - { - if (vol->type() != ModelVolumeType::MODEL_PART) - continue; - TriangleMesh vol_ch = vol->get_convex_hull(); - vol_ch.transform(vol->get_matrix()); - ch.merge(vol_ch); - } - ch = ch.convex_hull_3d(); - m_planes.clear(); - const Transform3d& inst_matrix = m_model_object->instances.front()->get_matrix(true); - - // Following constants are used for discarding too small polygons. - const float minimal_area = 5.f; // in square mm (world coordinates) - const float minimal_side = 1.f; // mm - - // Now we'll go through all the facets and append Points of facets sharing the same normal. - // This part is still performed in mesh coordinate system. - const int num_of_facets = ch.stl.stats.number_of_facets; - std::vector facet_queue(num_of_facets, 0); - std::vector facet_visited(num_of_facets, false); - int facet_queue_cnt = 0; - const stl_normal* normal_ptr = nullptr; - while (1) { - // Find next unvisited triangle: - int facet_idx = 0; - for (; facet_idx < num_of_facets; ++ facet_idx) - if (!facet_visited[facet_idx]) { - facet_queue[facet_queue_cnt ++] = facet_idx; - facet_visited[facet_idx] = true; - normal_ptr = &ch.stl.facet_start[facet_idx].normal; - m_planes.emplace_back(); - break; - } - if (facet_idx == num_of_facets) - break; // Everything was visited already - - while (facet_queue_cnt > 0) { - int facet_idx = facet_queue[-- facet_queue_cnt]; - const stl_normal& this_normal = ch.stl.facet_start[facet_idx].normal; - if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) { - stl_vertex* first_vertex = ch.stl.facet_start[facet_idx].vertex; - for (int j=0; j<3; ++j) - m_planes.back().vertices.emplace_back((double)first_vertex[j](0), (double)first_vertex[j](1), (double)first_vertex[j](2)); - - facet_visited[facet_idx] = true; - for (int j = 0; j < 3; ++ j) { - int neighbor_idx = ch.stl.neighbors_start[facet_idx].neighbor[j]; - if (! facet_visited[neighbor_idx]) - facet_queue[facet_queue_cnt ++] = neighbor_idx; - } - } - } - m_planes.back().normal = normal_ptr->cast(); - - // Now we'll transform all the points into world coordinates, so that the areas, angles and distances - // make real sense. - m_planes.back().vertices = transform(m_planes.back().vertices, inst_matrix); - - // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway): - if (m_planes.back().vertices.size() == 3 && - ((m_planes.back().vertices[0] - m_planes.back().vertices[1]).norm() < minimal_side - || (m_planes.back().vertices[0] - m_planes.back().vertices[2]).norm() < minimal_side - || (m_planes.back().vertices[1] - m_planes.back().vertices[2]).norm() < minimal_side)) - m_planes.pop_back(); - } - - // Let's prepare transformation of the normal vector from mesh to instance coordinates. - Geometry::Transformation t(inst_matrix); - Vec3d scaling = t.get_scaling_factor(); - t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); - - // Now we'll go through all the polygons, transform the points into xy plane to process them: - for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { - Pointf3s& polygon = m_planes[polygon_id].vertices; - const Vec3d& normal = m_planes[polygon_id].normal; - - // transform the normal according to the instance matrix: - Vec3d normal_transformed = t.get_matrix() * normal; - - // We are going to rotate about z and y to flatten the plane - Eigen::Quaterniond q; - Transform3d m = Transform3d::Identity(); - m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix(); - polygon = transform(polygon, m); - - // Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since - // it works in fixed point representation, we will rescale the polygon to avoid overflows. - // And yes, it is a nasty thing to do. Whoever has time is free to refactor. - Vec3d bb_size = BoundingBoxf3(polygon).size(); - float sf = std::min(1./bb_size(0), 1./bb_size(1)); - Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); - polygon = transform(polygon, tr); - polygon = Slic3r::Geometry::convex_hull(polygon); - polygon = transform(polygon, tr.inverse()); - - // Calculate area of the polygons and discard ones that are too small - float& area = m_planes[polygon_id].area; - area = 0.f; - for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula - area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1); - area = 0.5f * std::abs(area); - - bool discard = false; - if (area < minimal_area) - discard = true; - else { - // We also check the inner angles and discard polygons with angles smaller than the following threshold - const double angle_threshold = ::cos(10.0 * (double)PI / 180.0); - - for (unsigned int i = 0; i < polygon.size(); ++i) { - const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1]; - const Vec3d& curr = polygon[i]; - const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1]; - - if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) { - discard = true; - break; - } - } - } - - if (discard) { - m_planes.erase(m_planes.begin() + (polygon_id--)); - continue; - } - - // We will shrink the polygon a little bit so it does not touch the object edges: - Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); - centroid /= (double)polygon.size(); - for (auto& vertex : polygon) - vertex = 0.9f*vertex + 0.1f*centroid; - - // Polygon is now simple and convex, we'll round the corners to make them look nicer. - // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex - // towards their average (controlled by 'aggressivity'). This is repeated k times. - // In next iterations, the neighbours are not always taken at the middle (to increase the - // rounding effect at the corners, where we need it most). - const unsigned int k = 10; // number of iterations - const float aggressivity = 0.2f; // agressivity - const unsigned int N = polygon.size(); - std::vector> neighbours; - if (k != 0) { - Pointf3s points_out(2*k*N); // vector long enough to store the future vertices - for (unsigned int j=0; jvolumes) { - m_volumes_matrices.push_back(vol->get_matrix()); - m_volumes_types.push_back(vol->type()); - } - m_first_instance_scale = m_model_object->instances.front()->get_scaling_factor(); - m_first_instance_mirror = m_model_object->instances.front()->get_mirror(); - - m_planes_valid = true; -} - - -bool GLGizmoFlatten::is_plane_update_necessary() const -{ - if (m_state != On || !m_model_object || m_model_object->instances.empty()) - return false; - - if (! m_planes_valid || m_model_object->volumes.size() != m_volumes_matrices.size()) - return true; - - // We want to recalculate when the scale changes - some planes could (dis)appear. - if (! m_model_object->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale) - || ! m_model_object->instances.front()->get_mirror().isApprox(m_first_instance_mirror)) - return true; - - for (unsigned int i=0; i < m_model_object->volumes.size(); ++i) - if (! m_model_object->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i]) - || m_model_object->volumes[i]->type() != m_volumes_types[i]) - return true; - - return false; -} - -Vec3d GLGizmoFlatten::get_flattening_normal() const -{ - Vec3d out = m_normal; - m_normal = Vec3d::Zero(); - m_starting_center = Vec3d::Zero(); - return out; -} - -GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent) - : GLGizmoBase(parent), m_starting_center(Vec3d::Zero()), m_quadric(nullptr) -{ - m_quadric = ::gluNewQuadric(); - if (m_quadric != nullptr) - // using GLU_FILL does not work when the instance's transformation - // contains mirroring (normals are reverted) - ::gluQuadricDrawStyle(m_quadric, GLU_FILL); -} - -GLGizmoSlaSupports::~GLGizmoSlaSupports() -{ - if (m_quadric != nullptr) - ::gluDeleteQuadric(m_quadric); -} - -bool GLGizmoSlaSupports::on_init() -{ - std::string path = resources_dir() + "/icons/overlay/"; - - if (!m_textures[Off].load_from_file(path + "sla_support_points_off.png", false)) - return false; - - if (!m_textures[Hover].load_from_file(path + "sla_support_points_hover.png", false)) - return false; - - if (!m_textures[On].load_from_file(path + "sla_support_points_on.png", false)) - return false; - - m_shortcut_key = WXK_CONTROL_L; - - return true; -} - -void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection) -{ - m_starting_center = Vec3d::Zero(); - m_old_model_object = m_model_object; - m_model_object = model_object; - if (selection.is_empty()) - m_old_instance_id = -1; - - m_active_instance = selection.get_instance_idx(); - - if (model_object && selection.is_from_single_instance()) - { - if (is_mesh_update_necessary()) - update_mesh(); - - if (m_model_object != m_old_model_object) - m_editing_mode = false; - - if (m_editing_mode_cache.empty() && m_model_object->sla_points_status != sla::PointsStatus::UserModified) - get_data_from_backend(); - - if (m_state == On) { - m_parent.toggle_model_objects_visibility(false); - m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); - } - } -} - -void GLGizmoSlaSupports::on_render(const GLCanvas3D::Selection& selection) const -{ - ::glEnable(GL_BLEND); - ::glEnable(GL_DEPTH_TEST); - - render_points(selection, false); - render_selection_rectangle(); - -#if !ENABLE_IMGUI - render_tooltip_texture(); -#endif // not ENABLE_IMGUI - - ::glDisable(GL_BLEND); -} - -void GLGizmoSlaSupports::render_selection_rectangle() const -{ - if (!m_selection_rectangle_active) - return; - - ::glLineWidth(1.5f); - float render_color[3] = {1.f, 0.f, 0.f}; - ::glColor3fv(render_color); - - ::glPushAttrib(GL_TRANSFORM_BIT); // remember current MatrixMode - - ::glMatrixMode(GL_MODELVIEW); // cache modelview matrix and set to identity - ::glPushMatrix(); - ::glLoadIdentity(); - - ::glMatrixMode(GL_PROJECTION); // cache projection matrix and set to identity - ::glPushMatrix(); - ::glLoadIdentity(); - - ::glOrtho(0.f, m_canvas_width, m_canvas_height, 0.f, -1.f, 1.f); // set projection matrix so that world coords = window coords - - // render the selection rectangle (window coordinates): - ::glPushAttrib(GL_ENABLE_BIT); - ::glLineStipple(4, 0xAAAA); - ::glEnable(GL_LINE_STIPPLE); - - ::glBegin(GL_LINE_LOOP); - ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); - ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); - ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); - ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); - ::glEnd(); - ::glPopAttrib(); - - ::glPopMatrix(); // restore former projection matrix - ::glMatrixMode(GL_MODELVIEW); - ::glPopMatrix(); // restore former modelview matrix - ::glPopAttrib(); // restore former MatrixMode -} - -void GLGizmoSlaSupports::on_render_for_picking(const GLCanvas3D::Selection& selection) const -{ - ::glEnable(GL_DEPTH_TEST); - - render_points(selection, true); -} - -void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, bool picking) const -{ - if (m_quadric == nullptr || !selection.is_from_single_instance()) - return; - - if (!picking) - ::glEnable(GL_LIGHTING); - - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_shift = vol->get_sla_shift_z(); - const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); - const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); - - ::glPushMatrix(); - ::glTranslated(0.0, 0.0, z_shift); - ::glMultMatrixd(instance_matrix.data()); - - float render_color[3]; - for (int i = 0; i < (int)m_editing_mode_cache.size(); ++i) - { - const sla::SupportPoint& support_point = m_editing_mode_cache[i].first; - const bool& point_selected = m_editing_mode_cache[i].second; - - // First decide about the color of the point. - if (picking) { - render_color[0] = 1.0f; - render_color[1] = 1.0f; - render_color[2] = picking_color_component(i); - } - else { - if ((m_hover_id == i && m_editing_mode)) { // ignore hover state unless editing mode is active - render_color[0] = 0.f; - render_color[1] = 1.0f; - render_color[2] = 1.0f; - } - else { // neigher hover nor picking - bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].first.is_new_island; - if (m_editing_mode) { - render_color[0] = point_selected ? 1.0f : (supports_new_island ? 0.3f : 0.7f); - render_color[1] = point_selected ? 0.3f : (supports_new_island ? 0.3f : 0.7f); - render_color[2] = point_selected ? 0.3f : (supports_new_island ? 1.0f : 0.7f); - } - else - for (unsigned char i=0; i<3; ++i) render_color[i] = 0.5f; - } - } - ::glColor3fv(render_color); - float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f}; - ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); - - // Now render the sphere. Inverse matrix of the instance scaling is applied so that the - // sphere does not scale with the object. - ::glPushMatrix(); - ::glTranslated(support_point.pos(0), support_point.pos(1), support_point.pos(2)); - ::glMultMatrixd(instance_scaling_matrix_inverse.data()); - ::gluSphere(m_quadric, m_editing_mode_cache[i].first.head_front_radius * RenderPointScale, 64, 36); - ::glPopMatrix(); - } - - { - // Reset emissive component to zero (the default value) - float render_color_emissive[4] = { 0.f, 0.f, 0.f, 1.f }; - ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); - } - - if (!picking) - ::glDisable(GL_LIGHTING); - - ::glPopMatrix(); -} - -bool GLGizmoSlaSupports::is_mesh_update_necessary() const -{ - return (m_state == On) && (m_model_object != m_old_model_object) && (m_model_object != nullptr) && !m_model_object->instances.empty(); - - //if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix)) - // return false; -} - -void GLGizmoSlaSupports::update_mesh() -{ - Eigen::MatrixXf& V = m_V; - Eigen::MatrixXi& F = m_F; - // Composite mesh of all instances in the world coordinate system. - // This mesh does not account for the possible Z up SLA offset. - TriangleMesh mesh = m_model_object->raw_mesh(); - const stl_file& stl = mesh.stl; - V.resize(3 * stl.stats.number_of_facets, 3); - F.resize(stl.stats.number_of_facets, 3); - for (unsigned int i=0; ivertex[0](0); V(3*i+0, 1) = facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2); - V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) = facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2); - V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) = facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2); - F(i, 0) = 3*i+0; - F(i, 1) = 3*i+1; - F(i, 2) = 3*i+2; - } - - m_AABB = igl::AABB(); - m_AABB.init(m_V, m_F); - - // we'll now reload support points (selection might have changed): - editing_mode_reload_cache(); -} - -Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) -{ - // if the gizmo doesn't have the V, F structures for igl, calculate them first: - if (m_V.size() == 0) - update_mesh(); - - Eigen::Matrix viewport; - ::glGetIntegerv(GL_VIEWPORT, viewport.data()); - Eigen::Matrix modelview_matrix; - ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); - Eigen::Matrix projection_matrix; - ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix.data()); - - Vec3d point1; - Vec3d point2; - ::gluUnProject(mouse_pos(0), viewport(3)-mouse_pos(1), 0.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point1(0), &point1(1), &point1(2)); - ::gluUnProject(mouse_pos(0), viewport(3)-mouse_pos(1), 1.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point2(0), &point2(1), &point2(2)); - - igl::Hit hit; - - const GLCanvas3D::Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_offset = volume->get_sla_shift_z(); - - point1(2) -= z_offset; - point2(2) -= z_offset; - - Transform3d inv = volume->get_instance_transformation().get_matrix().inverse(); - - point1 = inv * point1; - point2 = inv * point2; - - if (!m_AABB.intersect_ray(m_V, m_F, point1.cast(), (point2-point1).cast(), hit)) - throw std::invalid_argument("unproject_on_mesh(): No intersection found."); - - int fid = hit.id; - Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); - return bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); -} - -// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. -// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is -// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo -// concludes that the event was not intended for it, it should return false. -bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down) -{ - if (m_editing_mode) { - - // left down - show the selection rectangle: - if (action == SLAGizmoEventType::LeftDown && shift_down) { - if (m_hover_id == -1) { - m_selection_rectangle_active = true; - m_selection_rectangle_start_corner = mouse_position; - m_selection_rectangle_end_corner = mouse_position; - m_canvas_width = m_parent.get_canvas_size().get_width(); - m_canvas_height = m_parent.get_canvas_size().get_height(); - } - else - select_point(m_hover_id); - - return true; - } - - // dragging the selection rectangle: - if (action == SLAGizmoEventType::Dragging && m_selection_rectangle_active) { - m_selection_rectangle_end_corner = mouse_position; - return true; - } - - // mouse up without selection rectangle - place point on the mesh: - if (action == SLAGizmoEventType::LeftUp && !m_selection_rectangle_active && !shift_down) { - if (m_ignore_up_event) { - m_ignore_up_event = false; - return false; - } - - int instance_id = m_parent.get_selection().get_instance_idx(); - if (m_old_instance_id != instance_id) - { - bool something_selected = (m_old_instance_id != -1); - m_old_instance_id = instance_id; - if (something_selected) - return false; - } - if (instance_id == -1) - return false; - - // If there is some selection, don't add new point and deselect everything instead. - if (m_selection_empty) { - Vec3f new_pos; - try { - new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new point in that case - m_editing_mode_cache.emplace_back(std::make_pair(sla::SupportPoint(new_pos, m_new_point_head_diameter/2.f, false), false)); - m_unsaved_changes = true; - } - catch (...) { // not clicked on object - return true; // prevents deselection of the gizmo by GLCanvas3D - } - } - else - select_point(NoPoints); - - return true; - } - - // left up with selection rectangle - select points inside the rectangle: - if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp) - && m_selection_rectangle_active) { - if (action == SLAGizmoEventType::ShiftUp) - m_ignore_up_event = true; - const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix(); - GLint viewport[4]; - ::glGetIntegerv(GL_VIEWPORT, viewport); - GLdouble modelview_matrix[16]; - ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix); - GLdouble projection_matrix[16]; - ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix); - - const GLCanvas3D::Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - double z_offset = volume->get_sla_shift_z(); - - // bounding box created from the rectangle corners - will take care of order of the corners - BoundingBox rectangle(Points{Point(m_selection_rectangle_start_corner.cast()), Point(m_selection_rectangle_end_corner.cast())}); - - const Transform3d& instance_matrix_no_translation = volume->get_instance_transformation().get_matrix(true); - // we'll recover current look direction from the modelview matrix (in world coords)... - Vec3f direction_to_camera(modelview_matrix[2], modelview_matrix[6], modelview_matrix[10]); - // ...and transform it to model coords. - direction_to_camera = (instance_matrix_no_translation.inverse().cast() * direction_to_camera).normalized().eval(); - - // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh: - for (unsigned int i=0; i() * support_point.pos; - pos(2) += z_offset; - GLdouble out_x, out_y, out_z; - ::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z); - out_y = m_canvas_height - out_y; - - if (rectangle.contains(Point(out_x, out_y))) { - bool is_obscured = false; - // Cast a ray in the direction of the camera and look for intersection with the mesh: - std::vector hits; - // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. - if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera * (support_point.head_front_radius + EPSILON), direction_to_camera, hits)) - // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. - // Also, the threshold is in mesh coordinates, not in actual dimensions. - if (hits.size() > 1 || hits.front().t > 0.001f) - is_obscured = true; - - if (!is_obscured) - select_point(i); - } - } - m_selection_rectangle_active = false; - return true; - } - - if (action == SLAGizmoEventType::Delete) { - // delete key pressed - delete_selected_points(); - return true; - } - - if (action == SLAGizmoEventType::ApplyChanges) { - editing_mode_apply_changes(); - return true; - } - - if (action == SLAGizmoEventType::DiscardChanges) { - editing_mode_discard_changes(); - return true; - } - - if (action == SLAGizmoEventType::RightDown) { - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - delete_selected_points(); - return true; - } - return false; - } - - if (action == SLAGizmoEventType::SelectAll) { - select_point(AllPoints); - return true; - } - } - - if (!m_editing_mode) { - if (action == SLAGizmoEventType::AutomaticGeneration) { - auto_generate(); - return true; - } - - if (action == SLAGizmoEventType::ManualEditing) { - switch_to_editing_mode(); - return true; - } - } - - return false; -} - -void GLGizmoSlaSupports::delete_selected_points(bool force) -{ - for (unsigned int idx=0; idxreslice_SLA_supports(*m_model_object); - } - - select_point(NoPoints); - - //m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); -} - -void GLGizmoSlaSupports::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) -{ - if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].first.is_new_island || !m_lock_unique_islands)) { - Vec3f new_pos; - try { - new_pos = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); - } - catch (...) { return; } - m_editing_mode_cache[m_hover_id].first.pos = new_pos; - m_editing_mode_cache[m_hover_id].first.is_new_island = false; - m_unsaved_changes = true; - // Do not update immediately, wait until the mouse is released. - // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - } -} - -#if !ENABLE_IMGUI -void GLGizmoSlaSupports::render_tooltip_texture() const { - if (m_tooltip_texture.get_id() == 0) - if (!m_tooltip_texture.load_from_file(resources_dir() + "/icons/sla_support_points_tooltip.png", false)) - return; - if (m_reset_texture.get_id() == 0) - if (!m_reset_texture.load_from_file(resources_dir() + "/icons/sla_support_points_reset.png", false)) - return; - - float zoom = m_parent.get_camera_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - float gap = 30.0f * inv_zoom; - - const Size& cnv_size = m_parent.get_canvas_size(); - float l = gap - cnv_size.get_width()/2.f * inv_zoom; - float r = l + (float)m_tooltip_texture.get_width() * inv_zoom; - float b = gap - cnv_size.get_height()/2.f * inv_zoom; - float t = b + (float)m_tooltip_texture.get_height() * inv_zoom; - - Rect reset_rect = m_parent.get_gizmo_reset_rect(m_parent, true); - - ::glDisable(GL_DEPTH_TEST); - ::glPushMatrix(); - ::glLoadIdentity(); - GLTexture::render_texture(m_tooltip_texture.get_id(), l, r, b, t); - GLTexture::render_texture(m_reset_texture.get_id(), reset_rect.get_left(), reset_rect.get_right(), reset_rect.get_bottom(), reset_rect.get_top()); - ::glPopMatrix(); - ::glEnable(GL_DEPTH_TEST); -} -#endif // not ENABLE_IMGUI - - -std::vector GLGizmoSlaSupports::get_config_options(const std::vector& keys) const -{ - std::vector out; - - if (!m_model_object) - return out; - - DynamicPrintConfig& object_cfg = m_model_object->config; - DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - std::unique_ptr default_cfg = nullptr; - - for (const std::string& key : keys) { - if (object_cfg.has(key)) - out.push_back(object_cfg.option(key)); - else - if (print_cfg.has(key)) - out.push_back(print_cfg.option(key)); - else { // we must get it from defaults - if (default_cfg == nullptr) - default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); - out.push_back(default_cfg->option(key)); - } - } - - return out; -} - - - -#if ENABLE_IMGUI -void GLGizmoSlaSupports::on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) -{ - if (!m_model_object) - return; - - bool first_run = true; // This is a hack to redraw the button when all points are removed, - // so it is not delayed until the background process finishes. -RENDER_AGAIN: - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->set_next_window_bg_alpha(0.5f); - m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove |/* ImGuiWindowFlags_NoResize | */ImGuiWindowFlags_NoCollapse); - - ImGui::PushItemWidth(100.0f); - - bool force_refresh = false; - bool remove_selected = false; - bool remove_all = false; - - if (m_editing_mode) { - m_imgui->text(_(L("Left mouse click - add point"))); - m_imgui->text(_(L("Right mouse click - remove point"))); - m_imgui->text(_(L("Shift + Left (+ drag) - select point(s)"))); - m_imgui->text(" "); // vertical gap - - std::vector options = {"0.2", "0.4", "0.6", "0.8", "1.0"}; - std::stringstream ss; - ss << std::setprecision(1) << m_new_point_head_diameter; - wxString str = ss.str(); - - bool old_combo_state = m_combo_box_open; - // The combo is commented out for now, until the feature is supported by backend. - // m_combo_box_open = m_imgui->combo(_(L("Head diameter")), options, str); - force_refresh |= (old_combo_state != m_combo_box_open); - - float current_number = atof(str); - if (old_combo_state && !m_combo_box_open) // closing the combo must always change the sizes (even if the selection did not change) - for (auto& point_and_selection : m_editing_mode_cache) - if (point_and_selection.second) { - point_and_selection.first.head_front_radius = current_number / 2.f; - m_unsaved_changes = true; - } - - if (std::abs(current_number - m_new_point_head_diameter) > 0.001) { - force_refresh = true; - m_new_point_head_diameter = current_number; - } - - bool changed = m_lock_unique_islands; - m_imgui->checkbox(_(L("Lock supports under new islands")), m_lock_unique_islands); - force_refresh |= changed != m_lock_unique_islands; - - m_imgui->disabled_begin(m_selection_empty); - remove_selected = m_imgui->button(_(L("Remove selected points"))); - m_imgui->disabled_end(); - - m_imgui->disabled_begin(m_editing_mode_cache.empty()); - remove_all = m_imgui->button(_(L("Remove all points"))); - m_imgui->disabled_end(); - - m_imgui->text(" "); // vertical gap - - if (m_imgui->button(_(L("Apply changes")))) { - editing_mode_apply_changes(); - force_refresh = true; - } - ImGui::SameLine(); - bool discard_changes = m_imgui->button(_(L("Discard changes"))); - if (discard_changes) { - editing_mode_discard_changes(); - force_refresh = true; - } - } - else { // not in editing mode: - ImGui::PushItemWidth(100.0f); - m_imgui->text(_(L("Minimal points distance: "))); - ImGui::SameLine(); - - std::vector opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); - float density = static_cast(opts[0])->value; - float minimal_point_distance = static_cast(opts[1])->value; - - bool value_changed = ImGui::SliderFloat("", &minimal_point_distance, 0.f, 20.f, "%.f mm"); - if (value_changed) - m_model_object->config.opt("support_points_minimal_distance", true)->value = minimal_point_distance; - - m_imgui->text(_(L("Support points density: "))); - ImGui::SameLine(); - if (ImGui::SliderFloat(" ", &density, 0.f, 200.f, "%.f %%")) { - value_changed = true; - m_model_object->config.opt("support_points_density_relative", true)->value = (int)density; - } - - if (value_changed) { // Update side panel - wxGetApp().obj_settings()->UpdateAndShow(true); - wxGetApp().obj_list()->update_settings_items(); - } - - bool generate = m_imgui->button(_(L("Auto-generate points [A]"))); - - if (generate) - auto_generate(); - - m_imgui->text(""); - if (m_imgui->button(_(L("Manual editing [M]")))) - switch_to_editing_mode(); - - m_imgui->disabled_begin(m_editing_mode_cache.empty()); - remove_all = m_imgui->button(_(L("Remove all points"))); - m_imgui->disabled_end(); - - m_imgui->text(""); - - m_imgui->text(m_model_object->sla_points_status == sla::PointsStatus::None ? "No points (will be autogenerated)" : - (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? "Autogenerated points (no modifications)" : - (m_model_object->sla_points_status == sla::PointsStatus::UserModified ? "User-modified points" : - (m_model_object->sla_points_status == sla::PointsStatus::Generating ? "Generation in progress..." : "UNKNOWN STATUS")))); - } - - m_imgui->end(); - - if (m_editing_mode != m_old_editing_state) { // user toggled between editing/non-editing mode - m_parent.toggle_sla_auxiliaries_visibility(!m_editing_mode); - force_refresh = true; - } - m_old_editing_state = m_editing_mode; - - if (remove_selected || remove_all) { - force_refresh = false; - m_parent.reload_scene(true); - if (remove_all) - select_point(AllPoints); - delete_selected_points(remove_all); - if (remove_all && !m_editing_mode) - editing_mode_apply_changes(); - if (first_run) { - first_run = false; - goto RENDER_AGAIN; - } - } - - if (force_refresh) - m_parent.reload_scene(true); -} -#endif // ENABLE_IMGUI - -bool GLGizmoSlaSupports::on_is_activable(const GLCanvas3D::Selection& selection) const -{ - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA - || !selection.is_from_single_instance()) - return false; - - // Check that none of the selected volumes is outside. - const GLCanvas3D::Selection::IndicesList& list = selection.get_volume_idxs(); - for (const auto& idx : list) - if (selection.get_volume(idx)->is_outside) - return false; - - return true; -} - -bool GLGizmoSlaSupports::on_is_selectable() const -{ - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); -} - -std::string GLGizmoSlaSupports::on_get_name() const -{ - return L("SLA Support Points [L]"); -} - -void GLGizmoSlaSupports::on_set_state() -{ - if (m_state == On) { - if (is_mesh_update_necessary()) - update_mesh(); - - m_parent.toggle_model_objects_visibility(false); - if (m_model_object) - m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); - } - if (m_state == Off) { - if (m_old_state != Off) { // the gizmo was just turned Off - - if (m_model_object) { - if (m_unsaved_changes) { - wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L("Do you want to save your manually edited support points ?\n")), - _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); - if (dlg.ShowModal() == wxID_YES) - editing_mode_apply_changes(); - else - editing_mode_discard_changes(); - } - } - - m_parent.toggle_model_objects_visibility(true); - m_editing_mode = false; // so it is not active next time the gizmo opens - m_editing_mode_cache.clear(); - } - } - m_old_state = m_state; -} - - - -void GLGizmoSlaSupports::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - } -} - - - -void GLGizmoSlaSupports::select_point(int i) -{ - if (i == AllPoints || i == NoPoints) { - for (auto& point_and_selection : m_editing_mode_cache) - point_and_selection.second = ( i == AllPoints ); - m_selection_empty = (i == NoPoints); - } - else { - m_editing_mode_cache[i].second = true; - m_selection_empty = false; - } -} - - - -void GLGizmoSlaSupports::editing_mode_discard_changes() -{ - m_editing_mode_cache.clear(); - for (const sla::SupportPoint& point : m_model_object->sla_support_points) - m_editing_mode_cache.push_back(std::make_pair(point, false)); - m_editing_mode = false; - m_unsaved_changes = false; -} - - - -void GLGizmoSlaSupports::editing_mode_apply_changes() -{ - // If there are no changes, don't touch the front-end. The data in the cache could have been - // taken from the backend and copying them to ModelObject would needlessly invalidate them. - if (m_unsaved_changes) { - m_model_object->sla_points_status = sla::PointsStatus::UserModified; - m_model_object->sla_support_points.clear(); - for (const std::pair& point_and_selection : m_editing_mode_cache) - m_model_object->sla_support_points.push_back(point_and_selection.first); - - // Recalculate support structures once the editing mode is left. - // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - wxGetApp().plater()->reslice_SLA_supports(*m_model_object); - } - m_editing_mode = false; - m_unsaved_changes = false; -} - - - -void GLGizmoSlaSupports::editing_mode_reload_cache() -{ - m_editing_mode_cache.clear(); - for (const sla::SupportPoint& point : m_model_object->sla_support_points) - m_editing_mode_cache.push_back(std::make_pair(point, false)); - m_unsaved_changes = false; -} - - - -void GLGizmoSlaSupports::get_data_from_backend() -{ - for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == m_model_object->id() && po->is_step_done(slaposSupportPoints)) { - m_editing_mode_cache.clear(); - const std::vector& points = po->get_support_points(); - auto mat = po->trafo().inverse().cast(); - for (unsigned int i=0; isla_points_status != sla::PointsStatus::UserModified) - m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated; - - break; - } - } - m_unsaved_changes = false; - - // We don't copy the data into ModelObject, as this would stop the background processing. -} - - - -void GLGizmoSlaSupports::auto_generate() -{ - wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L( - "Autogeneration will erase all manually edited points.\n\n" - "Are you sure you want to do it?\n" - )), _(L("Warning")), wxICON_WARNING | wxYES | wxNO); - - if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_editing_mode_cache.empty() || dlg.ShowModal() == wxID_YES) { - m_model_object->sla_support_points.clear(); - m_model_object->sla_points_status = sla::PointsStatus::Generating; - m_editing_mode_cache.clear(); - wxGetApp().plater()->reslice_SLA_supports(*m_model_object); - } -} - - - -void GLGizmoSlaSupports::switch_to_editing_mode() -{ - editing_mode_reload_cache(); - m_editing_mode = true; -} - - - - - - -// GLGizmoCut - -class GLGizmoCutPanel : public wxPanel -{ -public: - GLGizmoCutPanel(wxWindow *parent); - - void display(bool display); -private: - bool m_active; - wxCheckBox *m_cb_rotate; - wxButton *m_btn_cut; - wxButton *m_btn_cancel; -}; - -GLGizmoCutPanel::GLGizmoCutPanel(wxWindow *parent) - : wxPanel(parent) - , m_active(false) - , m_cb_rotate(new wxCheckBox(this, wxID_ANY, _(L("Rotate lower part upwards")))) - , m_btn_cut(new wxButton(this, wxID_OK, _(L("Perform cut")))) - , m_btn_cancel(new wxButton(this, wxID_CANCEL, _(L("Cancel")))) -{ - enum { MARGIN = 5 }; - - auto *sizer = new wxBoxSizer(wxHORIZONTAL); - - auto *label = new wxStaticText(this, wxID_ANY, _(L("Cut object:"))); - sizer->Add(label, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->Add(m_cb_rotate, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->AddStretchSpacer(); - sizer->Add(m_btn_cut, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->Add(m_btn_cancel, 0, wxALL | wxALIGN_CENTER, MARGIN); - - SetSizer(sizer); -} - -void GLGizmoCutPanel::display(bool display) -{ - Show(display); - GetParent()->Layout(); -} - - -const double GLGizmoCut::Offset = 10.0; -const double GLGizmoCut::Margin = 20.0; -const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0 }; - -GLGizmoCut::GLGizmoCut(GLCanvas3D& parent) - : GLGizmoBase(parent) - , m_cut_z(0.0) - , m_max_z(0.0) -#if !ENABLE_IMGUI - , m_panel(nullptr) -#endif // not ENABLE_IMGUI - , m_keep_upper(true) - , m_keep_lower(true) - , m_rotate_lower(false) -{} - -#if !ENABLE_IMGUI -void GLGizmoCut::create_external_gizmo_widgets(wxWindow *parent) -{ - wxASSERT(m_panel == nullptr); - - m_panel = new GLGizmoCutPanel(parent); - parent->GetSizer()->Add(m_panel, 0, wxEXPAND); - - parent->Layout(); - parent->Fit(); - auto prev_heigh = parent->GetMinSize().GetHeight(); - parent->SetMinSize(wxSize(-1, std::max(prev_heigh, m_panel->GetSize().GetHeight()))); - - m_panel->Hide(); - m_panel->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { - perform_cut(m_parent.get_selection()); - }, wxID_OK); -} -#endif // not ENABLE_IMGUI - -bool GLGizmoCut::on_init() -{ - // TODO: icon - - std::string path = resources_dir() + "/icons/overlay/"; - - if (!m_textures[Off].load_from_file(path + "cut_off.png", false)) { - return false; - } - - if (!m_textures[Hover].load_from_file(path + "cut_hover.png", false)) { - return false; - } - - if (!m_textures[On].load_from_file(path + "cut_on.png", false)) { - return false; - } - - m_grabbers.emplace_back(); - - m_shortcut_key = WXK_CONTROL_C; - - return true; -} - -std::string GLGizmoCut::on_get_name() const -{ - return L("Cut [C]"); -} - -void GLGizmoCut::on_set_state() -{ - // Reset m_cut_z on gizmo activation - if (get_state() == On) { - m_cut_z = m_parent.get_selection().get_bounding_box().size()(2) / 2.0; - } - -#if !ENABLE_IMGUI - // Display or hide the extra panel - if (m_panel != nullptr) { - m_panel->display(get_state() == On); - } -#endif // not ENABLE_IMGUI -} - -bool GLGizmoCut::on_is_activable(const GLCanvas3D::Selection& selection) const -{ - return selection.is_single_full_instance() && !selection.is_wipe_tower(); -} - -void GLGizmoCut::on_start_dragging(const GLCanvas3D::Selection& selection) -{ - if (m_hover_id == -1) { return; } - - const BoundingBoxf3& box = selection.get_bounding_box(); - m_start_z = m_cut_z; - update_max_z(selection); - m_drag_pos = m_grabbers[m_hover_id].center; - m_drag_center = box.center(); - m_drag_center(2) = m_cut_z; -} - -void GLGizmoCut::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) -{ - if (m_hover_id != -1) { - set_cut_z(m_start_z + calc_projection(data.mouse_ray)); - } -} - -void GLGizmoCut::on_render(const GLCanvas3D::Selection& selection) const -{ - if (m_grabbers[0].dragging) { - set_tooltip("Z: " + format(m_cut_z, 2)); - } - - update_max_z(selection); - - const BoundingBoxf3& box = selection.get_bounding_box(); - Vec3d plane_center = box.center(); - plane_center(2) = m_cut_z; - - const float min_x = box.min(0) - Margin; - const float max_x = box.max(0) + Margin; - const float min_y = box.min(1) - Margin; - const float max_y = box.max(1) + Margin; - ::glEnable(GL_DEPTH_TEST); - ::glDisable(GL_CULL_FACE); - ::glEnable(GL_BLEND); - ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Draw the cutting plane - ::glBegin(GL_QUADS); - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - ::glVertex3f(min_x, min_y, plane_center(2)); - ::glVertex3f(max_x, min_y, plane_center(2)); - ::glVertex3f(max_x, max_y, plane_center(2)); - ::glVertex3f(min_x, max_y, plane_center(2)); - ::glEnd(); - - ::glEnable(GL_CULL_FACE); - ::glDisable(GL_BLEND); - - // TODO: draw cut part contour? - - // Draw the grabber and the connecting line - m_grabbers[0].center = plane_center; - m_grabbers[0].center(2) = plane_center(2) + Offset; - - ::glDisable(GL_DEPTH_TEST); - ::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f); - ::glColor3f(1.0, 1.0, 0.0); - ::glBegin(GL_LINES); - ::glVertex3dv(plane_center.data()); - ::glVertex3dv(m_grabbers[0].center.data()); - ::glEnd(); - - std::copy(std::begin(GrabberColor), std::end(GrabberColor), m_grabbers[0].color); - m_grabbers[0].render(m_hover_id == 0, box.max_size()); -} - -void GLGizmoCut::on_render_for_picking(const GLCanvas3D::Selection& selection) const -{ - ::glDisable(GL_DEPTH_TEST); - - render_grabbers_for_picking(selection.get_bounding_box()); -} - -#if ENABLE_IMGUI -void GLGizmoCut::on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) -{ - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->set_next_window_bg_alpha(0.5f); - m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - ImGui::PushItemWidth(100.0f); - bool _value_changed = ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); - - m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper); - m_imgui->checkbox(_(L("Keep lower part")), m_keep_lower); - m_imgui->checkbox(_(L("Rotate lower part upwards")), m_rotate_lower); - - m_imgui->disabled_begin(!m_keep_upper && !m_keep_lower); - const bool cut_clicked = m_imgui->button(_(L("Perform cut"))); - m_imgui->disabled_end(); - - m_imgui->end(); - - if (cut_clicked && (m_keep_upper || m_keep_lower)) { - perform_cut(selection); - } -} -#endif // ENABLE_IMGUI - -void GLGizmoCut::update_max_z(const GLCanvas3D::Selection& selection) const -{ - m_max_z = selection.get_bounding_box().size()(2); - set_cut_z(m_cut_z); -} - -void GLGizmoCut::set_cut_z(double cut_z) const -{ - // Clamp the plane to the object's bounding box - m_cut_z = std::max(0.0, std::min(m_max_z, cut_z)); -} - -void GLGizmoCut::perform_cut(const GLCanvas3D::Selection& selection) -{ - const auto instance_idx = selection.get_instance_idx(); - const auto object_idx = selection.get_object_idx(); - - wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); - - wxGetApp().plater()->cut(object_idx, instance_idx, m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); -} - -double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const -{ - double projection = 0.0; - - const Vec3d starting_vec = m_drag_pos - m_drag_center; - const double len_starting_vec = starting_vec.norm(); - if (len_starting_vec != 0.0) - { - Vec3d mouse_dir = mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; - // vector from the starting position to the found intersection - Vec3d inters_vec = inters - m_drag_pos; - - // finds projection of the vector along the staring direction - projection = inters_vec.dot(starting_vec.normalized()); - } - return projection; -} - - -} // namespace GUI -} // namespace Slic3r diff --git a/src/slic3r/GUI/GLGizmo.hpp b/src/slic3r/GUI/GLGizmo.hpp deleted file mode 100644 index 18ddbac69..000000000 --- a/src/slic3r/GUI/GLGizmo.hpp +++ /dev/null @@ -1,596 +0,0 @@ -#ifndef slic3r_GLGizmo_hpp_ -#define slic3r_GLGizmo_hpp_ - -#include - -#include "../../slic3r/GUI/GLTexture.hpp" -#include "../../slic3r/GUI/GLCanvas3D.hpp" - -#include "libslic3r/Point.hpp" -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/SLA/SLAAutoSupports.hpp" - -#include -#include -#include - - -class wxWindow; -class GLUquadric; -typedef class GLUquadric GLUquadricObj; - - -namespace Slic3r { - -class BoundingBoxf3; -class Linef3; -class ModelObject; - -namespace GUI { - -class GLCanvas3D; -#if ENABLE_IMGUI -class ImGuiWrapper; -#endif // ENABLE_IMGUI - -class GLGizmoBase -{ -protected: - struct Grabber - { - static const float SizeFactor; - static const float MinHalfSize; - static const float DraggingScaleFactor; - - Vec3d center; - Vec3d angles; - float color[3]; - bool enabled; - bool dragging; - - Grabber(); - - void render(bool hover, float size) const; - void render_for_picking(float size) const { render(size, color, false); } - - float get_half_size(float size) const; - float get_dragging_half_size(float size) const; - - private: - void render(float size, const float* render_color, bool use_lighting) const; - void render_face(float half_size) const; - }; - -public: - enum EState - { - Off, - Hover, - On, - Num_States - }; - - struct UpdateData - { - const Linef3 mouse_ray; - const Point* mouse_pos; - bool shift_down; - - UpdateData(const Linef3& mouse_ray, const Point* mouse_pos = nullptr, bool shift_down = false) - : mouse_ray(mouse_ray), mouse_pos(mouse_pos), shift_down(shift_down) - {} - }; - -protected: - GLCanvas3D& m_parent; - - int m_group_id; - EState m_state; - int m_shortcut_key; - // textures are assumed to be square and all with the same size in pixels, no internal check is done - GLTexture m_textures[Num_States]; - int m_hover_id; - bool m_dragging; - float m_base_color[3]; - float m_drag_color[3]; - float m_highlight_color[3]; - mutable std::vector m_grabbers; -#if ENABLE_IMGUI - ImGuiWrapper* m_imgui; -#endif // ENABLE_IMGUI - -public: - explicit GLGizmoBase(GLCanvas3D& parent); - virtual ~GLGizmoBase() {} - - bool init() { return on_init(); } - - std::string get_name() const { return on_get_name(); } - - int get_group_id() const { return m_group_id; } - void set_group_id(int id) { m_group_id = id; } - - EState get_state() const { return m_state; } - void set_state(EState state) { m_state = state; on_set_state(); } - - int get_shortcut_key() const { return m_shortcut_key; } - void set_shortcut_key(int key) { m_shortcut_key = key; } - - bool is_activable(const GLCanvas3D::Selection& selection) const { return on_is_activable(selection); } - bool is_selectable() const { return on_is_selectable(); } - - unsigned int get_texture_id() const { return m_textures[m_state].get_id(); } - int get_textures_size() const { return m_textures[Off].get_width(); } - - int get_hover_id() const { return m_hover_id; } - void set_hover_id(int id); - - void set_highlight_color(const float* color); - - void enable_grabber(unsigned int id); - void disable_grabber(unsigned int id); - - void start_dragging(const GLCanvas3D::Selection& selection); - void stop_dragging(); - bool is_dragging() const { return m_dragging; } - - void update(const UpdateData& data, const GLCanvas3D::Selection& selection); - - void render(const GLCanvas3D::Selection& selection) const { on_render(selection); } - void render_for_picking(const GLCanvas3D::Selection& selection) const { on_render_for_picking(selection); } - -#if !ENABLE_IMGUI - virtual void create_external_gizmo_widgets(wxWindow *parent); -#endif // not ENABLE_IMGUI - -#if ENABLE_IMGUI - void render_input_window(float x, float y, const GLCanvas3D::Selection& selection) { on_render_input_window(x, y, selection); } -#endif // ENABLE_IMGUI - -protected: - virtual bool on_init() = 0; - virtual std::string on_get_name() const = 0; - virtual void on_set_state() {} - virtual void on_set_hover_id() {} - virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return true; } - virtual bool on_is_selectable() const { return true; } - virtual void on_enable_grabber(unsigned int id) {} - virtual void on_disable_grabber(unsigned int id) {} - virtual void on_start_dragging(const GLCanvas3D::Selection& selection) {} - virtual void on_stop_dragging() {} - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) = 0; - virtual void on_render(const GLCanvas3D::Selection& selection) const = 0; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const = 0; - -#if ENABLE_IMGUI - virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) {} -#endif // ENABLE_IMGUI - - float picking_color_component(unsigned int id) const; - void render_grabbers(const BoundingBoxf3& box) const; - void render_grabbers(float size) const; - void render_grabbers_for_picking(const BoundingBoxf3& box) const; - - void set_tooltip(const std::string& tooltip) const; - std::string format(float value, unsigned int decimals) const; -}; - -class GLGizmoRotate : public GLGizmoBase -{ - static const float Offset; - static const unsigned int CircleResolution; - static const unsigned int AngleResolution; - static const unsigned int ScaleStepsCount; - static const float ScaleStepRad; - static const unsigned int ScaleLongEvery; - static const float ScaleLongTooth; - static const unsigned int SnapRegionsCount; - static const float GrabberOffset; - -public: - enum Axis : unsigned char - { - X, - Y, - Z - }; - -private: - Axis m_axis; - double m_angle; - - GLUquadricObj* m_quadric; - - mutable Vec3d m_center; - mutable float m_radius; - - mutable float m_snap_coarse_in_radius; - mutable float m_snap_coarse_out_radius; - mutable float m_snap_fine_in_radius; - mutable float m_snap_fine_out_radius; - -public: - GLGizmoRotate(GLCanvas3D& parent, Axis axis); - GLGizmoRotate(const GLGizmoRotate& other); - virtual ~GLGizmoRotate(); - - double get_angle() const { return m_angle; } - void set_angle(double angle); - -protected: - virtual bool on_init(); - virtual std::string on_get_name() const { return ""; } - virtual void on_start_dragging(const GLCanvas3D::Selection& selection); - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; - -private: - void render_circle() const; - void render_scale() const; - void render_snap_radii() const; - void render_reference_radius() const; - void render_angle() const; - void render_grabber(const BoundingBoxf3& box) const; - void render_grabber_extension(const BoundingBoxf3& box, bool picking) const; - - void transform_to_local(const GLCanvas3D::Selection& selection) const; - // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate - Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const GLCanvas3D::Selection& selection) const; -}; - -class GLGizmoRotate3D : public GLGizmoBase -{ - std::vector m_gizmos; - -public: - explicit GLGizmoRotate3D(GLCanvas3D& parent); - - Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } - void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } - -protected: - virtual bool on_init(); - virtual std::string on_get_name() const; - virtual void on_set_state() - { - for (GLGizmoRotate& g : m_gizmos) - { - g.set_state(m_state); - } - } - virtual void on_set_hover_id() - { - for (unsigned int i = 0; i < 3; ++i) - { - m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); - } - } - virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return !selection.is_wipe_tower(); } - virtual void on_enable_grabber(unsigned int id) - { - if ((0 <= id) && (id < 3)) - m_gizmos[id].enable_grabber(0); - } - virtual void on_disable_grabber(unsigned int id) - { - if ((0 <= id) && (id < 3)) - m_gizmos[id].disable_grabber(0); - } - virtual void on_start_dragging(const GLCanvas3D::Selection& selection); - virtual void on_stop_dragging(); - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) - { - for (GLGizmoRotate& g : m_gizmos) - { - g.update(data, selection); - } - } - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const - { - for (const GLGizmoRotate& g : m_gizmos) - { - g.render_for_picking(selection); - } - } - -#if ENABLE_IMGUI - virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection); -#endif // ENABLE_IMGUI -}; - -class GLGizmoScale3D : public GLGizmoBase -{ - static const float Offset; - - mutable BoundingBoxf3 m_box; - - Vec3d m_scale; - - double m_snap_step; - - Vec3d m_starting_scale; - Vec3d m_starting_drag_position; - BoundingBoxf3 m_starting_box; - -public: - explicit GLGizmoScale3D(GLCanvas3D& parent); - - double get_snap_step(double step) const { return m_snap_step; } - void set_snap_step(double step) { m_snap_step = step; } - - const Vec3d& get_scale() const { return m_scale; } - void set_scale(const Vec3d& scale) { m_starting_scale = scale; m_scale = scale; } - -protected: - virtual bool on_init(); - virtual std::string on_get_name() const; - virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return !selection.is_wipe_tower(); } - virtual void on_start_dragging(const GLCanvas3D::Selection& selection); - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; - -#if ENABLE_IMGUI - virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection); -#endif // ENABLE_IMGUI - -private: - void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const; - - void do_scale_x(const UpdateData& data); - void do_scale_y(const UpdateData& data); - void do_scale_z(const UpdateData& data); - void do_scale_uniform(const UpdateData& data); - - double calc_ratio(const UpdateData& data) const; -}; - -class GLGizmoMove3D : public GLGizmoBase -{ - static const double Offset; - - Vec3d m_displacement; - - double m_snap_step; - - Vec3d m_starting_drag_position; - Vec3d m_starting_box_center; - Vec3d m_starting_box_bottom_center; - - GLUquadricObj* m_quadric; - -public: - explicit GLGizmoMove3D(GLCanvas3D& parent); - virtual ~GLGizmoMove3D(); - - double get_snap_step(double step) const { return m_snap_step; } - void set_snap_step(double step) { m_snap_step = step; } - - const Vec3d& get_displacement() const { return m_displacement; } - -protected: - virtual bool on_init(); - virtual std::string on_get_name() const; - virtual void on_start_dragging(const GLCanvas3D::Selection& selection); - virtual void on_stop_dragging(); - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; - -#if ENABLE_IMGUI - virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection); -#endif // ENABLE_IMGUI - -private: - double calc_projection(const UpdateData& data) const; - void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const; -}; - -class GLGizmoFlatten : public GLGizmoBase -{ -// This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself. - -private: - mutable Vec3d m_normal; - - struct PlaneData { - std::vector vertices; - Vec3d normal; - float area; - }; - - // This holds information to decide whether recalculation is necessary: - std::vector m_volumes_matrices; - std::vector m_volumes_types; - Vec3d m_first_instance_scale; - Vec3d m_first_instance_mirror; - - std::vector m_planes; - bool m_planes_valid = false; - mutable Vec3d m_starting_center; - const ModelObject* m_model_object = nullptr; - std::vector instances_matrices; - - void update_planes(); - bool is_plane_update_necessary() const; - -public: - explicit GLGizmoFlatten(GLCanvas3D& parent); - - void set_flattening_data(const ModelObject* model_object); - Vec3d get_flattening_normal() const; - -protected: - virtual bool on_init(); - virtual std::string on_get_name() const; - virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const; - virtual void on_start_dragging(const GLCanvas3D::Selection& selection); - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) {} - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; - virtual void on_set_state() - { - if (m_state == On && is_plane_update_necessary()) - update_planes(); - } -}; - - - -class GLGizmoSlaSupports : public GLGizmoBase -{ -private: - ModelObject* m_model_object = nullptr; - ModelObject* m_old_model_object = nullptr; - int m_active_instance = -1; - int m_old_instance_id = -1; - Vec3f unproject_on_mesh(const Vec2d& mouse_pos); - - const float RenderPointScale = 1.f; - - GLUquadricObj* m_quadric; - Eigen::MatrixXf m_V; // vertices - Eigen::MatrixXi m_F; // facets indices - igl::AABB m_AABB; - - struct SourceDataSummary { - Geometry::Transformation transformation; - }; - - // This holds information to decide whether recalculation is necessary: - SourceDataSummary m_source_data; - - mutable Vec3d m_starting_center; - -public: - explicit GLGizmoSlaSupports(GLCanvas3D& parent); - virtual ~GLGizmoSlaSupports(); - void set_sla_support_data(ModelObject* model_object, const GLCanvas3D::Selection& selection); - bool mouse_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down); - void delete_selected_points(bool force = false); - -private: - bool on_init(); - void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; - - void render_selection_rectangle() const; - void render_points(const GLCanvas3D::Selection& selection, bool picking = false) const; - bool is_mesh_update_necessary() const; - void update_mesh(); - -#if !ENABLE_IMGUI - void render_tooltip_texture() const; - mutable GLTexture m_tooltip_texture; - mutable GLTexture m_reset_texture; -#endif // not ENABLE_IMGUI - - bool m_lock_unique_islands = false; - bool m_editing_mode = false; // Is editing mode active? - bool m_old_editing_state = false; // To keep track of whether the user toggled between the modes (needed for imgui refreshes). - float m_new_point_head_diameter = 0.4f; // Size of a new point. - float m_minimal_point_distance = 20.f; - float m_density = 100.f; - std::vector> m_editing_mode_cache; // a support point and whether it is currently selected - - bool m_selection_rectangle_active = false; - Vec2d m_selection_rectangle_start_corner; - Vec2d m_selection_rectangle_end_corner; - bool m_ignore_up_event = false; - bool m_combo_box_open = false; // To ensure proper rendering of the imgui combobox. - bool m_unsaved_changes = false; // Are there unsaved changes in manual mode? - bool m_selection_empty = true; - EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) - int m_canvas_width; - int m_canvas_height; - - std::vector get_config_options(const std::vector& keys) const; - - // Methods that do the model_object and editing cache synchronization, - // editing mode selection, etc: - enum { - AllPoints = -2, - NoPoints, - }; - void select_point(int i); - void editing_mode_apply_changes(); - void editing_mode_discard_changes(); - void editing_mode_reload_cache(); - void get_data_from_backend(); - void auto_generate(); - void switch_to_editing_mode(); - -protected: - void on_set_state() override; - void on_start_dragging(const GLCanvas3D::Selection& selection) override; - -#if ENABLE_IMGUI - virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) override; -#endif // ENABLE_IMGUI - - virtual std::string on_get_name() const; - virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const; - virtual bool on_is_selectable() const; -}; - - -#if !ENABLE_IMGUI -class GLGizmoCutPanel; -#endif // not ENABLE_IMGUI - -class GLGizmoCut : public GLGizmoBase -{ - static const double Offset; - static const double Margin; - static const std::array GrabberColor; - - mutable double m_cut_z; - double m_start_z; - mutable double m_max_z; - Vec3d m_drag_pos; - Vec3d m_drag_center; - bool m_keep_upper; - bool m_keep_lower; - bool m_rotate_lower; -#if !ENABLE_IMGUI - GLGizmoCutPanel *m_panel; -#endif // not ENABLE_IMGUI - -public: - explicit GLGizmoCut(GLCanvas3D& parent); - -#if !ENABLE_IMGUI - virtual void create_external_gizmo_widgets(wxWindow *parent); -#endif // not ENABLE_IMGUI -#if !ENABLE_IMGUI -#endif // not ENABLE_IMGUI - -protected: - virtual bool on_init(); - virtual std::string on_get_name() const; - virtual void on_set_state(); - virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const; - virtual void on_start_dragging(const GLCanvas3D::Selection& selection); - virtual void on_update(const UpdateData& data, const GLCanvas3D::Selection& selection); - virtual void on_render(const GLCanvas3D::Selection& selection) const; - virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const; - -#if ENABLE_IMGUI - virtual void on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection); -#endif // ENABLE_IMGUI -private: - void update_max_z(const GLCanvas3D::Selection& selection) const; - void set_cut_z(double cut_z) const; - void perform_cut(const GLCanvas3D::Selection& selection); - double calc_projection(const Linef3& mouse_ray) const; -}; - - -} // namespace GUI -} // namespace Slic3r - -#endif // slic3r_GLGizmo_hpp_ - diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 19eff88e0..b48ca2044 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -6,19 +6,17 @@ #include #include -#if ENABLE_TEXTURES_FROM_SVG #include -#endif // ENABLE_TEXTURES_FROM_SVG #include #include -#if ENABLE_TEXTURES_FROM_SVG #define NANOSVG_IMPLEMENTATION #include "nanosvg/nanosvg.h" #define NANOSVGRAST_IMPLEMENTATION #include "nanosvg/nanosvgrast.h" -#endif // ENABLE_TEXTURES_FROM_SVG + +#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp" @@ -40,7 +38,6 @@ GLTexture::~GLTexture() reset(); } -#if ENABLE_TEXTURES_FROM_SVG bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps) { reset(); @@ -66,25 +63,20 @@ bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps else return false; } -#else -bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps) + +bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px) { reset(); - if (!boost::filesystem::exists(filename)) + if (filenames.empty() || states.empty() || (sprite_size_px == 0)) return false; - // Load a PNG with an alpha channel. - wxImage image; - if (!image.LoadFile(wxString::FromUTF8(filename.c_str()), wxBITMAP_TYPE_PNG)) - { - reset(); - return false; - } - - m_width = image.GetWidth(); - m_height = image.GetHeight(); + m_width = (int)(sprite_size_px * states.size()); + m_height = (int)(sprite_size_px * filenames.size()); int n_pixels = m_width * m_height; + int sprite_n_pixels = sprite_size_px * sprite_size_px; + int sprite_bytes = sprite_n_pixels * 4; + int sprite_stride = sprite_size_px * 4; if (n_pixels <= 0) { @@ -92,52 +84,136 @@ bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps) return false; } - // Get RGB & alpha raw data from wxImage, pack them into an array. - unsigned char* img_rgb = image.GetData(); - if (img_rgb == nullptr) + std::vector data(n_pixels * 4, 0); + std::vector sprite_data(sprite_bytes, 0); + std::vector sprite_white_only_data(sprite_bytes, 0); + std::vector sprite_gray_only_data(sprite_bytes, 0); + std::vector output_data(sprite_bytes, 0); + + NSVGrasterizer* rast = nsvgCreateRasterizer(); + if (rast == nullptr) { reset(); return false; } - unsigned char* img_alpha = image.GetAlpha(); - - std::vector data(n_pixels * 4, 0); - for (int i = 0; i < n_pixels; ++i) + int sprite_id = -1; + for (const std::string& filename : filenames) { - int data_id = i * 4; - int img_id = i * 3; - data[data_id + 0] = img_rgb[img_id + 0]; - data[data_id + 1] = img_rgb[img_id + 1]; - data[data_id + 2] = img_rgb[img_id + 2]; - data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255; + ++sprite_id; + + if (!boost::filesystem::exists(filename)) + continue; + + if (!boost::algorithm::iends_with(filename, ".svg")) + continue; + + NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f); + if (image == nullptr) + continue; + + float scale = (float)sprite_size_px / std::max(image->width, image->height); + + nsvgRasterize(rast, image, 0, 0, scale, sprite_data.data(), sprite_size_px, sprite_size_px, sprite_stride); + + // makes white only copy of the sprite + ::memcpy((void*)sprite_white_only_data.data(), (const void*)sprite_data.data(), sprite_bytes); + for (int i = 0; i < sprite_n_pixels; ++i) + { + int offset = i * 4; + if (sprite_white_only_data.data()[offset] != 0) + ::memset((void*)&sprite_white_only_data.data()[offset], 255, 3); + } + + // makes gray only copy of the sprite + ::memcpy((void*)sprite_gray_only_data.data(), (const void*)sprite_data.data(), sprite_bytes); + for (int i = 0; i < sprite_n_pixels; ++i) + { + int offset = i * 4; + if (sprite_gray_only_data.data()[offset] != 0) + ::memset((void*)&sprite_gray_only_data.data()[offset], 128, 3); + } + + int sprite_offset_px = sprite_id * sprite_size_px * m_width; + int state_id = -1; + for (const std::pair& state : states) + { + ++state_id; + + // select the sprite variant + std::vector* src = nullptr; + switch (state.first) + { + case 1: { src = &sprite_white_only_data; break; } + case 2: { src = &sprite_gray_only_data; break; } + default: { src = &sprite_data; break; } + } + + ::memcpy((void*)output_data.data(), (const void*)src->data(), sprite_bytes); + // applies background, if needed + if (state.second) + { + for (int i = 0; i < sprite_n_pixels; ++i) + { + int offset = i * 4; + float alpha = (float)output_data.data()[offset + 3] / 255.0f; + output_data.data()[offset + 0] = (unsigned char)(output_data.data()[offset + 0] * alpha); + output_data.data()[offset + 1] = (unsigned char)(output_data.data()[offset + 1] * alpha); + output_data.data()[offset + 2] = (unsigned char)(output_data.data()[offset + 2] * alpha); + output_data.data()[offset + 3] = (unsigned char)(128 * (1.0f - alpha) + output_data.data()[offset + 3] * alpha); + } + } + + int state_offset_px = sprite_offset_px + state_id * sprite_size_px; + for (int j = 0; j < (int)sprite_size_px; ++j) + { + ::memcpy((void*)&data.data()[(state_offset_px + j * m_width) * 4], (const void*)&output_data.data()[j * sprite_stride], sprite_stride); + } + } + + nsvgDelete(image); } + nsvgDeleteRasterizer(rast); + // sends data to gpu ::glPixelStorei(GL_UNPACK_ALIGNMENT, 1); ::glGenTextures(1, &m_id); ::glBindTexture(GL_TEXTURE_2D, m_id); ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); - if (use_mipmaps) - { - // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards - unsigned int levels_count = generate_mipmaps(image); - ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1 + levels_count); - ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } - else - { - ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); - } + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); ::glBindTexture(GL_TEXTURE_2D, 0); - m_source = filename; + m_source = filenames.front(); + +#if 0 + // debug output + static int pass = 0; + ++pass; + + wxImage output(m_width, m_height); + output.InitAlpha(); + + for (int h = 0; h < m_height; ++h) + { + int px_h = h * m_width; + for (int w = 0; w < m_width; ++w) + { + int offset = (px_h + w) * 4; + output.SetRGB(w, h, data.data()[offset + 0], data.data()[offset + 1], data.data()[offset + 2]); + output.SetAlpha(w, h, data.data()[offset + 3]); + } + } + + std::string out_filename = resources_dir() + "/icons/test_" + std::to_string(pass) + ".png"; + output.SaveFile(out_filename, wxBITMAP_TYPE_PNG); +#endif // 0 + return true; } -#endif // ENABLE_TEXTURES_FROM_SVG void GLTexture::reset() { @@ -216,7 +292,6 @@ unsigned int GLTexture::generate_mipmaps(wxImage& image) return (unsigned int)level; } -#if ENABLE_TEXTURES_FROM_SVG bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps) { // Load a PNG with an alpha channel. @@ -267,13 +342,13 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards unsigned int levels_count = generate_mipmaps(image); - ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1 + levels_count); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels_count); ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); } else { ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); } ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -342,13 +417,13 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns ::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()); } - ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1 + level); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level); ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); } else { ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); } ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -361,7 +436,6 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns return true; } -#endif // ENABLE_TEXTURES_FROM_SVG } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index af41ac342..e00b3a3be 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -2,6 +2,7 @@ #define slic3r_GLTexture_hpp_ #include +#include class wxImage; @@ -38,9 +39,16 @@ namespace GUI { virtual ~GLTexture(); bool load_from_file(const std::string& filename, bool use_mipmaps); -#if ENABLE_TEXTURES_FROM_SVG bool load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); -#endif // ENABLE_TEXTURES_FROM_SVG + // meanings of states: (std::pair) + // first field (int): + // 0 -> no changes + // 1 -> use white only color variant + // 2 -> use gray only color variant + // second field (bool): + // false -> no changes + // true -> add background color + bool load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px); void reset(); unsigned int get_id() const { return m_id; } @@ -54,11 +62,9 @@ namespace GUI { protected: unsigned int generate_mipmaps(wxImage& image); -#if ENABLE_TEXTURES_FROM_SVG private: bool load_from_png(const std::string& filename, bool use_mipmaps); bool load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); -#endif // ENABLE_TEXTURES_FROM_SVG }; } // namespace GUI diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 8d04868f8..2f7f13f6f 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -30,49 +30,70 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent); wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_3D, SimpleEvent); wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent); +const GLToolbarItem::ActionCallback GLToolbarItem::Default_Action_Callback = [](){}; +const GLToolbarItem::VisibilityCallback GLToolbarItem::Default_Visibility_Callback = []()->bool { return true; }; +const GLToolbarItem::EnabledStateCallback GLToolbarItem::Default_Enabled_State_Callback = []()->bool { return true; }; + GLToolbarItem::Data::Data() : name("") +#if ENABLE_SVG_ICONS + , icon_filename("") +#endif // ENABLE_SVG_ICONS , tooltip("") , sprite_id(-1) , is_toggable(false) -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS , visible(true) -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS + , action_callback(Default_Action_Callback) + , visibility_callback(Default_Visibility_Callback) + , enabled_state_callback(Default_Enabled_State_Callback) { } GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Data& data) : m_type(type) - , m_state(Disabled) + , m_state(Normal) , m_data(data) { } -void GLToolbarItem::do_action(wxEvtHandler *target) +bool GLToolbarItem::update_visibility() { - wxPostEvent(target, SimpleEvent(m_data.action_event)); + bool visible = m_data.visibility_callback(); + bool ret = (m_data.visible != visible); + if (ret) + m_data.visible = visible; + + return ret; } -void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const +bool GLToolbarItem::update_enabled_state() { - GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(texture_size, border_size, icon_size, gap_size)); + bool enabled = m_data.enabled_state_callback(); + bool ret = (is_enabled() != enabled); + if (ret) + m_state = enabled ? GLToolbarItem::Normal : GLToolbarItem::Disabled; + + return ret; } -GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const +void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const +{ + GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(tex_width, tex_height, icon_size)); +} + +GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const { GLTexture::Quad_UVs uvs; - float inv_texture_size = (texture_size != 0) ? 1.0f / (float)texture_size : 0.0f; + float inv_tex_width = (tex_width != 0) ? 1.0f / (float)tex_width : 0.0f; + float inv_tex_height = (tex_height != 0) ? 1.0f / (float)tex_height : 0.0f; - float scaled_icon_size = (float)icon_size * inv_texture_size; - float scaled_border_size = (float)border_size * inv_texture_size; - float scaled_gap_size = (float)gap_size * inv_texture_size; - float stride = scaled_icon_size + scaled_gap_size; - - float left = scaled_border_size + (float)m_state * stride; - float right = left + scaled_icon_size; - float top = scaled_border_size + (float)m_data.sprite_id * stride; - float bottom = top + scaled_icon_size; + float scaled_icon_width = (float)icon_size * inv_tex_width; + float scaled_icon_height = (float)icon_size * inv_tex_height; + float left = (float)m_state * scaled_icon_width; + float right = left + scaled_icon_width; + float top = (float)m_data.sprite_id * scaled_icon_height; + float bottom = top + scaled_icon_height; uvs.left_top = { left, top }; uvs.left_bottom = { left, bottom }; @@ -82,13 +103,13 @@ GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int texture_size, unsigned i return uvs; } +#if !ENABLE_SVG_ICONS ItemsIconsTexture::Metadata::Metadata() : filename("") , icon_size(0) - , icon_border_size(0) - , icon_gap_size(0) { } +#endif // !ENABLE_SVG_ICONS BackgroundTexture::Metadata::Metadata() : filename("") @@ -99,6 +120,10 @@ BackgroundTexture::Metadata::Metadata() { } +#if ENABLE_SVG_ICONS +const float GLToolbar::Default_Icons_Size = 64.0f; +#endif // ENABLE_SVG_ICONS + GLToolbar::Layout::Layout() : type(Horizontal) , orientation(Center) @@ -107,16 +132,32 @@ GLToolbar::Layout::Layout() , border(0.0f) , separator_size(0.0f) , gap_size(0.0f) +#if ENABLE_SVG_ICONS + , icons_size(Default_Icons_Size) + , scale(1.0f) +#else , icons_scale(1.0f) +#endif // ENABLE_SVG_ICONS , width(0.0f) , height(0.0f) , dirty(true) { } +#if ENABLE_SVG_ICONS +GLToolbar::GLToolbar(GLToolbar::EType type, const std::string& name) +#else GLToolbar::GLToolbar(GLToolbar::EType type) +#endif // ENABLE_SVG_ICONS : m_type(type) +#if ENABLE_SVG_ICONS + , m_name(name) +#endif // ENABLE_SVG_ICONS , m_enabled(false) +#if ENABLE_SVG_ICONS + , m_icons_texture_dirty(true) +#endif // ENABLE_SVG_ICONS + , m_tooltip("") { } @@ -128,8 +169,19 @@ GLToolbar::~GLToolbar() } } +#if ENABLE_SVG_ICONS +bool GLToolbar::init(const BackgroundTexture::Metadata& background_texture) +#else bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture) +#endif // ENABLE_SVG_ICONS { +#if ENABLE_SVG_ICONS + if (m_background_texture.texture.get_id() != 0) + return true; + + std::string path = resources_dir() + "/icons/"; + bool res = false; +#else if (m_icons_texture.texture.get_id() != 0) return true; @@ -137,6 +189,7 @@ bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const Bac bool res = !icons_texture.filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture.filename, false); if (res) m_icons_texture.metadata = icons_texture; +#endif // ENABLE_SVG_ICONS if (!background_texture.filename.empty()) res = m_background_texture.texture.load_from_file(path + background_texture.filename, false); @@ -192,11 +245,33 @@ void GLToolbar::set_gap_size(float size) m_layout.dirty = true; } +#if ENABLE_SVG_ICONS +void GLToolbar::set_icons_size(float size) +{ + if (m_layout.icons_size != size) + { + m_layout.icons_size = size; + m_layout.dirty = true; + m_icons_texture_dirty = true; + } +} + +void GLToolbar::set_scale(float scale) +{ + if (m_layout.scale != scale) + { + m_layout.scale = scale; + m_layout.dirty = true; + m_icons_texture_dirty = true; + } +} +#else void GLToolbar::set_icons_scale(float scale) { m_layout.icons_scale = scale; m_layout.dirty = true; } +#endif // ENABLE_SVG_ICONS bool GLToolbar::is_enabled() const { @@ -247,30 +322,6 @@ float GLToolbar::get_height() const return m_layout.height; } -void GLToolbar::enable_item(const std::string& name) -{ - for (GLToolbarItem* item : m_items) - { - if ((item->get_name() == name) && (item->get_state() == GLToolbarItem::Disabled)) - { - item->set_state(GLToolbarItem::Normal); - return; - } - } -} - -void GLToolbar::disable_item(const std::string& name) -{ - for (GLToolbarItem* item : m_items) - { - if (item->get_name() == name) - { - item->set_state(GLToolbarItem::Disabled); - return; - } - } -} - void GLToolbar::select_item(const std::string& name) { if (is_item_disabled(name)) @@ -308,7 +359,6 @@ bool GLToolbar::is_item_disabled(const std::string& name) const return false; } -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS bool GLToolbar::is_item_visible(const std::string& name) const { for (GLToolbarItem* item : m_items) @@ -320,96 +370,12 @@ bool GLToolbar::is_item_visible(const std::string& name) const return false; } -void GLToolbar::set_item_visible(const std::string& name, bool visible) +bool GLToolbar::update_items_state() { - for (GLToolbarItem* item : m_items) - { - if ((item->get_name() == name) && (item->is_visible() != visible)) - { - item->set_visible(visible); - m_layout.dirty = true; - break; - } - } - - // updates separators visibility to avoid having two consecutive - bool any_item_visible = false; - for (GLToolbarItem* item : m_items) - { - if (!item->is_separator()) - any_item_visible |= item->is_visible(); - else - { - item->set_visible(any_item_visible); - any_item_visible = false; - } - } - -} -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS - -std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) -{ - if (!m_enabled) - return ""; - - switch (m_layout.type) - { - default: - case Layout::Horizontal: { return update_hover_state_horizontal(mouse_pos, parent); } - case Layout::Vertical: { return update_hover_state_vertical(mouse_pos, parent); } - } -} - -int GLToolbar::contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const -{ - if (!m_enabled) - return -1; - - switch (m_layout.type) - { - default: - case Layout::Horizontal: { return contains_mouse_horizontal(mouse_pos, parent); } - case Layout::Vertical: { return contains_mouse_vertical(mouse_pos, parent); } - } -} - -void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) -{ - if (item_id < (unsigned int)m_items.size()) - { - GLToolbarItem* item = m_items[item_id]; - if ((item != nullptr) && !item->is_separator() && item->is_hovered()) - { - if (item->is_toggable()) - { - GLToolbarItem::EState state = item->get_state(); - if (state == GLToolbarItem::Hover) - item->set_state(GLToolbarItem::HoverPressed); - else if (state == GLToolbarItem::HoverPressed) - item->set_state(GLToolbarItem::Hover); - - parent.render(); - item->do_action(parent.get_wxglcanvas()); - } - else - { - if (m_type == Radio) - select_item(item->get_name()); - else - item->set_state(GLToolbarItem::HoverPressed); - - parent.render(); - item->do_action(parent.get_wxglcanvas()); - if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) - { - // the item may get disabled during the action, if not, set it back to hover state - item->set_state(GLToolbarItem::Hover); - parent.render(); - } - } - } - } + bool ret = false; + ret |= update_items_visibility(); + ret |= update_items_enabled_state(); + return ret; } void GLToolbar::render(const GLCanvas3D& parent) const @@ -417,6 +383,11 @@ void GLToolbar::render(const GLCanvas3D& parent) const if (!m_enabled || m_items.empty()) return; +#if ENABLE_SVG_ICONS + if (m_icons_texture_dirty) + generate_icons_texture(); +#endif // ENABLE_SVG_ICONS + ::glDisable(GL_DEPTH_TEST); ::glPushMatrix(); @@ -432,6 +403,71 @@ void GLToolbar::render(const GLCanvas3D& parent) const ::glPopMatrix(); } +bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) +{ + Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY()); + bool processed = false; + + // mouse anywhere + if (!evt.Dragging() && !evt.Leaving() && !evt.Entering() && (m_mouse_capture.parent != nullptr)) + { + if (m_mouse_capture.any() && (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())) + // prevents loosing selection into the scene if mouse down was done inside the toolbar and mouse up was down outside it, + // as when switching between views + processed = true; + + m_mouse_capture.reset(); + } + + if (evt.Moving()) + m_tooltip = update_hover_state(mouse_pos, parent); + else if (evt.LeftUp()) + m_mouse_capture.left = false; + else if (evt.MiddleUp()) + m_mouse_capture.middle = false; + else if (evt.RightUp()) + m_mouse_capture.right = false; + else if (evt.Dragging() && m_mouse_capture.any()) + // if the button down was done on this toolbar, prevent from dragging into the scene + processed = true; + + int item_id = contains_mouse(mouse_pos, parent); + if (item_id == -1) + { + // mouse is outside the toolbar + m_tooltip = ""; + } + else + { + // mouse inside toolbar + if (evt.LeftDown() || evt.LeftDClick()) + { + m_mouse_capture.left = true; + m_mouse_capture.parent = &parent; + if ((item_id != -2) && !m_items[item_id]->is_separator()) + { + // mouse is inside an icon + do_action((unsigned int)item_id, parent); + processed = true; + } + } + else if (evt.MiddleDown()) + { + m_mouse_capture.middle = true; + m_mouse_capture.parent = &parent; + } + else if (evt.RightDown()) + { + m_mouse_capture.right = true; + m_mouse_capture.parent = &parent; + } + else if (evt.LeftUp()) + processed = true; + } + + return processed; +} + void GLToolbar::calc_layout() const { switch (m_layout.type) @@ -461,12 +497,20 @@ float GLToolbar::get_width_horizontal() const float GLToolbar::get_width_vertical() const { +#if ENABLE_SVG_ICONS + return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; +#else return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; +#endif // ENABLE_SVG_ICONS } float GLToolbar::get_height_horizontal() const { +#if ENABLE_SVG_ICONS + return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; +#else return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; +#endif // ENABLE_SVG_ICONS } float GLToolbar::get_height_vertical() const @@ -476,13 +520,29 @@ float GLToolbar::get_height_vertical() const float GLToolbar::get_main_size() const { +#if ENABLE_SVG_ICONS + float size = 2.0f * m_layout.border; + for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) + { + if (!m_items[i]->is_visible()) + continue; + + if (m_items[i]->is_separator()) + size += m_layout.separator_size; + else + size += (float)m_layout.icons_size; + } + + if (m_items.size() > 1) + size += ((float)m_items.size() - 1.0f) * m_layout.gap_size; + + size *= m_layout.scale; +#else float size = 2.0f * m_layout.border * m_layout.icons_scale; for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) { -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (!m_items[i]->is_visible()) continue; -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (m_items[i]->is_separator()) size += m_layout.separator_size * m_layout.icons_scale; @@ -492,22 +552,82 @@ float GLToolbar::get_main_size() const if (m_items.size() > 1) size += ((float)m_items.size() - 1.0f) * m_layout.gap_size * m_layout.icons_scale; +#endif // ENABLE_SVG_ICONS return size; } +void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) +{ + if (item_id < (unsigned int)m_items.size()) + { + GLToolbarItem* item = m_items[item_id]; + if ((item != nullptr) && !item->is_separator() && item->is_hovered()) + { + if (item->is_toggable()) + { + GLToolbarItem::EState state = item->get_state(); + if (state == GLToolbarItem::Hover) + item->set_state(GLToolbarItem::HoverPressed); + else if (state == GLToolbarItem::HoverPressed) + item->set_state(GLToolbarItem::Hover); + + parent.render(); + item->do_action(); + } + else + { + if (m_type == Radio) + select_item(item->get_name()); + else + item->set_state(GLToolbarItem::HoverPressed); + + parent.render(); + item->do_action(); + if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) + { + // the item may get disabled during the action, if not, set it back to hover state + item->set_state(GLToolbarItem::Hover); + parent.render(); + } + } + } + } +} + +std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) +{ + if (!m_enabled) + return ""; + + switch (m_layout.type) + { + default: + case Layout::Horizontal: { return update_hover_state_horizontal(mouse_pos, parent); } + case Layout::Vertical: { return update_hover_state_vertical(mouse_pos, parent); } + } +} + std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent) { // NB: mouse_pos is already scaled appropriately float zoom = parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_SVG_ICONS + float factor = m_layout.scale * inv_zoom; +#else float factor = m_layout.icons_scale * inv_zoom; +#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); +#if ENABLE_SVG_ICONS + float scaled_icons_size = m_layout.icons_size * factor; +#else float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; +#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -522,10 +642,8 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC for (GLToolbarItem* item : m_items) { -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (!item->is_visible()) continue; -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (item->is_separator()) left += separator_stride; @@ -601,16 +719,23 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan float zoom = parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_SVG_ICONS + float factor = m_layout.scale * inv_zoom; +#else float factor = m_layout.icons_scale * inv_zoom; +#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); +#if ENABLE_SVG_ICONS + float scaled_icons_size = m_layout.icons_size * factor; +#else float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; +#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; - float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; @@ -621,10 +746,8 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan for (GLToolbarItem* item : m_items) { -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (!item->is_visible()) continue; -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (item->is_separator()) top -= separator_stride; @@ -694,25 +817,43 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan return tooltip; } +int GLToolbar::contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const +{ + if (!m_enabled) + return -1; + + switch (m_layout.type) + { + default: + case Layout::Horizontal: { return contains_mouse_horizontal(mouse_pos, parent); } + case Layout::Vertical: { return contains_mouse_vertical(mouse_pos, parent); } + } +} + int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const { // NB: mouse_pos is already scaled appropriately float zoom = parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_SVG_ICONS + float factor = m_layout.scale * inv_zoom; +#else float factor = m_layout.icons_scale * inv_zoom; +#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); +#if ENABLE_SVG_ICONS + float scaled_icons_size = m_layout.icons_size * factor; +#else float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; +#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; - float separator_stride = scaled_separator_size + scaled_gap_size; - float icon_stride = scaled_icons_size + scaled_gap_size; - float left = m_layout.left + scaled_border; float top = m_layout.top - scaled_border; @@ -722,22 +863,50 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3 { ++id; -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (!item->is_visible()) continue; -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (item->is_separator()) - left += separator_stride; + { + float right = left + scaled_separator_size; + float bottom = top - scaled_icons_size; + + // mouse inside the separator + if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + return id; + + left = right; + right += scaled_gap_size; + + if (id < m_items.size() - 1) + { + // mouse inside the gap + if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + return -2; + } + + left = right; + } else { float right = left + scaled_icons_size; float bottom = top - scaled_icons_size; - + + // mouse inside the icon if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) return id; - left += icon_stride; + left = right; + right += scaled_gap_size; + + if (id < m_items.size() - 1) + { + // mouse inside the gap + if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + return -2; + } + + left = right; } } @@ -750,19 +919,24 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& float zoom = parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_SVG_ICONS + float factor = m_layout.scale * inv_zoom; +#else float factor = m_layout.icons_scale * inv_zoom; +#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); +#if ENABLE_SVG_ICONS + float scaled_icons_size = m_layout.icons_size * factor; +#else float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; +#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; - float separator_stride = scaled_separator_size + scaled_gap_size; - float icon_stride = scaled_icons_size + scaled_gap_size; - float left = m_layout.left + scaled_border; float top = m_layout.top - scaled_border; @@ -772,22 +946,50 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& { ++id; -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (!item->is_visible()) continue; -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (item->is_separator()) - top -= separator_stride; + { + float right = left + scaled_icons_size; + float bottom = top - scaled_separator_size; + + // mouse inside the separator + if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + return id; + + top = bottom; + bottom -= scaled_gap_size; + + if (id < m_items.size() - 1) + { + // mouse inside the gap + if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + return -2; + } + + top = bottom; + } else { float right = left + scaled_icons_size; float bottom = top - scaled_icons_size; + // mouse inside the icon if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) return id; - top -= icon_stride; + top = bottom; + bottom -= scaled_gap_size; + + if (id < m_items.size() - 1) + { + // mouse inside the gap + if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top)) + return -2; + } + + top = bottom; } } @@ -796,17 +998,34 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& void GLToolbar::render_horizontal(const GLCanvas3D& parent) const { +#if ENABLE_SVG_ICONS + unsigned int tex_id = m_icons_texture.get_id(); + int tex_width = m_icons_texture.get_width(); + int tex_height = m_icons_texture.get_height(); +#else unsigned int tex_id = m_icons_texture.texture.get_id(); - int tex_size = m_icons_texture.texture.get_width(); + int tex_width = m_icons_texture.texture.get_width(); + int tex_height = m_icons_texture.texture.get_height(); +#endif // ENABLE_SVG_ICONS - if ((tex_id == 0) || (tex_size <= 0)) +#if !ENABLE_SVG_ICONS + if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; +#endif // !ENABLE_SVG_ICONS float zoom = parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_SVG_ICONS + float factor = inv_zoom * m_layout.scale; +#else float factor = inv_zoom * m_layout.icons_scale; +#endif // ENABLE_SVG_ICONS +#if ENABLE_SVG_ICONS + float scaled_icons_size = m_layout.icons_size * factor; +#else float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; +#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -907,19 +1126,26 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const left += scaled_border; top -= scaled_border; +#if ENABLE_SVG_ICONS + if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) + return; +#endif // ENABLE_SVG_ICONS + // renders icons for (const GLToolbarItem* item : m_items) { -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (!item->is_visible()) continue; -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (item->is_separator()) left += separator_stride; else { - item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.metadata.icon_border_size, m_icons_texture.metadata.icon_size, m_icons_texture.metadata.icon_gap_size); +#if ENABLE_SVG_ICONS + item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); +#else + item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, m_icons_texture.metadata.icon_size); +#endif // ENABLE_SVG_ICONS left += icon_stride; } } @@ -927,17 +1153,34 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const void GLToolbar::render_vertical(const GLCanvas3D& parent) const { +#if ENABLE_SVG_ICONS + unsigned int tex_id = m_icons_texture.get_id(); + int tex_width = m_icons_texture.get_width(); + int tex_height = m_icons_texture.get_height(); +#else unsigned int tex_id = m_icons_texture.texture.get_id(); - int tex_size = m_icons_texture.texture.get_width(); + int tex_width = m_icons_texture.texture.get_width(); + int tex_height = m_icons_texture.texture.get_height(); +#endif // ENABLE_SVG_ICONS - if ((tex_id == 0) || (tex_size <= 0)) +#if !ENABLE_SVG_ICONS + if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; +#endif // !ENABLE_SVG_ICONS float zoom = parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_SVG_ICONS + float factor = inv_zoom * m_layout.scale; +#else float factor = inv_zoom * m_layout.icons_scale; +#endif // ENABLE_SVG_ICONS +#if ENABLE_SVG_ICONS + float scaled_icons_size = m_layout.icons_size * factor; +#else float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * factor; +#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -1038,23 +1281,111 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const left += scaled_border; top -= scaled_border; +#if ENABLE_SVG_ICONS + if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) + return; +#endif // ENABLE_SVG_ICONS + // renders icons for (const GLToolbarItem* item : m_items) { -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (!item->is_visible()) continue; -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS if (item->is_separator()) top -= separator_stride; else { - item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.metadata.icon_border_size, m_icons_texture.metadata.icon_size, m_icons_texture.metadata.icon_gap_size); +#if ENABLE_SVG_ICONS + item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); +#else + item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, m_icons_texture.metadata.icon_size); +#endif // ENABLE_SVG_ICONS top -= icon_stride; } } } +#if ENABLE_SVG_ICONS +bool GLToolbar::generate_icons_texture() const +{ + std::string path = resources_dir() + "/icons/"; + std::vector filenames; + for (GLToolbarItem* item : m_items) + { + const std::string& icon_filename = item->get_icon_filename(); + if (!icon_filename.empty()) + filenames.push_back(path + icon_filename); + } + + std::vector> states; + if (m_name == "Top") + { + states.push_back(std::make_pair(1, false)); + states.push_back(std::make_pair(0, false)); + states.push_back(std::make_pair(2, false)); + states.push_back(std::make_pair(0, false)); + states.push_back(std::make_pair(0, false)); + } + else if (m_name == "View") + { + states.push_back(std::make_pair(1, false)); + states.push_back(std::make_pair(1, true)); + states.push_back(std::make_pair(1, false)); + states.push_back(std::make_pair(0, false)); + states.push_back(std::make_pair(1, true)); + } + + bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_layout.icons_size * m_layout.scale)); + if (res) + m_icons_texture_dirty = false; + + return res; +} +#endif // ENABLE_SVG_ICONS + +bool GLToolbar::update_items_visibility() +{ + bool ret = false; + + for (GLToolbarItem* item : m_items) + { + ret |= item->update_visibility(); + } + + if (ret) + m_layout.dirty = true; + + // updates separators visibility to avoid having two of them consecutive + bool any_item_visible = false; + for (GLToolbarItem* item : m_items) + { + if (!item->is_separator()) + any_item_visible |= item->is_visible(); + else + { + item->set_visible(any_item_visible); + any_item_visible = false; + } + } + + return ret; +} + +bool GLToolbar::update_items_enabled_state() +{ + bool ret = false; + + for (GLToolbarItem* item : m_items) + { + ret |= item->update_enabled_state(); + } + + if (ret) + m_layout.dirty = true; + + return ret; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 951d5e072..5fac1f5b2 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -31,6 +31,10 @@ wxDECLARE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent); class GLToolbarItem { public: + typedef std::function ActionCallback; + typedef std::function VisibilityCallback; + typedef std::function EnabledStateCallback; + enum EType : unsigned char { Action, @@ -51,17 +55,24 @@ public: struct Data { std::string name; +#if ENABLE_SVG_ICONS + std::string icon_filename; +#endif // ENABLE_SVG_ICONS std::string tooltip; unsigned int sprite_id; bool is_toggable; - wxEventType action_event; -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS bool visible; -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS + ActionCallback action_callback; + VisibilityCallback visibility_callback; + EnabledStateCallback enabled_state_callback; Data(); }; + static const ActionCallback Default_Action_Callback; + static const VisibilityCallback Default_Visibility_Callback; + static const EnabledStateCallback Default_Enabled_State_Callback; + private: EType m_type; EState m_state; @@ -74,9 +85,12 @@ public: void set_state(EState state) { m_state = state; } const std::string& get_name() const { return m_data.name; } +#if ENABLE_SVG_ICONS + const std::string& get_icon_filename() const { return m_data.icon_filename; } +#endif // ENABLE_SVG_ICONS const std::string& get_tooltip() const { return m_data.tooltip; } - void do_action(wxEvtHandler *target); + void do_action() { m_data.action_callback(); } bool is_enabled() const { return m_state != Disabled; } bool is_disabled() const { return m_state == Disabled; } @@ -84,18 +98,24 @@ public: bool is_pressed() const { return (m_state == Pressed) || (m_state == HoverPressed); } bool is_toggable() const { return m_data.is_toggable; } -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS bool is_visible() const { return m_data.visible; } - void set_visible(bool visible) { m_data.visible = visible; } -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS bool is_separator() const { return m_type == Separator; } - void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const; + // returns true if the state changes + bool update_visibility(); + // returns true if the state changes + bool update_enabled_state(); + + void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const; private: - GLTexture::Quad_UVs get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const; + GLTexture::Quad_UVs get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const; + void set_visible(bool visible) { m_data.visible = visible; } + + friend class GLToolbar; }; +#if !ENABLE_SVG_ICONS // items icon textures are assumed to be square and all with the same size in pixels, no internal check is done // icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState // from left to right @@ -107,10 +127,6 @@ struct ItemsIconsTexture std::string filename; // size of the square icons, in pixels unsigned int icon_size; - // size of the border, in pixels - unsigned int icon_border_size; - // distance between two adjacent icons (to avoid filtering artifacts), in pixels - unsigned int icon_gap_size; Metadata(); }; @@ -118,6 +134,7 @@ struct ItemsIconsTexture GLTexture texture; Metadata metadata; }; +#endif // !ENABLE_SVG_ICONS struct BackgroundTexture { @@ -144,6 +161,10 @@ struct BackgroundTexture class GLToolbar { public: +#if ENABLE_SVG_ICONS + static const float Default_Icons_Size; +#endif // ENABLE_SVG_ICONS + enum EType : unsigned char { Normal, @@ -177,7 +198,12 @@ public: float border; float separator_size; float gap_size; +#if ENABLE_SVG_ICONS + float icons_size; + float scale; +#else float icons_scale; +#endif // ENABLE_SVG_ICONS float width; float height; @@ -190,18 +216,50 @@ private: typedef std::vector ItemsList; EType m_type; +#if ENABLE_SVG_ICONS + std::string m_name; +#endif // ENABLE_SVG_ICONS bool m_enabled; +#if ENABLE_SVG_ICONS + mutable GLTexture m_icons_texture; + mutable bool m_icons_texture_dirty; +#else ItemsIconsTexture m_icons_texture; +#endif // ENABLE_SVG_ICONS BackgroundTexture m_background_texture; mutable Layout m_layout; ItemsList m_items; + struct MouseCapture + { + bool left; + bool middle; + bool right; + GLCanvas3D* parent; + + MouseCapture() { reset(); } + + bool any() const { return left || middle || right; } + void reset() { left = middle = right = false; parent = nullptr; } + }; + + MouseCapture m_mouse_capture; + std::string m_tooltip; + public: +#if ENABLE_SVG_ICONS + GLToolbar(EType type, const std::string& name); +#else explicit GLToolbar(EType type); +#endif // ENABLE_SVG_ICONS ~GLToolbar(); +#if ENABLE_SVG_ICONS + bool init(const BackgroundTexture::Metadata& background_texture); +#else bool init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture); +#endif // ENABLE_SVG_ICONS Layout::EType get_layout_type() const; void set_layout_type(Layout::EType type); @@ -212,7 +270,12 @@ public: void set_border(float border); void set_separator_size(float size); void set_gap_size(float size); +#if ENABLE_SVG_ICONS + void set_icons_size(float size); + void set_scale(float scale); +#else void set_icons_scale(float scale); +#endif // ENABLE_SVG_ICONS bool is_enabled() const; void set_enabled(bool enable); @@ -223,26 +286,22 @@ public: float get_width() const; float get_height() const; - void enable_item(const std::string& name); - void disable_item(const std::string& name); void select_item(const std::string& name); bool is_item_pressed(const std::string& name) const; bool is_item_disabled(const std::string& name) const; -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS bool is_item_visible(const std::string& name) const; - void set_item_visible(const std::string& name, bool visible); -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS - std::string update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); + const std::string& get_tooltip() const { return m_tooltip; } - // returns the id of the item under the given mouse position or -1 if none - int contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; - void do_action(unsigned int item_id, GLCanvas3D& parent); + // returns true if any item changed its state + bool update_items_state(); void render(const GLCanvas3D& parent) const; + bool on_mouse(wxMouseEvent& evt, GLCanvas3D& parent); + private: void calc_layout() const; float get_width_horizontal() const; @@ -250,13 +309,26 @@ private: float get_height_horizontal() const; float get_height_vertical() const; float get_main_size() const; + void do_action(unsigned int item_id, GLCanvas3D& parent); + std::string update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); std::string update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent); std::string update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent); + // returns the id of the item under the given mouse position or -1 if none + int contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; int contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; int contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; void render_horizontal(const GLCanvas3D& parent) const; void render_vertical(const GLCanvas3D& parent) const; + +#if ENABLE_SVG_ICONS + bool generate_icons_texture() const; +#endif // ENABLE_SVG_ICONS + + // returns true if any item changed its state + bool update_items_visibility(); + // returns true if any item changed its state + bool update_items_enabled_state(); }; } // namespace GUI diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index a001ad747..e3d8cc76d 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -30,6 +30,8 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Print.hpp" +#include "Tab.hpp" +#include "GUI_ObjectList.hpp" namespace Slic3r { namespace GUI { @@ -123,6 +125,9 @@ void config_wizard(int reason) if (! wxGetApp().check_unsaved_changes()) return; + // save selected preset before config wizard running + const auto printer_preset_name = wxGetApp().preset_bundle->printers.get_edited_preset().name; + try { ConfigWizard wizard(nullptr, static_cast(reason)); wizard.run(wxGetApp().preset_bundle, wxGetApp().preset_updater); @@ -131,8 +136,18 @@ void config_wizard(int reason) show_error(nullptr, e.what()); } - // Load the currently selected preset into the GUI, update the preset selection box. - wxGetApp().load_current_presets(); + // select old(before config wizard running) preset + wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printer_preset_name); + // If old preset if invisible now, then first visible preset will be selected + // So, let control the case if multi-part object is on the scene and first visible preset is SLA + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && + wxGetApp().obj_list()->has_multi_part_objects()) + { + show_info(nullptr, + _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + + _(L("Please check and fix your object list.")), + _(L("Attention!"))); + } } // opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index ab54ee105..a6a356b2c 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" @@ -79,9 +80,7 @@ IMPLEMENT_APP(GUI_App) GUI_App::GUI_App() : wxApp() , m_em_unit(10) -#if ENABLE_IMGUI , m_imgui(new ImGuiWrapper()) -#endif // ENABLE_IMGUI {} bool GUI_App::OnInit() @@ -90,14 +89,17 @@ bool GUI_App::OnInit() const wxString resources_dir = from_u8(Slic3r::resources_dir()); wxCHECK_MSG(wxDirExists(resources_dir), false, wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); - -#if ENABLE_IMGUI wxCHECK_MSG(m_imgui->init(), false, "Failed to initialize ImGui"); -#endif // ENABLE_IMGUI - SetAppName("Slic3r++_alpha"); + SetAppName("Slic3r++-beta"); SetAppDisplayName("Slic3r++"); +// Enable this to get the default Win32 COMCTRL32 behavior of static boxes. +// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0); +// Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible +// performance when working on high resolution multi-display setups. +// wxSystemOptions::SetOption("msw.notebook.themed-background", 0); + // Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION; // Set the Slic3r data directory at the Slic3r XS module. @@ -162,22 +164,13 @@ bool GUI_App::OnInit() Bind(wxEVT_IDLE, [this](wxIdleEvent& event) { + if (! plater_) + return; + if (app_config->dirty() && app_config->get("autosave") == "1") app_config->save(); - // ! Temporary workaround for the correct behavior of the Scrolled sidebar panel - // Do this "manipulations" only once ( after (re)create of the application ) - if (plater_ && sidebar().obj_list()->GetMinHeight() > 15 * wxGetApp().em_unit()) - { - wxWindowUpdateLocker noUpdates_sidebar(&sidebar()); - sidebar().obj_list()->SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit())); - - // !!! to correct later layouts - update_mode(); // update view mode after fix of the object_list size - } - - if (this->plater() != nullptr) - this->obj_manipul()->update_if_dirty(); + this->obj_manipul()->update_if_dirty(); // Preset updating & Configwizard are done after the above initializations, // and after MainFrame is created & shown. @@ -204,12 +197,13 @@ bool GUI_App::OnInit() } preset_updater->sync(preset_bundle); }); - - load_current_presets(); } }); + load_current_presets(); + mainframe->Show(true); + update_mode(); // update view mode after fix of the object_list size m_initialized = true; return true; } @@ -260,6 +254,7 @@ void GUI_App::init_fonts() { m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); + m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); #ifdef __WXMAC__ m_small_font.SetPointSize(11); @@ -507,7 +502,7 @@ Tab* GUI_App::get_tab(Preset::Type type) { for (Tab* tab: tabs_list) if (tab->type() == type) - return tab; + return tab->complited() ? tab : nullptr; // To avoid actions with no-completed Tab return nullptr; } @@ -533,23 +528,10 @@ void GUI_App::save_mode(const /*ConfigOptionMode*/int mode) // Update view mode according to selected menu void GUI_App::update_mode() { - wxWindowUpdateLocker noUpdates(&sidebar()); - - const ConfigOptionMode mode = wxGetApp().get_mode(); - - obj_list()->get_sizer()->Show(mode > comSimple); - sidebar().set_mode_value(mode); -// sidebar().show_buttons(mode == comExpert); - obj_list()->unselect_objects(); - obj_list()->update_selections(); - obj_list()->update_object_menu(); - - sidebar().update_mode_sizer(mode); - - sidebar().Layout(); + sidebar().update_mode(); for (auto tab : tabs_list) - tab->update_visibility(); + tab->update_mode(); plater()->update_object_menu(); } @@ -766,6 +748,13 @@ wxNotebook* GUI_App::tab_panel() const return mainframe->m_tabpanel; } +int GUI_App::extruders_cnt() const +{ + const Preset& preset = preset_bundle->printers.get_selected_preset(); + return preset.printer_technology() == ptSLA ? 1 : + preset.config.option("nozzle_diameter")->values.size(); +} + void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name) { if (name.empty()) { return; } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 4c23e3eb7..fb47c5bf6 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -5,9 +5,7 @@ #include #include "libslic3r/PrintConfig.hpp" #include "MainFrame.hpp" -#if ENABLE_IMGUI #include "ImGuiWrapper.hpp" -#endif // ENABLE_IMGUI #include #include @@ -80,16 +78,14 @@ class GUI_App : public wxApp wxFont m_small_font; wxFont m_bold_font; + wxFont m_normal_font; size_t m_em_unit; // width of a "m"-symbol in pixels for current system font // Note: for 100% Scale m_em_unit = 10 -> it's a good enough coefficient for a size setting of controls wxLocale* m_wxLocale{ nullptr }; -#if ENABLE_IMGUI std::unique_ptr m_imgui; -#endif // ENABLE_IMGUI - std::unique_ptr m_printhost_job_queue; public: @@ -111,6 +107,7 @@ public: const wxFont& small_font() { return m_small_font; } const wxFont& bold_font() { return m_bold_font; } + const wxFont& normal_font() { return m_normal_font; } size_t em_unit() const { return m_em_unit; } void set_em_unit(const size_t em_unit) { m_em_unit = em_unit; } @@ -163,12 +160,11 @@ public: Plater* plater_{ nullptr }; wxNotebook* tab_panel() const ; + int extruders_cnt() const; std::vector tabs_list; -#if ENABLE_IMGUI ImGuiWrapper* imgui() { return m_imgui.get(); } -#endif // ENABLE_IMGUI PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 8a320fca2..9f8955a4e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -10,6 +10,7 @@ #include "libslic3r/Model.hpp" #include "LambdaObjectDialog.hpp" #include "GLCanvas3D.hpp" +#include "Selection.hpp" #include #include "slic3r/Utils/FixModelByWin10.hpp" @@ -38,6 +39,22 @@ FreqSettingsBundle FREQ_SETTINGS_BUNDLE_SLA = { L("Pad and Support") , { "supports_enable", "pad_enable" } } }; +static PrinterTechnology printer_technology() +{ + return wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology(); +} + +// Config from current edited printer preset +static DynamicPrintConfig& printer_config() +{ + return wxGetApp().preset_bundle->printers.get_edited_preset().config; +} + +static int extruders_count() +{ + return wxGetApp().extruders_cnt(); +} + ObjectList::ObjectList(wxWindow* parent) : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), m_parent(parent) @@ -78,7 +95,7 @@ ObjectList::ObjectList(wxWindow* parent) : #endif //__WXMSW__ }); -// Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX + Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX #ifdef __WXMSW__ GetMainWindow()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { @@ -110,10 +127,7 @@ ObjectList::~ObjectList() void ObjectList::create_objects_ctrl() { - // temporary workaround for the correct behavior of the Scrolled sidebar panel: - // 1. set a height of the list to some big value - // 2. change it to the normal min value (200) after first whole App updating/layouting - SetMinSize(wxSize(-1, 3000)); // #ys_FIXME + SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit())); m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer->Add(this, 1, wxGROW); @@ -259,7 +273,7 @@ void ObjectList::update_extruder_values_for_items(const int max_extruder) void ObjectList::update_objects_list_extruder_column(int extruders_count) { if (!this) return; // #ys_FIXME - if (wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) + if (printer_technology() == ptSLA) extruders_count = 1; wxDataViewChoiceRenderer* ch_render = dynamic_cast(GetColumn(1)->GetRenderer()); @@ -376,7 +390,8 @@ void ObjectList::selection_changed() fix_multiselection_conflicts(); // update object selection on Plater - update_selections_on_canvas(); + if (!m_prevent_canvas_selection_update) + update_selections_on_canvas(); // to update the toolbar and info sizer if (!GetSelection() || m_objects_model->GetItemType(GetSelection()) == itObject) { @@ -424,11 +439,10 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) else if (title == _("Name") && pt.x >15 && m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.GetRefData()) { - if (is_windows10()) { - const auto obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); - wxGetApp().plater()->fix_through_netfabb(obj_idx); - } + if (is_windows10()) + fix_through_netfabb(); } + #ifndef __WXMSW__ GetMainWindow()->SetToolTip(""); // hide tooltip #endif //__WXMSW__ @@ -436,9 +450,13 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) void ObjectList::show_context_menu() { - if (multiple_selection() && selected_instances_of_same_object()) + if (multiple_selection()) { - wxGetApp().plater()->PopupMenu(&m_menu_instance); + if (selected_instances_of_same_object()) + wxGetApp().plater()->PopupMenu(&m_menu_instance); + else + show_multi_selection_menu(); + return; } @@ -451,7 +469,7 @@ void ObjectList::show_context_menu() wxMenu* menu = type & itInstance ? &m_menu_instance : m_objects_model->GetParent(item) != wxDataViewItem(0) ? &m_menu_part : - wxGetApp().plater()->printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; + printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; if (!(type & itInstance)) append_menu_item_settings(menu); @@ -596,7 +614,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event) std::vector ObjectList::get_options(const bool is_part) { - if (wxGetApp().plater()->printer_technology() == ptSLA) { + if (printer_technology() == ptSLA) { SLAPrintObjectConfig full_sla_config; auto options = full_sla_config.keys(); options.erase(find(options.begin(), options.end(), "layer_height")); @@ -615,7 +633,7 @@ std::vector ObjectList::get_options(const bool is_part) const std::vector& ObjectList::get_options_for_bundle(const wxString& bundle_name) { - const FreqSettingsBundle& bundle = wxGetApp().plater()->printer_technology() == ptSLA ? + const FreqSettingsBundle& bundle = printer_technology() == ptSLA ? FREQ_SETTINGS_BUNDLE_SLA : FREQ_SETTINGS_BUNDLE_FFF; for (auto& it : bundle) @@ -625,7 +643,7 @@ const std::vector& ObjectList::get_options_for_bundle(const wxStrin } #if 0 // if "Quick menu" is selected - FreqSettingsBundle& bundle_quick = wxGetApp().plater()->printer_technology() == ptSLA ? + FreqSettingsBundle& bundle_quick = printer_technology() == ptSLA ? m_freq_settings_sla: m_freq_settings_fff; for (auto& it : bundle_quick) @@ -643,8 +661,7 @@ void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const { auto options = get_options(is_part); - auto extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : - wxGetApp().preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + const int extruders_cnt = extruders_count(); DynamicPrintConfig config; for (auto& option : options) @@ -654,9 +671,7 @@ void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const if (category.empty() || (category == "Extruders" && extruders_cnt == 1)) continue; - const std::string& label = opt->label.empty() ? opt->full_label : - opt->full_label.empty() ? opt->label : - opt->full_label; + const std::string& label = !opt->full_label.empty() ? opt->full_label : opt->label; std::pair option_label(option, label); std::vector< std::pair > new_category; auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category); @@ -704,7 +719,7 @@ void ObjectList::get_settings_choice(const wxString& category_name) if (selection_cnt > 0) { // Add selected items to the "Quick menu" - FreqSettingsBundle& freq_settings = wxGetApp().plater()->printer_technology() == ptSLA ? + FreqSettingsBundle& freq_settings = printer_technology() == ptSLA ? m_freq_settings_sla : m_freq_settings_fff; bool changed_existing = false; @@ -750,7 +765,9 @@ void ObjectList::get_settings_choice(const wxString& category_name) for (auto sel : selections) selected_options.push_back((*settings_list)[sel].first); - const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + const DynamicPrintConfig& from_config = printer_technology() == ptFFF ? + wxGetApp().preset_bundle->prints.get_edited_preset().config : + wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; for (auto& setting : (*settings_list)) { @@ -852,7 +869,7 @@ void ObjectList::append_menu_items_add_volume(wxMenu* menu) const ConfigOptionMode mode = wxGetApp().get_mode(); - if (mode < comExpert) + if (mode == comAdvanced) { append_menu_item(menu, wxID_ANY, _(L("Add part")), "", [this](wxCommandEvent&) { load_subobject(ModelVolumeType::MODEL_PART); }, *m_bmp_vector[int(ModelVolumeType::MODEL_PART)]); @@ -932,7 +949,9 @@ wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_) // Create new items for settings popupmenu - menu->m_separator_frst = menu->AppendSeparator(); + if (printer_technology() == ptFFF || + menu->GetMenuItems().size() > 0 && !menu->GetMenuItems().back()->IsSeparator()) + menu->m_separator_frst = menu->AppendSeparator(); // Add frequently settings create_freq_settings_popupmenu(menu); @@ -968,10 +987,14 @@ wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu) [this](wxCommandEvent&) { split_instances(); }, "", menu); } -void ObjectList::append_menu_item_rename(wxMenu* menu) +void ObjectList::append_menu_items_osx(wxMenu* menu) { + append_menu_item(menu, wxID_ANY, _(L("Delete item")), "", + [this](wxCommandEvent&) { remove(); }, "", menu); + append_menu_item(menu, wxID_ANY, _(L("Rename")), "", [this](wxCommandEvent&) { rename_item(); }, "", menu); + menu->AppendSeparator(); } @@ -984,12 +1007,20 @@ void ObjectList::append_menu_item_fix_through_netfabb(wxMenu* menu) menu->AppendSeparator(); } +void ObjectList::append_menu_item_export_stl(wxMenu* menu) const +{ + append_menu_item(menu, wxID_ANY, _(L("Export object as STL")) + dots, "", + [](wxCommandEvent&) { wxGetApp().plater()->export_stl(true); }, "", menu); + menu->AppendSeparator(); +} + void ObjectList::create_object_popupmenu(wxMenu *menu) { #ifdef __WXOSX__ - append_menu_item_rename(menu); + append_menu_items_osx(menu); #endif // __WXOSX__ + append_menu_item_export_stl(menu); append_menu_item_fix_through_netfabb(menu); // Split object to parts @@ -1007,9 +1038,10 @@ void ObjectList::create_object_popupmenu(wxMenu *menu) void ObjectList::create_sla_object_popupmenu(wxMenu *menu) { #ifdef __WXOSX__ - append_menu_item_rename(menu); + append_menu_items_osx(menu); #endif // __WXOSX__ - + + append_menu_item_export_stl(menu); append_menu_item_fix_through_netfabb(menu); // rest of a object_sla_menu will be added later in: // - append_menu_item_settings() -> for "Add (settings)" @@ -1018,7 +1050,7 @@ void ObjectList::create_sla_object_popupmenu(wxMenu *menu) void ObjectList::create_part_popupmenu(wxMenu *menu) { #ifdef __WXOSX__ - append_menu_item_rename(menu); + append_menu_items_osx(menu); #endif // __WXOSX__ append_menu_item_fix_through_netfabb(menu); @@ -1064,11 +1096,10 @@ wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) { // Add default settings bundles - const FreqSettingsBundle& bundle = wxGetApp().plater()->printer_technology() == ptFFF ? + const FreqSettingsBundle& bundle = printer_technology() == ptFFF ? FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA; - auto extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : - wxGetApp().preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + const int extruders_cnt = extruders_count(); for (auto& it : bundle) { if (it.first.empty() || it.first == "Extruders" && extruders_cnt == 1) @@ -1080,7 +1111,7 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) } #if 0 // Add "Quick" settings bundles - const FreqSettingsBundle& bundle_quick = wxGetApp().plater()->printer_technology() == ptFFF ? + const FreqSettingsBundle& bundle_quick = printer_technology() == ptFFF ? m_freq_settings_fff : m_freq_settings_sla; for (auto& it : bundle_quick) { @@ -1246,10 +1277,13 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode if (obj_idx < 0) return; - const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); assert(obj_idx == selection.get_object_idx()); - // Selected instance index in ModelObject. Only valid if there is only one instance selected in the selection. - int instance_idx = selection.get_instance_idx(); + + /** Any changes of the Object's composition is duplicated for all Object's Instances + * So, It's enough to take a bounding box of a first selected Instance and calculate Part(generic_subobject) position + */ + int instance_idx = *selection.get_instance_idxs().begin(); assert(instance_idx != -1); if (instance_idx == -1) return; @@ -1262,7 +1296,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode const wxString name = _(L("Generic")) + "-" + _(type_name); TriangleMesh mesh; - auto& bed_shape = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("bed_shape")->values; + auto& bed_shape = printer_config().option("bed_shape")->values; const auto& sz = BoundingBoxf(bed_shape).size(); const auto side = 0.1 * std::max(sz(0), sz(1)); @@ -1390,12 +1424,14 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con // Cannot delete a wipe tower. return false; + ModelObject* object = (*m_objects)[obj_idx]; + if (type == itVolume) { - const auto volume = (*m_objects)[obj_idx]->volumes[idx]; + const auto volume = object->volumes[idx]; // if user is deleting the last solid part, throw error int solid_cnt = 0; - for (auto vol : (*m_objects)[obj_idx]->volumes) + for (auto vol : object->volumes) if (vol->is_model_part()) ++solid_cnt; if (volume->is_model_part() && solid_cnt == 1) { @@ -1403,14 +1439,23 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con return false; } - (*m_objects)[obj_idx]->delete_volume(idx); + object->delete_volume(idx); + + if (object->volumes.size() == 1) + { + const auto last_volume = object->volumes[0]; + if (!last_volume->config.empty()) { + object->config.apply(last_volume->config); + last_volume->config.clear(); + } + } } else if (type == itInstance) { - if ((*m_objects)[obj_idx]->instances.size() == 1) { + if (object->instances.size() == 1) { Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last intance from object."))); return false; } - (*m_objects)[obj_idx]->delete_instance(idx); + object->delete_instance(idx); } else return false; @@ -1430,7 +1475,7 @@ void ObjectList::split() ModelVolume* volume; if (!get_volume_by_item(item, volume)) return; - DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + DynamicPrintConfig& config = printer_config(); const ConfigOption *nozzle_dmtrs_opt = config.option("nozzle_diameter", false); const auto nozzle_dmrs_cnt = (nozzle_dmtrs_opt == nullptr) ? size_t(1) : dynamic_cast(nozzle_dmtrs_opt)->values.size(); if (volume->split(nozzle_dmrs_cnt) == 1) { @@ -1500,12 +1545,7 @@ bool ObjectList::is_splittable() if (!get_volume_by_item(item, volume) || !volume) return false; - int splittable = volume->is_splittable(); - if (splittable == -1) { - splittable = (int)volume->mesh.has_multiple_patches(); - volume->set_splittable(splittable); - } - return splittable != 0; + return volume->is_splittable(); } bool ObjectList::selected_instances_of_same_object() @@ -1525,7 +1565,7 @@ bool ObjectList::selected_instances_of_same_object() bool ObjectList::can_split_instances() { - const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); return selection.is_multiple_full_instance() || selection.is_single_full_instance(); } @@ -1740,6 +1780,12 @@ void ObjectList::delete_from_model_and_list(const std::vector& it if (item->type&itVolume) { m_objects_model->Delete(m_objects_model->GetItemByVolumeId(item->obj_idx, item->sub_obj_idx)); + if ((*m_objects)[item->obj_idx]->volumes.size() == 1 && + (*m_objects)[item->obj_idx]->config.has("extruder")) + { + const wxString extruder = wxString::Format("%d", (*m_objects)[item->obj_idx]->config.option("extruder")->value); + m_objects_model->SetValue(extruder, m_objects_model->GetItemById(item->obj_idx), 1); + } wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx); } else @@ -1812,8 +1858,11 @@ void ObjectList::remove() { if (m_objects_model->GetParent(item) == wxDataViewItem(0)) delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); - else + else { + if (sels.size() == 1) + select_item(m_objects_model->GetParent(item)); del_subobject_item(item); + } } } @@ -1829,7 +1878,7 @@ bool ObjectList::multiple_selection() const void ObjectList::update_selections() { - const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); wxDataViewItemArray sels; // We doesn't update selection if SettingsItem for the current object/part is selected @@ -1906,16 +1955,28 @@ void ObjectList::update_selections() select_items(sels); + /* Because of ScrollLines() and GetItemRect() functions are implemented + * only for GENERIC DataViewCtrl in current version of wxWidgets, + * use this part of code only for MSW + */ +#if defined(wxUSE_GENERICDATAVIEWCTRL) + // Scroll selected Item in the middle of an object list if (GetSelection()) { - const int sel_item_row = m_objects_model->GetRowByItem(GetSelection()); - ScrollLines(sel_item_row - m_selected_row); - m_selected_row = sel_item_row; + const wxRect& sel_rc = GetItemRect(GetSelection()); + const wxRect& main_rc = GetClientRect(); + if (sel_rc.GetBottom() <= main_rc.GetTop()+sel_rc.height || + sel_rc.GetTop() >= main_rc.GetBottom() ) + { + const wxRect& top_rc = GetItemRect(GetTopItem()); + ScrollLines(int((sel_rc.y - top_rc.y) / top_rc.GetHeight()) - 0.5*GetCountPerPage()); + } } +#endif } void ObjectList::update_selections_on_canvas() { - GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); const int sel_cnt = GetSelectedItemsCount(); if (sel_cnt == 0) { @@ -1924,8 +1985,8 @@ void ObjectList::update_selections_on_canvas() return; } - auto add_to_selection = [this](const wxDataViewItem& item, GLCanvas3D::Selection& selection, bool as_single_selection) - { + auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, bool as_single_selection) + { if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { selection.add_object(m_objects_model->GetIdByItem(item), as_single_selection); return; @@ -1966,6 +2027,8 @@ void ObjectList::update_selections_on_canvas() void ObjectList::select_item(const wxDataViewItem& item) { + if (! item.IsOk()) { return; } + m_prevent_list_events = true; UnselectAll(); @@ -2129,14 +2192,28 @@ bool ObjectList::has_multi_part_objects() void ObjectList::update_settings_items() { + m_prevent_canvas_selection_update = true; + wxDataViewItemArray sel; + GetSelections(sel); // stash selection + wxDataViewItemArray items; m_objects_model->GetChildren(wxDataViewItem(0), items); for (auto& item : items) { const wxDataViewItem& settings_item = m_objects_model->GetSettingsItem(item); select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); + + // If settings item was deleted from the list, + // it's need to be deleted from selection array, if it was there + if (settings_item != m_objects_model->GetSettingsItem(item) && + sel.Index(settings_item) != wxNOT_FOUND) { + sel.Remove(settings_item); + } } - UnselectAll(); + + // restore selection: + SetSelections(sel); + m_prevent_canvas_selection_update = false; } void ObjectList::update_object_menu() @@ -2168,7 +2245,7 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set void ObjectList::split_instances() { - const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); const int obj_idx = selection.get_object_idx(); if (obj_idx == -1) return; @@ -2232,13 +2309,37 @@ void ObjectList::fix_through_netfabb() const if (!item) return; - ItemType type = m_objects_model->GetItemType(item); + const ItemType type = m_objects_model->GetItemType(item); + + const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : + type & itVolume ? m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)) : -1; + + const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; + + wxGetApp().plater()->fix_through_netfabb(obj_idx, vol_idx); - if (type & itObject) - wxGetApp().plater()->fix_through_netfabb(m_objects_model->GetIdByItem(item)); - else if (type & itVolume) - wxGetApp().plater()->fix_through_netfabb(m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)), - m_objects_model->GetVolumeIdByItem(item)); + update_item_error_icon(obj_idx, vol_idx); +} + +void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) const +{ + const wxDataViewItem item = vol_idx <0 ? m_objects_model->GetItemById(obj_idx) : + m_objects_model->GetItemByVolumeId(obj_idx, vol_idx); + if (!item) + return; + + auto model_object = (*m_objects)[obj_idx]; + + const stl_stats& stats = model_object->volumes[vol_idx<0 ? 0 : vol_idx]->mesh.stl.stats; + const int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + + stats.facets_added + stats.facets_reversed + stats.backwards_edges; + + if (errors == 0) { + // delete Error_icon if all errors are fixed + wxVariant variant; + variant << PrusaDataViewBitmapText(from_u8(model_object->name), wxNullBitmap); + m_objects_model->SetValue(variant, item, 0); + } } void ObjectList::ItemValueChanged(wxDataViewEvent &event) @@ -2261,5 +2362,94 @@ void ObjectList::OnEditingDone(wxDataViewEvent &event) _(L("the following characters are not allowed:")) + " <>:/\\|?*\""); } +void ObjectList::show_multi_selection_menu() +{ + wxDataViewItemArray sels; + GetSelections(sels); + + for (const wxDataViewItem& item : sels) + if (!(m_objects_model->GetItemType(item) & (itVolume | itObject))) + // show this menu only for Object(s)/Volume(s) selection + return; + + wxMenu* menu = new wxMenu(); + +#ifdef __WXOSX__ + append_menu_item(menu, wxID_ANY, _(L("Delete items")), "", + [this](wxCommandEvent&) { remove(); }, "", menu); +#endif //__WXOSX__ + + if (extruders_count() > 1) + append_menu_item(menu, wxID_ANY, _(L("Set extruder for selected items")), + _(L("Select extruder number for selected objects and/or parts")), + [this](wxCommandEvent&) { extruder_selection(); }, "", menu); + + PopupMenu(menu); +} + +void ObjectList::extruder_selection() +{ + wxArrayString choices; + choices.Add("default"); + for (int i = 1; i <= extruders_count(); ++i) + choices.Add(wxString::Format("%d", i)); + + const wxString& selected_extruder = wxGetSingleChoice(_(L("Select extruder number:")), + _(L("This extruder will be set for selected items")), + choices, 0, this); + if (selected_extruder.IsEmpty()) + return; + + const int extruder_num = selected_extruder == "default" ? 0 : atoi(selected_extruder.c_str()); + +// /* Another variant for an extruder selection */ +// extruder_num = wxGetNumberFromUser(_(L("Attention!!! \n" +// "It's a possibile to set an extruder number \n" +// "for whole Object(s) and/or object Part(s), \n" +// "not for an Instance. ")), +// _(L("Enter extruder number:")), +// _(L("This extruder will be set for selected items")), +// 1, 1, 5, this); + + set_extruder_for_selected_items(extruder_num); +} + +void ObjectList::set_extruder_for_selected_items(const int extruder) const +{ + wxDataViewItemArray sels; + GetSelections(sels); + + for (const wxDataViewItem& item : sels) + { + const ItemType type = m_objects_model->GetItemType(item); + + const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : + m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + + const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; + + DynamicPrintConfig& config = type & itObject ? (*m_objects)[obj_idx]->config : + (*m_objects)[obj_idx]->volumes[vol_idx]->config; + + if (config.has("extruder")) { + if (extruder == 0) + config.erase("extruder"); + else + config.option("extruder")->value = extruder; + } + else if (extruder > 0) + config.set_key_value("extruder", new ConfigOptionInt(extruder)); + + const wxString extruder_str = extruder == 0 ? wxString ("default") : + wxString::Format("%d", config.option("extruder")->value); + m_objects_model->SetValue(extruder_str, item, 1); + + wxGetApp().plater()->canvas3D()->ensure_on_bed(obj_idx); + } + + // update scene + wxGetApp().plater()->update(); +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 138bacac4..e0df8dd40 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -127,6 +127,10 @@ class ObjectList : public wxDataViewCtrl bool m_prevent_update_extruder_in_config = false; // We use this flag to avoid updating of the extruder value in config // during updating of the extruder count. + bool m_prevent_canvas_selection_update = false; // This flag prevents changing selection on the canvas. See function + // update_settings_items - updating canvas selection is undesirable, + // because it would turn off the gizmos (mainly a problem for the SLA gizmo) + bool m_parts_changed = false; bool m_part_settings_changed = false; @@ -180,8 +184,9 @@ public: wxMenuItem* append_menu_item_settings(wxMenu* menu); wxMenuItem* append_menu_item_change_type(wxMenu* menu); wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu); - void append_menu_item_rename(wxMenu* menu); + void append_menu_items_osx(wxMenu* menu); void append_menu_item_fix_through_netfabb(wxMenu* menu); + void append_menu_item_export_stl(wxMenu* menu) const ; void create_object_popupmenu(wxMenu *menu); void create_sla_object_popupmenu(wxMenu*menu); void create_part_popupmenu(wxMenu*menu); @@ -265,6 +270,7 @@ public: void split_instances(); void rename_item(); void fix_through_netfabb() const; + void update_item_error_icon(const int obj_idx, int vol_idx) const ; private: void OnChar(wxKeyEvent& event); void OnContextMenu(wxDataViewEvent &event); @@ -277,6 +283,10 @@ private: void ItemValueChanged(wxDataViewEvent &event); void OnEditingDone(wxDataViewEvent &event); + void show_multi_selection_menu(); + void extruder_selection(); + void set_extruder_for_selected_items(const int extruder) const ; + std::vector get_options(const bool is_part); const std::vector& get_options_for_bundle(const wxString& bundle_name); void get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index ab83df05d..6c8fdcab7 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -7,6 +7,7 @@ #include "PresetBundle.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Geometry.hpp" +#include "Selection.hpp" #include @@ -155,7 +156,7 @@ void ObjectManipulation::UpdateAndShow(const bool show) OG_Settings::UpdateAndShow(show); } -void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& selection) +void ObjectManipulation::update_settings_value(const Selection& selection) { m_new_move_label_string = L("Position"); m_new_rotate_label_string = L("Rotation"); @@ -208,8 +209,8 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation(); m_new_scale = volume->get_volume_scaling_factor(); - m_new_size = volume->get_instance_transformation().get_matrix(true, true) * volume->get_volume_transformation().get_matrix(true, true) * volume->bounding_box.size(); - m_new_enabled = true; + m_new_size = volume->get_volume_transformation().get_matrix(true, true) * volume->bounding_box.size(); + m_new_enabled = true; } else if (wxGetApp().obj_list()->multiple_selection()) { @@ -348,7 +349,7 @@ void ObjectManipulation::reset_settings_value() void ObjectManipulation::change_position_value(const Vec3d& position) { auto canvas = wxGetApp().plater()->canvas3D(); - GLCanvas3D::Selection& selection = canvas->get_selection(); + Selection& selection = canvas->get_selection(); selection.start_dragging(); selection.translate(position - m_cache.position, selection.requires_local_axes()); canvas->do_move(); @@ -359,13 +360,13 @@ void ObjectManipulation::change_position_value(const Vec3d& position) void ObjectManipulation::change_rotation_value(const Vec3d& rotation) { GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); - const GLCanvas3D::Selection& selection = canvas->get_selection(); + const Selection& selection = canvas->get_selection(); - GLCanvas3D::TransformationType transformation_type(GLCanvas3D::TransformationType::World_Relative_Joint); - if (selection.is_single_full_instance() || selection.requires_local_axes()) + TransformationType transformation_type(TransformationType::World_Relative_Joint); + if (selection.is_single_full_instance() || selection.requires_local_axes()) transformation_type.set_independent(); if (selection.is_single_full_instance()) { - //FIXME GLCanvas3D::Selection::rotate() does not process absoulte rotations correctly: It does not recognize the axis index, which was changed. + //FIXME Selection::rotate() does not process absoulte rotations correctly: It does not recognize the axis index, which was changed. // transformation_type.set_absolute(); transformation_type.set_local(); } @@ -384,7 +385,7 @@ void ObjectManipulation::change_rotation_value(const Vec3d& rotation) void ObjectManipulation::change_scale_value(const Vec3d& scale) { Vec3d scaling_factor = scale; - const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); if (m_uniform_scale || selection.requires_uniform_scale()) { Vec3d abs_scale_diff = (scale - m_cache.scale).cwiseAbs(); @@ -418,7 +419,7 @@ void ObjectManipulation::change_scale_value(const Vec3d& scale) void ObjectManipulation::change_size_value(const Vec3d& size) { - const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); Vec3d ref_size = m_cache.size; if (selection.is_single_volume() || selection.is_single_modifier()) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 16160c84d..a5a180a56 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -12,6 +12,7 @@ class PrusaLockButton; namespace Slic3r { namespace GUI { +class Selection; class ObjectManipulation : public OG_Settings { @@ -90,7 +91,7 @@ public: bool IsShown() override; void UpdateAndShow(const bool show) override; - void update_settings_value(const GLCanvas3D::Selection& selection); + void update_settings_value(const Selection& selection); // Called from the App to update the UI if dirty. void update_if_dirty(); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 3781cbf45..dbfdb8e6c 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -10,6 +10,8 @@ #include "I18N.hpp" +#include + namespace Slic3r { namespace GUI @@ -40,7 +42,7 @@ void OG_Settings::Hide() void OG_Settings::UpdateAndShow(const bool show) { Show(show); -// m_parent->Layout(); +// m_parent->Layout(); } wxSizer* OG_Settings::get_sizer() @@ -84,6 +86,7 @@ void ObjectSettings::update_settings_list() btn->Bind(wxEVT_BUTTON, [opt_key, config, this](wxEvent &event) { config->erase(opt_key); wxTheApp->CallAfter([this]() { + wxWindowUpdateLocker noUpdates(m_parent); update_settings_list(); m_parent->Layout(); }); @@ -119,7 +122,7 @@ void ObjectSettings::update_settings_list() if (cat.second.size() == 1 && cat.second[0] == "extruder") continue; - auto optgroup = std::make_shared(m_parent, cat.first, config, false, extra_column); + auto optgroup = std::make_shared(m_og->ctrl_parent(), cat.first, config, false, extra_column); optgroup->label_width = 15 * wxGetApp().em_unit();//150; optgroup->sidetext_width = 7 * wxGetApp().em_unit();//70; diff --git a/src/slic3r/GUI/GUI_ObjectSettings.hpp b/src/slic3r/GUI/GUI_ObjectSettings.hpp index 3e72713bf..12115e208 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.hpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.hpp @@ -2,6 +2,7 @@ #define slic3r_GUI_ObjectSettings_hpp_ #include +#include #include class wxBoxSizer; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 7ac3f80e2..2361ff6d3 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -27,17 +27,11 @@ namespace Slic3r { namespace GUI { -View3D::View3D(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) + View3D::View3D(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) : m_canvas_widget(nullptr) , m_canvas(nullptr) -#if !ENABLE_IMGUI - , m_gizmo_widget(nullptr) -#endif // !ENABLE_IMGUI - , m_model(nullptr) - , m_config(nullptr) - , m_process(nullptr) { - init(parent, model, config, process); + init(parent, bed, camera, view_toolbar, model, config, process); } View3D::~View3D() @@ -50,13 +44,13 @@ View3D::~View3D() } } -bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) +bool View3D::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) { if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; m_canvas_widget = GLCanvas3DManager::create_wxglcanvas(this); - _3DScene::add_canvas(m_canvas_widget); + _3DScene::add_canvas(m_canvas_widget, bed, camera, view_toolbar); m_canvas = _3DScene::get_canvas(this->m_canvas_widget); m_canvas->allow_multisample(GLCanvas3DManager::can_multisample()); @@ -70,17 +64,8 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba m_canvas->enable_gizmos(true); m_canvas->enable_toolbar(true); -#if !ENABLE_IMGUI - m_gizmo_widget = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); - m_gizmo_widget->SetSizer(new wxBoxSizer(wxVERTICAL)); - m_canvas->set_external_gizmo_widgets_parent(m_gizmo_widget); -#endif // !ENABLE_IMGUI - wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); -#if !ENABLE_IMGUI - main_sizer->Add(m_gizmo_widget, 0, wxALL | wxEXPAND, 0); -#endif // !ENABLE_IMGUI SetSizer(main_sizer); SetMinSize(GetSize()); @@ -89,18 +74,6 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba return true; } -void View3D::set_bed(Bed3D* bed) -{ - if (m_canvas != nullptr) - m_canvas->set_bed(bed); -} - -void View3D::set_view_toolbar(GLToolbar* toolbar) -{ - if (m_canvas != nullptr) - m_canvas->set_view_toolbar(toolbar); -} - void View3D::set_as_dirty() { if (m_canvas != nullptr) @@ -137,20 +110,6 @@ void View3D::mirror_selection(Axis axis) m_canvas->mirror_selection(axis); } -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS -void View3D::update_toolbar_items_visibility() -{ - if (m_canvas != nullptr) - m_canvas->update_toolbar_items_visibility(); -} -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS - -void View3D::enable_toolbar_item(const std::string& name, bool enable) -{ - if (m_canvas != nullptr) - m_canvas->enable_toolbar_item(name, enable); -} - int View3D::check_volumes_outside_state() const { return (m_canvas != nullptr) ? m_canvas->check_volumes_outside_state() : false; @@ -191,10 +150,11 @@ void View3D::reload_scene(bool refresh_immediately, bool force_full_scene_refres void View3D::render() { if (m_canvas != nullptr) - m_canvas->render(); + //m_canvas->render(); + m_canvas->set_as_dirty(); } -Preview::Preview(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process_func) +Preview::Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process_func) : m_canvas_widget(nullptr) , m_canvas(nullptr) , m_double_slider_sizer(nullptr) @@ -214,28 +174,28 @@ Preview::Preview(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicing , m_loaded(false) , m_enabled(false) , m_schedule_background_process(schedule_background_process_func) +#ifdef __linux__ + , m_volumes_cleanup_required(false) +#endif // __linux__ { - if (init(parent, config, process, gcode_preview_data)) + if (init(parent, bed, camera, view_toolbar)) { show_hide_ui_elements("none"); load_print(); } } -bool Preview::init(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data) +bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) { - if ((config == nullptr) || (process == nullptr) || (gcode_preview_data == nullptr)) - return false; - if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; m_canvas_widget = GLCanvas3DManager::create_wxglcanvas(this); - _3DScene::add_canvas(m_canvas_widget); - m_canvas = _3DScene::get_canvas(this->m_canvas_widget); + _3DScene::add_canvas(m_canvas_widget, bed, camera, view_toolbar); + m_canvas = _3DScene::get_canvas(this->m_canvas_widget); m_canvas->allow_multisample(GLCanvas3DManager::can_multisample()); m_canvas->set_config(m_config); - m_canvas->set_process(process); + m_canvas->set_process(m_process); m_canvas->enable_legend_texture(true); m_canvas->enable_dynamic_background(true); @@ -343,18 +303,6 @@ Preview::~Preview() } } -void Preview::set_bed(Bed3D* bed) -{ - if (m_canvas != nullptr) - m_canvas->set_bed(bed); -} - -void Preview::set_view_toolbar(GLToolbar* toolbar) -{ - if (m_canvas != nullptr) - m_canvas->set_view_toolbar(toolbar); -} - void Preview::set_number_extruders(unsigned int number_extruders) { if (m_number_extruders != number_extruders) @@ -391,45 +339,46 @@ void Preview::select_view(const std::string& direction) m_canvas->select_view(direction); } -void Preview::set_viewport_from_scene(GLCanvas3D* canvas) -{ - if (canvas != nullptr) - m_canvas->set_viewport_from_scene(*canvas); -} - -void Preview::set_viewport_into_scene(GLCanvas3D* canvas) -{ - if (canvas != nullptr) - canvas->set_viewport_from_scene(*m_canvas); -} - void Preview::set_drop_target(wxDropTarget* target) { if (target != nullptr) SetDropTarget(target); } -void Preview::load_print() +void Preview::load_print(bool keep_z_range) { PrinterTechnology tech = m_process->current_printer_technology(); if (tech == ptFFF) - load_print_as_fff(); + load_print_as_fff(keep_z_range); else if (tech == ptSLA) load_print_as_sla(); } -void Preview::reload_print(bool force, bool keep_volumes) +void Preview::reload_print(bool keep_volumes) { - if (!keep_volumes) +#ifdef __linux__ + // We are getting mysterious crashes on Linux in gtk due to OpenGL context activation GH #1874 #1955. + // So we are applying a workaround here: a delayed release of OpenGL vertex buffers. + if (!IsShown()) + { + m_volumes_cleanup_required = !keep_volumes; + return; + } +#endif /* __linux __ */ + if ( +#ifdef __linux__ + m_volumes_cleanup_required || +#endif /* __linux__ */ + !keep_volumes) { m_canvas->reset_volumes(); m_canvas->reset_legend_texture(); m_loaded = false; +#ifdef __linux__ + m_volumes_cleanup_required = false; +#endif /* __linux__ */ } - if (!IsShown() && !force) - return; - load_print(); } @@ -440,7 +389,7 @@ void Preview::refresh_print() if (!IsShown()) return; - load_print(); + load_print(true); } void Preview::bind_event_handlers() @@ -497,10 +446,10 @@ void Preview::reset_sliders() m_double_slider_sizer->Hide((size_t)0); } -void Preview::update_sliders(const std::vector& layers_z) +void Preview::update_sliders(const std::vector& layers_z, bool keep_z_range) { m_enabled = true; - update_double_slider(layers_z); + update_double_slider(layers_z, keep_z_range); m_double_slider_sizer->Show((size_t)0); Layout(); } @@ -609,15 +558,19 @@ static int find_close_layer_idx(const std::vector& zs, double &z, double return -1; } -void Preview::update_double_slider(const std::vector& layers_z, bool force_sliders_full_range) +void Preview::update_double_slider(const std::vector& layers_z, bool keep_z_range) { // Save the initial slider span. double z_low = m_slider->GetLowerValueD(); double z_high = m_slider->GetHigherValueD(); bool was_empty = m_slider->GetMaxValue() == 0; - bool span_changed = layers_z.empty() || std::abs(layers_z.back() - m_slider->GetMaxValueD()) > 1e-6; - force_sliders_full_range |= was_empty | span_changed; - bool snap_to_min = force_sliders_full_range || m_slider->is_lower_at_min(); + bool force_sliders_full_range = was_empty; + if (!keep_z_range) + { + bool span_changed = layers_z.empty() || std::abs(layers_z.back() - m_slider->GetMaxValueD()) > 1e-6; + force_sliders_full_range |= span_changed; + } + bool snap_to_min = force_sliders_full_range || m_slider->is_lower_at_min(); bool snap_to_max = force_sliders_full_range || m_slider->is_higher_at_max(); std::vector> values; @@ -703,7 +656,7 @@ void Preview::update_double_slider_from_canvas(wxKeyEvent& event) event.Skip(); } -void Preview::load_print_as_fff() +void Preview::load_print_as_fff(bool keep_z_range) { if (m_loaded || m_process->current_printer_technology() != ptFFF) return; @@ -789,10 +742,11 @@ void Preview::load_print_as_fff() if (IsShown()) { - if (gcode_preview_data_valid) + if (gcode_preview_data_valid) { // Load the real G-code preview. m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); - else + m_loaded = true; + } else // Load the initial preview based on slices, not the final G-code. m_canvas->load_preview(colors, color_print_values); show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple"); @@ -803,8 +757,7 @@ void Preview::load_print_as_fff() reset_sliders(); m_canvas_widget->Refresh(); } else - update_sliders(zs); - m_loaded = true; + update_sliders(zs, keep_z_range); } } @@ -816,19 +769,17 @@ void Preview::load_print_as_sla() unsigned int n_layers = 0; const SLAPrint* print = m_process->sla_print(); - std::set zs; + std::vector zs; + double initial_layer_height = print->material_config().initial_layer_height.value; for (const SLAPrintObject* obj : print->objects()) - { - double shift_z = obj->get_current_elevation(); if (obj->is_step_done(slaposIndexSlices)) { - const SLAPrintObject::SliceIndex& index = obj->get_slice_index(); - for (const SLAPrintObject::SliceIndex::value_type& id : index) - { - zs.insert(shift_z + id.first); - } + auto slicerecords = obj->get_slice_records(); + auto low_coord = slicerecords.begin()->key(); + for (auto& rec : slicerecords) + zs.emplace_back(initial_layer_height + (rec.key() - low_coord) * SCALING_FACTOR); } - } + sort_remove_duplicates(zs); n_layers = (unsigned int)zs.size(); if (n_layers == 0) @@ -843,11 +794,7 @@ void Preview::load_print_as_sla() show_hide_ui_elements("none"); if (n_layers > 0) - { - std::vector layer_zs; - std::copy(zs.begin(), zs.end(), std::back_inserter(layer_zs)); - update_sliders(layer_zs); - } + update_sliders(zs); m_loaded = true; } diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index d3cb4e5bf..96c49e54f 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -28,30 +28,20 @@ namespace GUI { class GLCanvas3D; class GLToolbar; class Bed3D; +struct Camera; class View3D : public wxPanel { wxGLCanvas* m_canvas_widget; GLCanvas3D* m_canvas; -#if !ENABLE_IMGUI - wxPanel* m_gizmo_widget; -#endif // !ENABLE_IMGUI - - Model* m_model; - DynamicPrintConfig* m_config; - BackgroundSlicingProcess* m_process; - public: - View3D(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); + View3D(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); virtual ~View3D(); wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } GLCanvas3D* get_canvas3d() { return m_canvas; } - void set_bed(Bed3D* bed); - void set_view_toolbar(GLToolbar* toolbar); - void set_as_dirty(); void bed_shape_changed(); @@ -60,10 +50,6 @@ public: void delete_selected(); void mirror_selection(Axis axis); -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS - void update_toolbar_items_visibility(); -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS - void enable_toolbar_item(const std::string& name, bool enable); int check_volumes_outside_state() const; bool is_layers_editing_enabled() const; @@ -77,7 +63,7 @@ public: void render(); private: - bool init(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); + bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); }; class Preview : public wxPanel @@ -98,6 +84,12 @@ class Preview : public wxPanel BackgroundSlicingProcess* m_process; GCodePreviewData* m_gcode_preview_data; +#ifdef __linux__ + // We are getting mysterious crashes on Linux in gtk due to OpenGL context activation GH #1874 #1955. + // So we are applying a workaround here. + bool m_volumes_cleanup_required; +#endif /* __linux__ */ + // Calling this function object forces Plater::schedule_background_process. std::function m_schedule_background_process; @@ -110,30 +102,25 @@ class Preview : public wxPanel PrusaDoubleSlider* m_slider {nullptr}; public: - Preview(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process = [](){}); + Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process = [](){}); virtual ~Preview(); wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } GLCanvas3D* get_canvas3d() { return m_canvas; } - void set_bed(Bed3D* bed); - void set_view_toolbar(GLToolbar* toolbar); - void set_number_extruders(unsigned int number_extruders); void set_canvas_as_dirty(); void set_enabled(bool enabled); void bed_shape_changed(); void select_view(const std::string& direction); - void set_viewport_from_scene(GLCanvas3D* canvas); - void set_viewport_into_scene(GLCanvas3D* canvas); void set_drop_target(wxDropTarget* target); - void load_print(); - void reload_print(bool force = false, bool keep_volumes = false); + void load_print(bool keep_z_range = false); + void reload_print(bool keep_volumes = false); void refresh_print(); private: - bool init(wxWindow* parent, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data); + bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); void bind_event_handlers(); void unbind_event_handlers(); @@ -141,7 +128,7 @@ private: void show_hide_ui_elements(const std::string& what); void reset_sliders(); - void update_sliders(const std::vector& layers_z); + void update_sliders(const std::vector& layers_z, bool keep_z_range = false); void on_size(wxSizeEvent& evt); void on_choice_view_type(wxCommandEvent& evt); @@ -153,14 +140,14 @@ private: // Create/Update/Reset double slider on 3dPreview void create_double_slider(); - void update_double_slider(const std::vector& layers_z, bool force_sliders_full_range = false); + void update_double_slider(const std::vector& layers_z, bool keep_z_range = false); void fill_slider_values(std::vector> &values, const std::vector &layers_z); void reset_double_slider(); // update DoubleSlider after keyDown in canvas void update_double_slider_from_canvas(wxKeyEvent& event); - void load_print_as_fff(); + void load_print_as_fff(bool keep_z_range = false); void load_print_as_sla(); void on_sliders_scroll_changed(wxEvent& event); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp new file mode 100644 index 000000000..1e12674ec --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -0,0 +1,281 @@ +#include "GLGizmoBase.hpp" + +#include + +#include "slic3r/GUI/GUI_App.hpp" + + + + + +// TODO: Display tooltips quicker on Linux + + + +namespace Slic3r { +namespace GUI { + +const float GLGizmoBase::Grabber::SizeFactor = 0.025f; +const float GLGizmoBase::Grabber::MinHalfSize = 1.5f; +const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f; + +GLGizmoBase::Grabber::Grabber() + : center(Vec3d::Zero()) + , angles(Vec3d::Zero()) + , dragging(false) + , enabled(true) +{ + color[0] = 1.0f; + color[1] = 1.0f; + color[2] = 1.0f; +} + +void GLGizmoBase::Grabber::render(bool hover, float size) const +{ + float render_color[3]; + if (hover) + { + render_color[0] = 1.0f - color[0]; + render_color[1] = 1.0f - color[1]; + render_color[2] = 1.0f - color[2]; + } + else + ::memcpy((void*)render_color, (const void*)color, 3 * sizeof(float)); + + render(size, render_color, true); +} + +float GLGizmoBase::Grabber::get_half_size(float size) const +{ + return std::max(size * SizeFactor, MinHalfSize); +} + +float GLGizmoBase::Grabber::get_dragging_half_size(float size) const +{ + return std::max(size * SizeFactor * DraggingScaleFactor, MinHalfSize); +} + +void GLGizmoBase::Grabber::render(float size, const float* render_color, bool use_lighting) const +{ + float half_size = dragging ? get_dragging_half_size(size) : get_half_size(size); + + if (use_lighting) + ::glEnable(GL_LIGHTING); + + ::glColor3fv(render_color); + + ::glPushMatrix(); + ::glTranslated(center(0), center(1), center(2)); + + ::glRotated(Geometry::rad2deg(angles(2)), 0.0, 0.0, 1.0); + ::glRotated(Geometry::rad2deg(angles(1)), 0.0, 1.0, 0.0); + ::glRotated(Geometry::rad2deg(angles(0)), 1.0, 0.0, 0.0); + + // face min x + ::glPushMatrix(); + ::glTranslatef(-(GLfloat)half_size, 0.0f, 0.0f); + ::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f); + render_face(half_size); + ::glPopMatrix(); + + // face max x + ::glPushMatrix(); + ::glTranslatef((GLfloat)half_size, 0.0f, 0.0f); + ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f); + render_face(half_size); + ::glPopMatrix(); + + // face min y + ::glPushMatrix(); + ::glTranslatef(0.0f, -(GLfloat)half_size, 0.0f); + ::glRotatef(90.0f, 1.0f, 0.0f, 0.0f); + render_face(half_size); + ::glPopMatrix(); + + // face max y + ::glPushMatrix(); + ::glTranslatef(0.0f, (GLfloat)half_size, 0.0f); + ::glRotatef(-90.0f, 1.0f, 0.0f, 0.0f); + render_face(half_size); + ::glPopMatrix(); + + // face min z + ::glPushMatrix(); + ::glTranslatef(0.0f, 0.0f, -(GLfloat)half_size); + ::glRotatef(180.0f, 1.0f, 0.0f, 0.0f); + render_face(half_size); + ::glPopMatrix(); + + // face max z + ::glPushMatrix(); + ::glTranslatef(0.0f, 0.0f, (GLfloat)half_size); + render_face(half_size); + ::glPopMatrix(); + + ::glPopMatrix(); + + if (use_lighting) + ::glDisable(GL_LIGHTING); +} + +void GLGizmoBase::Grabber::render_face(float half_size) const +{ + ::glBegin(GL_TRIANGLES); + ::glNormal3f(0.0f, 0.0f, 1.0f); + ::glVertex3f(-(GLfloat)half_size, -(GLfloat)half_size, 0.0f); + ::glVertex3f((GLfloat)half_size, -(GLfloat)half_size, 0.0f); + ::glVertex3f((GLfloat)half_size, (GLfloat)half_size, 0.0f); + ::glVertex3f((GLfloat)half_size, (GLfloat)half_size, 0.0f); + ::glVertex3f(-(GLfloat)half_size, (GLfloat)half_size, 0.0f); + ::glVertex3f(-(GLfloat)half_size, -(GLfloat)half_size, 0.0f); + ::glEnd(); +} + +#if ENABLE_SVG_ICONS +GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) +#else +GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id) +#endif // ENABLE_SVG_ICONS + : m_parent(parent) + , m_group_id(-1) + , m_state(Off) + , m_shortcut_key(0) +#if ENABLE_SVG_ICONS + , m_icon_filename(icon_filename) +#endif // ENABLE_SVG_ICONS + , m_sprite_id(sprite_id) + , m_hover_id(-1) + , m_dragging(false) + , m_imgui(wxGetApp().imgui()) +{ + ::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 3 * sizeof(float)); + ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 3 * sizeof(float)); + ::memcpy((void*)m_highlight_color, (const void*)DEFAULT_HIGHLIGHT_COLOR, 3 * sizeof(float)); +} + +void GLGizmoBase::set_hover_id(int id) +{ + if (m_grabbers.empty() || (id < (int)m_grabbers.size())) + { + m_hover_id = id; + on_set_hover_id(); + } +} + +void GLGizmoBase::set_highlight_color(const float* color) +{ + if (color != nullptr) + ::memcpy((void*)m_highlight_color, (const void*)color, 3 * sizeof(float)); +} + +void GLGizmoBase::enable_grabber(unsigned int id) +{ + if ((0 <= id) && (id < (unsigned int)m_grabbers.size())) + m_grabbers[id].enabled = true; + + on_enable_grabber(id); +} + +void GLGizmoBase::disable_grabber(unsigned int id) +{ + if ((0 <= id) && (id < (unsigned int)m_grabbers.size())) + m_grabbers[id].enabled = false; + + on_disable_grabber(id); +} + +void GLGizmoBase::start_dragging(const Selection& selection) +{ + m_dragging = true; + + for (int i = 0; i < (int)m_grabbers.size(); ++i) + { + m_grabbers[i].dragging = (m_hover_id == i); + } + + on_start_dragging(selection); +} + +void GLGizmoBase::stop_dragging() +{ + m_dragging = false; + + for (int i = 0; i < (int)m_grabbers.size(); ++i) + { + m_grabbers[i].dragging = false; + } + + on_stop_dragging(); +} + +void GLGizmoBase::update(const UpdateData& data, const Selection& selection) +{ + if (m_hover_id != -1) + on_update(data, selection); +} + +std::array GLGizmoBase::picking_color_component(unsigned int id) const +{ + static const float INV_255 = 1.0f / 255.0f; + + id = BASE_ID - id; + + if (m_group_id > -1) + id -= m_group_id; + + // color components are encoded to match the calculation of volume_id made into GLCanvas3D::_picking_pass() + return std::array { (float)((id >> 0) & 0xff) * INV_255, // red + (float)((id >> 8) & 0xff) * INV_255, // green + (float)((id >> 16) & 0xff) * INV_255 }; // blue +} + +void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const +{ + float size = (float)box.max_size(); + + for (int i = 0; i < (int)m_grabbers.size(); ++i) + { + if (m_grabbers[i].enabled) + m_grabbers[i].render((m_hover_id == i), size); + } +} + +void GLGizmoBase::render_grabbers(float size) const +{ + for (int i = 0; i < (int)m_grabbers.size(); ++i) + { + if (m_grabbers[i].enabled) + m_grabbers[i].render((m_hover_id == i), size); + } +} + +void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const +{ + float size = (float)box.max_size(); + + for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) + { + if (m_grabbers[i].enabled) + { + std::array color = picking_color_component(i); + m_grabbers[i].color[0] = color[0]; + m_grabbers[i].color[1] = color[1]; + m_grabbers[i].color[2] = color[2]; + m_grabbers[i].render_for_picking(size); + } + } +} + + +void GLGizmoBase::set_tooltip(const std::string& tooltip) const +{ + m_parent.set_tooltip(tooltip); +} + +std::string GLGizmoBase::format(float value, unsigned int decimals) const +{ + return Slic3r::string_printf("%.*f", decimals, value); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp new file mode 100644 index 000000000..b70c1f8df --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -0,0 +1,183 @@ +#ifndef slic3r_GLGizmoBase_hpp_ +#define slic3r_GLGizmoBase_hpp_ + +#include "libslic3r/Point.hpp" + +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/Selection.hpp" + + +class wxWindow; +class GLUquadric; +typedef class GLUquadric GLUquadricObj; + + +namespace Slic3r { + +class BoundingBoxf3; +class Linef3; +class ModelObject; + +namespace GUI { + +static const float DEFAULT_BASE_COLOR[3] = { 0.625f, 0.625f, 0.625f }; +static const float DEFAULT_DRAG_COLOR[3] = { 1.0f, 1.0f, 1.0f }; +static const float DEFAULT_HIGHLIGHT_COLOR[3] = { 1.0f, 0.38f, 0.0f }; +static const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; + + + +class ImGuiWrapper; + + +class GLGizmoBase +{ +public: + // Starting value for ids to avoid clashing with ids used by GLVolumes + // (254 is choosen to leave some space for forward compatibility) + static const unsigned int BASE_ID = 255 * 255 * 254; + +protected: + struct Grabber + { + static const float SizeFactor; + static const float MinHalfSize; + static const float DraggingScaleFactor; + + Vec3d center; + Vec3d angles; + float color[3]; + bool enabled; + bool dragging; + + Grabber(); + + void render(bool hover, float size) const; + void render_for_picking(float size) const { render(size, color, false); } + + float get_half_size(float size) const; + float get_dragging_half_size(float size) const; + + private: + void render(float size, const float* render_color, bool use_lighting) const; + void render_face(float half_size) const; + }; + +public: + enum EState + { + Off, + Hover, + On, + Num_States + }; + + struct UpdateData + { + const Linef3 mouse_ray; + const Point* mouse_pos; + bool shift_down; + + UpdateData(const Linef3& mouse_ray, const Point* mouse_pos = nullptr, bool shift_down = false) + : mouse_ray(mouse_ray), mouse_pos(mouse_pos), shift_down(shift_down) + {} + }; + +protected: + GLCanvas3D& m_parent; + + int m_group_id; + EState m_state; + int m_shortcut_key; +#if ENABLE_SVG_ICONS + std::string m_icon_filename; +#endif // ENABLE_SVG_ICONS + unsigned int m_sprite_id; + int m_hover_id; + bool m_dragging; + float m_base_color[3]; + float m_drag_color[3]; + float m_highlight_color[3]; + mutable std::vector m_grabbers; + ImGuiWrapper* m_imgui; + +public: +#if ENABLE_SVG_ICONS + GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + virtual ~GLGizmoBase() {} + + bool init() { return on_init(); } + + std::string get_name() const { return on_get_name(); } + + int get_group_id() const { return m_group_id; } + void set_group_id(int id) { m_group_id = id; } + + EState get_state() const { return m_state; } + void set_state(EState state) { m_state = state; on_set_state(); } + + int get_shortcut_key() const { return m_shortcut_key; } + void set_shortcut_key(int key) { m_shortcut_key = key; } + +#if ENABLE_SVG_ICONS + const std::string& get_icon_filename() const { return m_icon_filename; } +#endif // ENABLE_SVG_ICONS + + bool is_activable(const Selection& selection) const { return on_is_activable(selection); } + bool is_selectable() const { return on_is_selectable(); } + + unsigned int get_sprite_id() const { return m_sprite_id; } + + int get_hover_id() const { return m_hover_id; } + void set_hover_id(int id); + + void set_highlight_color(const float* color); + + void enable_grabber(unsigned int id); + void disable_grabber(unsigned int id); + + void start_dragging(const Selection& selection); + void stop_dragging(); + bool is_dragging() const { return m_dragging; } + + void update(const UpdateData& data, const Selection& selection); + + void render(const Selection& selection) const { on_render(selection); } + void render_for_picking(const Selection& selection) const { on_render_for_picking(selection); } + void render_input_window(float x, float y, float bottom_limit, const Selection& selection) { on_render_input_window(x, y, bottom_limit, selection); } + +protected: + virtual bool on_init() = 0; + virtual std::string on_get_name() const = 0; + virtual void on_set_state() {} + virtual void on_set_hover_id() {} + virtual bool on_is_activable(const Selection& selection) const { return true; } + virtual bool on_is_selectable() const { return true; } + virtual void on_enable_grabber(unsigned int id) {} + virtual void on_disable_grabber(unsigned int id) {} + virtual void on_start_dragging(const Selection& selection) {} + virtual void on_stop_dragging() {} + virtual void on_update(const UpdateData& data, const Selection& selection) = 0; + virtual void on_render(const Selection& selection) const = 0; + virtual void on_render_for_picking(const Selection& selection) const = 0; + virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) {} + + // Returns the picking color for the given id, based on the BASE_ID constant + // No check is made for clashing with other picking color (i.e. GLVolumes) + std::array picking_color_component(unsigned int id) const; + void render_grabbers(const BoundingBoxf3& box) const; + void render_grabbers(float size) const; + void render_grabbers_for_picking(const BoundingBoxf3& box) const; + + void set_tooltip(const std::string& tooltip) const; + std::string format(float value, unsigned int decimals) const; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoBase_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp new file mode 100644 index 000000000..e228706c0 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -0,0 +1,257 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoCut.hpp" + +#include + +#include +#include +#include +#include + +#include "slic3r/GUI/GUI_App.hpp" + + +namespace Slic3r { +namespace GUI { + + + + + + +// GLGizmoCut + +class GLGizmoCutPanel : public wxPanel +{ +public: + GLGizmoCutPanel(wxWindow *parent); + + void display(bool display); +private: + bool m_active; + wxCheckBox *m_cb_rotate; + wxButton *m_btn_cut; + wxButton *m_btn_cancel; +}; + +GLGizmoCutPanel::GLGizmoCutPanel(wxWindow *parent) + : wxPanel(parent) + , m_active(false) + , m_cb_rotate(new wxCheckBox(this, wxID_ANY, _(L("Rotate lower part upwards")))) + , m_btn_cut(new wxButton(this, wxID_OK, _(L("Perform cut")))) + , m_btn_cancel(new wxButton(this, wxID_CANCEL, _(L("Cancel")))) +{ + enum { MARGIN = 5 }; + + auto *sizer = new wxBoxSizer(wxHORIZONTAL); + + auto *label = new wxStaticText(this, wxID_ANY, _(L("Cut object:"))); + sizer->Add(label, 0, wxALL | wxALIGN_CENTER, MARGIN); + sizer->Add(m_cb_rotate, 0, wxALL | wxALIGN_CENTER, MARGIN); + sizer->AddStretchSpacer(); + sizer->Add(m_btn_cut, 0, wxALL | wxALIGN_CENTER, MARGIN); + sizer->Add(m_btn_cancel, 0, wxALL | wxALIGN_CENTER, MARGIN); + + SetSizer(sizer); +} + +void GLGizmoCutPanel::display(bool display) +{ + Show(display); + GetParent()->Layout(); +} + + +const double GLGizmoCut::Offset = 10.0; +const double GLGizmoCut::Margin = 20.0; +const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0 }; + +#if ENABLE_SVG_ICONS +GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +#else +GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, sprite_id) +#endif // ENABLE_SVG_ICONS + , m_cut_z(0.0) + , m_max_z(0.0) + , m_keep_upper(true) + , m_keep_lower(true) + , m_rotate_lower(false) +{} + + +bool GLGizmoCut::on_init() +{ + m_grabbers.emplace_back(); + m_shortcut_key = WXK_CONTROL_C; + return true; +} + +std::string GLGizmoCut::on_get_name() const +{ + return L("Cut [C]"); +} + +void GLGizmoCut::on_set_state() +{ + // Reset m_cut_z on gizmo activation + if (get_state() == On) { + m_cut_z = m_parent.get_selection().get_bounding_box().size()(2) / 2.0; + } +} + +bool GLGizmoCut::on_is_activable(const Selection& selection) const +{ + return selection.is_single_full_instance() && !selection.is_wipe_tower(); +} + +void GLGizmoCut::on_start_dragging(const Selection& selection) +{ + if (m_hover_id == -1) { return; } + + const BoundingBoxf3& box = selection.get_bounding_box(); + m_start_z = m_cut_z; + update_max_z(selection); + m_drag_pos = m_grabbers[m_hover_id].center; + m_drag_center = box.center(); + m_drag_center(2) = m_cut_z; +} + +void GLGizmoCut::on_update(const UpdateData& data, const Selection& selection) +{ + if (m_hover_id != -1) { + set_cut_z(m_start_z + calc_projection(data.mouse_ray)); + } +} + +void GLGizmoCut::on_render(const Selection& selection) const +{ + if (m_grabbers[0].dragging) { + set_tooltip("Z: " + format(m_cut_z, 2)); + } + + update_max_z(selection); + + const BoundingBoxf3& box = selection.get_bounding_box(); + Vec3d plane_center = box.center(); + plane_center(2) = m_cut_z; + + const float min_x = box.min(0) - Margin; + const float max_x = box.max(0) + Margin; + const float min_y = box.min(1) - Margin; + const float max_y = box.max(1) + Margin; + ::glEnable(GL_DEPTH_TEST); + ::glDisable(GL_CULL_FACE); + ::glEnable(GL_BLEND); + ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Draw the cutting plane + ::glBegin(GL_QUADS); + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, plane_center(2)); + ::glVertex3f(max_x, min_y, plane_center(2)); + ::glVertex3f(max_x, max_y, plane_center(2)); + ::glVertex3f(min_x, max_y, plane_center(2)); + ::glEnd(); + + ::glEnable(GL_CULL_FACE); + ::glDisable(GL_BLEND); + + // TODO: draw cut part contour? + + // Draw the grabber and the connecting line + m_grabbers[0].center = plane_center; + m_grabbers[0].center(2) = plane_center(2) + Offset; + + ::glDisable(GL_DEPTH_TEST); + ::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f); + ::glColor3f(1.0, 1.0, 0.0); + ::glBegin(GL_LINES); + ::glVertex3dv(plane_center.data()); + ::glVertex3dv(m_grabbers[0].center.data()); + ::glEnd(); + + std::copy(std::begin(GrabberColor), std::end(GrabberColor), m_grabbers[0].color); + m_grabbers[0].render(m_hover_id == 0, box.max_size()); +} + +void GLGizmoCut::on_render_for_picking(const Selection& selection) const +{ + ::glDisable(GL_DEPTH_TEST); + + render_grabbers_for_picking(selection.get_bounding_box()); +} + +void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) +{ + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + ImGui::PushItemWidth(100.0f); + bool _value_changed = ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); + + m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper); + m_imgui->checkbox(_(L("Keep lower part")), m_keep_lower); + m_imgui->checkbox(_(L("Rotate lower part upwards")), m_rotate_lower); + + m_imgui->disabled_begin(!m_keep_upper && !m_keep_lower); + const bool cut_clicked = m_imgui->button(_(L("Perform cut"))); + m_imgui->disabled_end(); + + m_imgui->end(); + + if (cut_clicked && (m_keep_upper || m_keep_lower)) { + perform_cut(selection); + } +} + +void GLGizmoCut::update_max_z(const Selection& selection) const +{ + m_max_z = selection.get_bounding_box().size()(2); + set_cut_z(m_cut_z); +} + +void GLGizmoCut::set_cut_z(double cut_z) const +{ + // Clamp the plane to the object's bounding box + m_cut_z = std::max(0.0, std::min(m_max_z, cut_z)); +} + +void GLGizmoCut::perform_cut(const Selection& selection) +{ + const auto instance_idx = selection.get_instance_idx(); + const auto object_idx = selection.get_object_idx(); + + wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); + + wxGetApp().plater()->cut(object_idx, instance_idx, m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); +} + +double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const +{ + double projection = 0.0; + + const Vec3d starting_vec = m_drag_pos - m_drag_center; + const double len_starting_vec = starting_vec.norm(); + if (len_starting_vec != 0.0) + { + Vec3d mouse_dir = mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - m_drag_pos; + + // finds projection of the vector along the staring direction + projection = inters_vec.dot(starting_vec.normalized()); + } + return projection; +} + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp new file mode 100644 index 000000000..fd4e8d8dc --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -0,0 +1,53 @@ +#ifndef slic3r_GLGizmoCut_hpp_ +#define slic3r_GLGizmoCut_hpp_ + +#include "GLGizmoBase.hpp" + + +namespace Slic3r { +namespace GUI { + +class GLGizmoCut : public GLGizmoBase +{ + static const double Offset; + static const double Margin; + static const std::array GrabberColor; + + mutable double m_cut_z; + double m_start_z; + mutable double m_max_z; + Vec3d m_drag_pos; + Vec3d m_drag_center; + bool m_keep_upper; + bool m_keep_lower; + bool m_rotate_lower; + +public: +#if ENABLE_SVG_ICONS + GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const; + virtual void on_set_state(); + virtual bool on_is_activable(const Selection& selection) const; + virtual void on_start_dragging(const Selection& selection); + virtual void on_update(const UpdateData& data, const Selection& selection); + virtual void on_render(const Selection& selection) const; + virtual void on_render_for_picking(const Selection& selection) const; + virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); + +private: + void update_max_z(const Selection& selection) const; + void set_cut_z(double cut_z) const; + void perform_cut(const Selection& selection); + double calc_projection(const Linef3& mouse_ray) const; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoCut_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp new file mode 100644 index 000000000..2ed048677 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -0,0 +1,357 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoFlatten.hpp" + +#include + +#include + +namespace Slic3r { +namespace GUI { + + +#if ENABLE_SVG_ICONS +GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +#else +GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, sprite_id) +#endif // ENABLE_SVG_ICONS + , m_normal(Vec3d::Zero()) + , m_starting_center(Vec3d::Zero()) +{ +} + +bool GLGizmoFlatten::on_init() +{ + m_shortcut_key = WXK_CONTROL_F; + return true; +} + +std::string GLGizmoFlatten::on_get_name() const +{ + return L("Place on face [F]"); +} + +bool GLGizmoFlatten::on_is_activable(const Selection& selection) const +{ + return selection.is_single_full_instance(); +} + +void GLGizmoFlatten::on_start_dragging(const Selection& selection) +{ + if (m_hover_id != -1) + { + assert(m_planes_valid); + m_normal = m_planes[m_hover_id].normal; + m_starting_center = selection.get_bounding_box().center(); + } +} + +void GLGizmoFlatten::on_render(const Selection& selection) const +{ + ::glClear(GL_DEPTH_BUFFER_BIT); + + ::glEnable(GL_DEPTH_TEST); + ::glEnable(GL_BLEND); + + if (selection.is_single_full_instance()) + { + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + ::glPushMatrix(); + ::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()); + ::glMultMatrixd(m.data()); + if (this->is_plane_update_necessary()) + const_cast(this)->update_planes(); + for (int i = 0; i < (int)m_planes.size(); ++i) + { + if (i == m_hover_id) + ::glColor4f(0.9f, 0.9f, 0.9f, 0.75f); + else + ::glColor4f(0.9f, 0.9f, 0.9f, 0.5f); + + ::glBegin(GL_POLYGON); + for (const Vec3d& vertex : m_planes[i].vertices) + { + ::glVertex3dv(vertex.data()); + } + ::glEnd(); + } + ::glPopMatrix(); + } + + ::glEnable(GL_CULL_FACE); + ::glDisable(GL_BLEND); +} + +void GLGizmoFlatten::on_render_for_picking(const Selection& selection) const +{ + ::glDisable(GL_DEPTH_TEST); + ::glDisable(GL_BLEND); + + if (selection.is_single_full_instance()) + { + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + ::glPushMatrix(); + ::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()); + ::glMultMatrixd(m.data()); + if (this->is_plane_update_necessary()) + const_cast(this)->update_planes(); + for (int i = 0; i < (int)m_planes.size(); ++i) + { + ::glColor3fv(picking_color_component(i).data()); + ::glBegin(GL_POLYGON); + for (const Vec3d& vertex : m_planes[i].vertices) + { + ::glVertex3dv(vertex.data()); + } + ::glEnd(); + } + ::glPopMatrix(); + } + + ::glEnable(GL_CULL_FACE); +} + +void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) +{ + m_starting_center = Vec3d::Zero(); + if (m_model_object != model_object) { + m_planes.clear(); + m_planes_valid = false; + } + m_model_object = model_object; +} + +void GLGizmoFlatten::update_planes() +{ + TriangleMesh ch; + for (const ModelVolume* vol : m_model_object->volumes) + { + if (vol->type() != ModelVolumeType::MODEL_PART) + continue; + TriangleMesh vol_ch = vol->get_convex_hull(); + vol_ch.transform(vol->get_matrix()); + ch.merge(vol_ch); + } + ch = ch.convex_hull_3d(); + m_planes.clear(); + const Transform3d& inst_matrix = m_model_object->instances.front()->get_matrix(true); + + // Following constants are used for discarding too small polygons. + const float minimal_area = 5.f; // in square mm (world coordinates) + const float minimal_side = 1.f; // mm + + // Now we'll go through all the facets and append Points of facets sharing the same normal. + // This part is still performed in mesh coordinate system. + const int num_of_facets = ch.stl.stats.number_of_facets; + std::vector facet_queue(num_of_facets, 0); + std::vector facet_visited(num_of_facets, false); + int facet_queue_cnt = 0; + const stl_normal* normal_ptr = nullptr; + while (1) { + // Find next unvisited triangle: + int facet_idx = 0; + for (; facet_idx < num_of_facets; ++ facet_idx) + if (!facet_visited[facet_idx]) { + facet_queue[facet_queue_cnt ++] = facet_idx; + facet_visited[facet_idx] = true; + normal_ptr = &ch.stl.facet_start[facet_idx].normal; + m_planes.emplace_back(); + break; + } + if (facet_idx == num_of_facets) + break; // Everything was visited already + + while (facet_queue_cnt > 0) { + int facet_idx = facet_queue[-- facet_queue_cnt]; + const stl_normal& this_normal = ch.stl.facet_start[facet_idx].normal; + if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) { + stl_vertex* first_vertex = ch.stl.facet_start[facet_idx].vertex; + for (int j=0; j<3; ++j) + m_planes.back().vertices.emplace_back((double)first_vertex[j](0), (double)first_vertex[j](1), (double)first_vertex[j](2)); + + facet_visited[facet_idx] = true; + for (int j = 0; j < 3; ++ j) { + int neighbor_idx = ch.stl.neighbors_start[facet_idx].neighbor[j]; + if (! facet_visited[neighbor_idx]) + facet_queue[facet_queue_cnt ++] = neighbor_idx; + } + } + } + m_planes.back().normal = normal_ptr->cast(); + + // Now we'll transform all the points into world coordinates, so that the areas, angles and distances + // make real sense. + m_planes.back().vertices = transform(m_planes.back().vertices, inst_matrix); + + // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway): + if (m_planes.back().vertices.size() == 3 && + ((m_planes.back().vertices[0] - m_planes.back().vertices[1]).norm() < minimal_side + || (m_planes.back().vertices[0] - m_planes.back().vertices[2]).norm() < minimal_side + || (m_planes.back().vertices[1] - m_planes.back().vertices[2]).norm() < minimal_side)) + m_planes.pop_back(); + } + + // Let's prepare transformation of the normal vector from mesh to instance coordinates. + Geometry::Transformation t(inst_matrix); + Vec3d scaling = t.get_scaling_factor(); + t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); + + // Now we'll go through all the polygons, transform the points into xy plane to process them: + for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { + Pointf3s& polygon = m_planes[polygon_id].vertices; + const Vec3d& normal = m_planes[polygon_id].normal; + + // transform the normal according to the instance matrix: + Vec3d normal_transformed = t.get_matrix() * normal; + + // We are going to rotate about z and y to flatten the plane + Eigen::Quaterniond q; + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix(); + polygon = transform(polygon, m); + + // Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since + // it works in fixed point representation, we will rescale the polygon to avoid overflows. + // And yes, it is a nasty thing to do. Whoever has time is free to refactor. + Vec3d bb_size = BoundingBoxf3(polygon).size(); + float sf = std::min(1./bb_size(0), 1./bb_size(1)); + Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); + polygon = transform(polygon, tr); + polygon = Slic3r::Geometry::convex_hull(polygon); + polygon = transform(polygon, tr.inverse()); + + // Calculate area of the polygons and discard ones that are too small + float& area = m_planes[polygon_id].area; + area = 0.f; + for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula + area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1); + area = 0.5f * std::abs(area); + + bool discard = false; + if (area < minimal_area) + discard = true; + else { + // We also check the inner angles and discard polygons with angles smaller than the following threshold + const double angle_threshold = ::cos(10.0 * (double)PI / 180.0); + + for (unsigned int i = 0; i < polygon.size(); ++i) { + const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1]; + const Vec3d& curr = polygon[i]; + const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1]; + + if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) { + discard = true; + break; + } + } + } + + if (discard) { + m_planes.erase(m_planes.begin() + (polygon_id--)); + continue; + } + + // We will shrink the polygon a little bit so it does not touch the object edges: + Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); + centroid /= (double)polygon.size(); + for (auto& vertex : polygon) + vertex = 0.9f*vertex + 0.1f*centroid; + + // Polygon is now simple and convex, we'll round the corners to make them look nicer. + // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex + // towards their average (controlled by 'aggressivity'). This is repeated k times. + // In next iterations, the neighbours are not always taken at the middle (to increase the + // rounding effect at the corners, where we need it most). + const unsigned int k = 10; // number of iterations + const float aggressivity = 0.2f; // agressivity + const unsigned int N = polygon.size(); + std::vector> neighbours; + if (k != 0) { + Pointf3s points_out(2*k*N); // vector long enough to store the future vertices + for (unsigned int j=0; jvolumes) { + m_volumes_matrices.push_back(vol->get_matrix()); + m_volumes_types.push_back(vol->type()); + } + m_first_instance_scale = m_model_object->instances.front()->get_scaling_factor(); + m_first_instance_mirror = m_model_object->instances.front()->get_mirror(); + + m_planes_valid = true; +} + + +bool GLGizmoFlatten::is_plane_update_necessary() const +{ + if (m_state != On || !m_model_object || m_model_object->instances.empty()) + return false; + + if (! m_planes_valid || m_model_object->volumes.size() != m_volumes_matrices.size()) + return true; + + // We want to recalculate when the scale changes - some planes could (dis)appear. + if (! m_model_object->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale) + || ! m_model_object->instances.front()->get_mirror().isApprox(m_first_instance_mirror)) + return true; + + for (unsigned int i=0; i < m_model_object->volumes.size(); ++i) + if (! m_model_object->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i]) + || m_model_object->volumes[i]->type() != m_volumes_types[i]) + return true; + + return false; +} + +Vec3d GLGizmoFlatten::get_flattening_normal() const +{ + Vec3d out = m_normal; + m_normal = Vec3d::Zero(); + m_starting_center = Vec3d::Zero(); + return out; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp new file mode 100644 index 000000000..1bd17e5ef --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -0,0 +1,67 @@ +#ifndef slic3r_GLGizmoFlatten_hpp_ +#define slic3r_GLGizmoFlatten_hpp_ + +#include "GLGizmoBase.hpp" + + +namespace Slic3r { +namespace GUI { + + +class GLGizmoFlatten : public GLGizmoBase +{ +// This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself. + +private: + mutable Vec3d m_normal; + + struct PlaneData { + std::vector vertices; + Vec3d normal; + float area; + }; + + // This holds information to decide whether recalculation is necessary: + std::vector m_volumes_matrices; + std::vector m_volumes_types; + Vec3d m_first_instance_scale; + Vec3d m_first_instance_mirror; + + std::vector m_planes; + bool m_planes_valid = false; + mutable Vec3d m_starting_center; + const ModelObject* m_model_object = nullptr; + std::vector instances_matrices; + + void update_planes(); + bool is_plane_update_necessary() const; + +public: +#if ENABLE_SVG_ICONS + GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + + void set_flattening_data(const ModelObject* model_object); + Vec3d get_flattening_normal() const; + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const; + virtual bool on_is_activable(const Selection& selection) const; + virtual void on_start_dragging(const Selection& selection); + virtual void on_update(const UpdateData& data, const Selection& selection) {} + virtual void on_render(const Selection& selection) const; + virtual void on_render_for_picking(const Selection& selection) const; + virtual void on_set_state() + { + if (m_state == On && is_plane_update_necessary()) + update_planes(); + } +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoFlatten_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp new file mode 100644 index 000000000..03151c433 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -0,0 +1,255 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoMove.hpp" + +#include + + +namespace Slic3r { +namespace GUI { + +const double GLGizmoMove3D::Offset = 10.0; + +#if ENABLE_SVG_ICONS +GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +#else +GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, sprite_id) +#endif // ENABLE_SVG_ICONS + , m_displacement(Vec3d::Zero()) + , m_snap_step(1.0) + , m_starting_drag_position(Vec3d::Zero()) + , m_starting_box_center(Vec3d::Zero()) + , m_starting_box_bottom_center(Vec3d::Zero()) + , m_quadric(nullptr) +{ + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +GLGizmoMove3D::~GLGizmoMove3D() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); +} + +bool GLGizmoMove3D::on_init() +{ + for (int i = 0; i < 3; ++i) + { + m_grabbers.push_back(Grabber()); + } + + m_shortcut_key = WXK_CONTROL_M; + + return true; +} + +std::string GLGizmoMove3D::on_get_name() const +{ + return L("Move [M]"); +} + +void GLGizmoMove3D::on_start_dragging(const Selection& selection) +{ + if (m_hover_id != -1) + { + m_displacement = Vec3d::Zero(); + const BoundingBoxf3& box = selection.get_bounding_box(); + m_starting_drag_position = m_grabbers[m_hover_id].center; + m_starting_box_center = box.center(); + m_starting_box_bottom_center = box.center(); + m_starting_box_bottom_center(2) = box.min(2); + } +} + +void GLGizmoMove3D::on_stop_dragging() +{ + m_displacement = Vec3d::Zero(); +} + +void GLGizmoMove3D::on_update(const UpdateData& data, const Selection& selection) +{ + if (m_hover_id == 0) + m_displacement(0) = calc_projection(data); + else if (m_hover_id == 1) + m_displacement(1) = calc_projection(data); + else if (m_hover_id == 2) + m_displacement(2) = calc_projection(data); +} + +void GLGizmoMove3D::on_render(const Selection& selection) const +{ + bool show_position = selection.is_single_full_instance(); + const Vec3d& position = selection.get_bounding_box().center(); + + if ((show_position && (m_hover_id == 0)) || m_grabbers[0].dragging) + set_tooltip("X: " + format(show_position ? position(0) : m_displacement(0), 2)); + else if (!m_grabbers[0].dragging && (m_hover_id == 0)) + set_tooltip("X"); + else if ((show_position && (m_hover_id == 1)) || m_grabbers[1].dragging) + set_tooltip("Y: " + format(show_position ? position(1) : m_displacement(1), 2)); + else if (!m_grabbers[1].dragging && (m_hover_id == 1)) + set_tooltip("Y"); + else if ((show_position && (m_hover_id == 2)) || m_grabbers[2].dragging) + set_tooltip("Z: " + format(show_position ? position(2) : m_displacement(2), 2)); + else if (!m_grabbers[2].dragging && (m_hover_id == 2)) + set_tooltip("Z"); + + ::glClear(GL_DEPTH_BUFFER_BIT); + ::glEnable(GL_DEPTH_TEST); + + const BoundingBoxf3& box = selection.get_bounding_box(); + const Vec3d& center = box.center(); + + // x axis + m_grabbers[0].center = Vec3d(box.max(0) + Offset, center(1), center(2)); + ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); + + // y axis + m_grabbers[1].center = Vec3d(center(0), box.max(1) + Offset, center(2)); + ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); + + // z axis + m_grabbers[2].center = Vec3d(center(0), center(1), box.max(2) + Offset); + ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); + + ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); + + if (m_hover_id == -1) + { + // draw axes + for (unsigned int i = 0; i < 3; ++i) + { + if (m_grabbers[i].enabled) + { + ::glColor3fv(AXES_COLOR[i]); + ::glBegin(GL_LINES); + ::glVertex3dv(center.data()); + ::glVertex3dv(m_grabbers[i].center.data()); + ::glEnd(); + } + } + + // draw grabbers + render_grabbers(box); + for (unsigned int i = 0; i < 3; ++i) + { + if (m_grabbers[i].enabled) + render_grabber_extension((Axis)i, box, false); + } + } + else + { + // draw axis + ::glColor3fv(AXES_COLOR[m_hover_id]); + ::glBegin(GL_LINES); + ::glVertex3dv(center.data()); + ::glVertex3dv(m_grabbers[m_hover_id].center.data()); + ::glEnd(); + + // draw grabber + m_grabbers[m_hover_id].render(true, box.max_size()); + render_grabber_extension((Axis)m_hover_id, box, false); + } +} + +void GLGizmoMove3D::on_render_for_picking(const Selection& selection) const +{ + ::glDisable(GL_DEPTH_TEST); + + const BoundingBoxf3& box = selection.get_bounding_box(); + render_grabbers_for_picking(box); + render_grabber_extension(X, box, true); + render_grabber_extension(Y, box, true); + render_grabber_extension(Z, box, true); +} + +void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) +{ +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + bool show_position = selection.is_single_full_instance(); + const Vec3d& position = selection.get_bounding_box().center(); + + Vec3d displacement = show_position ? position : m_displacement; + wxString label = show_position ? _(L("Position (mm)")) : _(L("Displacement (mm)")); + + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->input_vec3("", displacement, 100.0f, "%.2f"); + + m_imgui->end(); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +} + +double GLGizmoMove3D::calc_projection(const UpdateData& data) const +{ + double projection = 0.0; + + Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; + double len_starting_vec = starting_vec.norm(); + if (len_starting_vec != 0.0) + { + Vec3d mouse_dir = data.mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - m_starting_drag_position; + + // finds projection of the vector along the staring direction + projection = inters_vec.dot(starting_vec.normalized()); + } + + if (data.shift_down) + projection = m_snap_step * (double)std::round(projection / m_snap_step); + + return projection; +} + +void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const +{ + if (m_quadric == nullptr) + return; + + double size = m_dragging ? (double)m_grabbers[axis].get_dragging_half_size((float)box.max_size()) : (double)m_grabbers[axis].get_half_size((float)box.max_size()); + + float color[3]; + ::memcpy((void*)color, (const void*)m_grabbers[axis].color, 3 * sizeof(float)); + if (!picking && (m_hover_id != -1)) + { + color[0] = 1.0f - color[0]; + color[1] = 1.0f - color[1]; + color[2] = 1.0f - color[2]; + } + + if (!picking) + ::glEnable(GL_LIGHTING); + + ::glColor3fv(color); + ::glPushMatrix(); + ::glTranslated(m_grabbers[axis].center(0), m_grabbers[axis].center(1), m_grabbers[axis].center(2)); + if (axis == X) + ::glRotated(90.0, 0.0, 1.0, 0.0); + else if (axis == Y) + ::glRotated(-90.0, 1.0, 0.0, 0.0); + + ::glTranslated(0.0, 0.0, 2.0 * size); + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); + ::glPopMatrix(); + + if (!picking) + ::glDisable(GL_LIGHTING); +} + + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp new file mode 100644 index 000000000..ddab2b777 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -0,0 +1,57 @@ +#ifndef slic3r_GLGizmoMove_hpp_ +#define slic3r_GLGizmoMove_hpp_ + +#include "GLGizmoBase.hpp" + + +namespace Slic3r { +namespace GUI { + +class GLGizmoMove3D : public GLGizmoBase +{ + static const double Offset; + + Vec3d m_displacement; + + double m_snap_step; + + Vec3d m_starting_drag_position; + Vec3d m_starting_box_center; + Vec3d m_starting_box_bottom_center; + + GLUquadricObj* m_quadric; + +public: +#if ENABLE_SVG_ICONS + GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + virtual ~GLGizmoMove3D(); + + double get_snap_step(double step) const { return m_snap_step; } + void set_snap_step(double step) { m_snap_step = step; } + + const Vec3d& get_displacement() const { return m_displacement; } + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const; + virtual void on_start_dragging(const Selection& selection); + virtual void on_stop_dragging(); + virtual void on_update(const UpdateData& data, const Selection& selection); + virtual void on_render(const Selection& selection) const; + virtual void on_render_for_picking(const Selection& selection) const; + virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); + +private: + double calc_projection(const UpdateData& data) const; + void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const; +}; + + + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoMove_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp new file mode 100644 index 000000000..72b9ad18a --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -0,0 +1,503 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoRotate.hpp" + +#include + + +namespace Slic3r { +namespace GUI { + + +const float GLGizmoRotate::Offset = 5.0f; +const unsigned int GLGizmoRotate::CircleResolution = 64; +const unsigned int GLGizmoRotate::AngleResolution = 64; +const unsigned int GLGizmoRotate::ScaleStepsCount = 72; +const float GLGizmoRotate::ScaleStepRad = 2.0f * (float)PI / GLGizmoRotate::ScaleStepsCount; +const unsigned int GLGizmoRotate::ScaleLongEvery = 2; +const float GLGizmoRotate::ScaleLongTooth = 0.1f; // in percent of radius +const unsigned int GLGizmoRotate::SnapRegionsCount = 8; +const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius + +GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) +#if ENABLE_SVG_ICONS + : GLGizmoBase(parent, "", -1) +#else + : GLGizmoBase(parent, -1) +#endif // ENABLE_SVG_ICONS + , m_axis(axis) + , m_angle(0.0) + , m_quadric(nullptr) + , m_center(0.0, 0.0, 0.0) + , m_radius(0.0f) + , m_snap_coarse_in_radius(0.0f) + , m_snap_coarse_out_radius(0.0f) + , m_snap_fine_in_radius(0.0f) + , m_snap_fine_out_radius(0.0f) +{ + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other) +#if ENABLE_SVG_ICONS + : GLGizmoBase(other.m_parent, other.m_icon_filename, other.m_sprite_id) +#else + : GLGizmoBase(other.m_parent, other.m_sprite_id) +#endif // ENABLE_SVG_ICONS + , m_axis(other.m_axis) + , m_angle(other.m_angle) + , m_quadric(nullptr) + , m_center(other.m_center) + , m_radius(other.m_radius) + , m_snap_coarse_in_radius(other.m_snap_coarse_in_radius) + , m_snap_coarse_out_radius(other.m_snap_coarse_out_radius) + , m_snap_fine_in_radius(other.m_snap_fine_in_radius) + , m_snap_fine_out_radius(other.m_snap_fine_out_radius) +{ + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +GLGizmoRotate::~GLGizmoRotate() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); +} + +void GLGizmoRotate::set_angle(double angle) +{ + if (std::abs(angle - 2.0 * (double)PI) < EPSILON) + angle = 0.0; + + m_angle = angle; +} + +bool GLGizmoRotate::on_init() +{ + m_grabbers.push_back(Grabber()); + return true; +} + +void GLGizmoRotate::on_start_dragging(const Selection& selection) +{ + const BoundingBoxf3& box = selection.get_bounding_box(); + m_center = box.center(); + m_radius = Offset + box.radius(); + m_snap_coarse_in_radius = m_radius / 3.0f; + m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; + m_snap_fine_in_radius = m_radius; + m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; +} + +void GLGizmoRotate::on_update(const UpdateData& data, const Selection& selection) +{ + Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, selection)); + + Vec2d orig_dir = Vec2d::UnitX(); + Vec2d new_dir = mouse_pos.normalized(); + + double theta = ::acos(clamp(-1.0, 1.0, new_dir.dot(orig_dir))); + if (cross2(orig_dir, new_dir) < 0.0) + theta = 2.0 * (double)PI - theta; + + double len = mouse_pos.norm(); + + // snap to coarse snap region + if ((m_snap_coarse_in_radius <= len) && (len <= m_snap_coarse_out_radius)) + { + double step = 2.0 * (double)PI / (double)SnapRegionsCount; + theta = step * (double)std::round(theta / step); + } + else + { + // snap to fine snap region (scale) + if ((m_snap_fine_in_radius <= len) && (len <= m_snap_fine_out_radius)) + { + double step = 2.0 * (double)PI / (double)ScaleStepsCount; + theta = step * (double)std::round(theta / step); + } + } + + if (theta == 2.0 * (double)PI) + theta = 0.0; + + m_angle = theta; +} + +void GLGizmoRotate::on_render(const Selection& selection) const +{ + if (!m_grabbers[0].enabled) + return; + + const BoundingBoxf3& box = selection.get_bounding_box(); + + std::string axis; + switch (m_axis) + { + case X: { axis = "X"; break; } + case Y: { axis = "Y"; break; } + case Z: { axis = "Z"; break; } + } + + if (!m_dragging && (m_hover_id == 0)) + set_tooltip(axis); + else if (m_dragging) + set_tooltip(axis + ": " + format((float)Geometry::rad2deg(m_angle), 4) + "\u00B0"); + else + { + m_center = box.center(); + m_radius = Offset + box.radius(); + m_snap_coarse_in_radius = m_radius / 3.0f; + m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; + m_snap_fine_in_radius = m_radius; + m_snap_fine_out_radius = m_radius * (1.0f + ScaleLongTooth); + } + + ::glEnable(GL_DEPTH_TEST); + + ::glPushMatrix(); + transform_to_local(selection); + + ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); + ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color); + + render_circle(); + + if (m_hover_id != -1) + { + render_scale(); + render_snap_radii(); + render_reference_radius(); + } + + ::glColor3fv(m_highlight_color); + + if (m_hover_id != -1) + render_angle(); + + render_grabber(box); + render_grabber_extension(box, false); + + ::glPopMatrix(); +} + +void GLGizmoRotate::on_render_for_picking(const Selection& selection) const +{ + ::glDisable(GL_DEPTH_TEST); + + ::glPushMatrix(); + + transform_to_local(selection); + + const BoundingBoxf3& box = selection.get_bounding_box(); + render_grabbers_for_picking(box); + render_grabber_extension(box, true); + + ::glPopMatrix(); +} + +void GLGizmoRotate::render_circle() const +{ + ::glBegin(GL_LINE_LOOP); + for (unsigned int i = 0; i < ScaleStepsCount; ++i) + { + float angle = (float)i * ScaleStepRad; + float x = ::cos(angle) * m_radius; + float y = ::sin(angle) * m_radius; + float z = 0.0f; + ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); + } + ::glEnd(); +} + +void GLGizmoRotate::render_scale() const +{ + float out_radius_long = m_snap_fine_out_radius; + float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth); + + ::glBegin(GL_LINES); + for (unsigned int i = 0; i < ScaleStepsCount; ++i) + { + float angle = (float)i * ScaleStepRad; + float cosa = ::cos(angle); + float sina = ::sin(angle); + float in_x = cosa * m_radius; + float in_y = sina * m_radius; + float in_z = 0.0f; + float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short; + float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short; + float out_z = 0.0f; + ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z); + ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z); + } + ::glEnd(); +} + +void GLGizmoRotate::render_snap_radii() const +{ + float step = 2.0f * (float)PI / (float)SnapRegionsCount; + + float in_radius = m_radius / 3.0f; + float out_radius = 2.0f * in_radius; + + ::glBegin(GL_LINES); + for (unsigned int i = 0; i < SnapRegionsCount; ++i) + { + float angle = (float)i * step; + float cosa = ::cos(angle); + float sina = ::sin(angle); + float in_x = cosa * in_radius; + float in_y = sina * in_radius; + float in_z = 0.0f; + float out_x = cosa * out_radius; + float out_y = sina * out_radius; + float out_z = 0.0f; + ::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z); + ::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z); + } + ::glEnd(); +} + +void GLGizmoRotate::render_reference_radius() const +{ + ::glBegin(GL_LINES); + ::glVertex3f(0.0f, 0.0f, 0.0f); + ::glVertex3f((GLfloat)(m_radius * (1.0f + GrabberOffset)), 0.0f, 0.0f); + ::glEnd(); +} + +void GLGizmoRotate::render_angle() const +{ + float step_angle = (float)m_angle / AngleResolution; + float ex_radius = m_radius * (1.0f + GrabberOffset); + + ::glBegin(GL_LINE_STRIP); + for (unsigned int i = 0; i <= AngleResolution; ++i) + { + float angle = (float)i * step_angle; + float x = ::cos(angle) * ex_radius; + float y = ::sin(angle) * ex_radius; + float z = 0.0f; + ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); + } + ::glEnd(); +} + +void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const +{ + double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset); + m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0); + m_grabbers[0].angles(2) = m_angle; + + ::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color); + + ::glBegin(GL_LINES); + ::glVertex3f(0.0f, 0.0f, 0.0f); + ::glVertex3dv(m_grabbers[0].center.data()); + ::glEnd(); + + ::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 3 * sizeof(float)); + render_grabbers(box); +} + +void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) const +{ + if (m_quadric == nullptr) + return; + + double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size((float)box.max_size()) : (double)m_grabbers[0].get_half_size((float)box.max_size()); + + float color[3]; + ::memcpy((void*)color, (const void*)m_grabbers[0].color, 3 * sizeof(float)); + if (!picking && (m_hover_id != -1)) + { + color[0] = 1.0f - color[0]; + color[1] = 1.0f - color[1]; + color[2] = 1.0f - color[2]; + } + + if (!picking) + ::glEnable(GL_LIGHTING); + + ::glColor3fv(color); + ::glPushMatrix(); + ::glTranslated(m_grabbers[0].center(0), m_grabbers[0].center(1), m_grabbers[0].center(2)); + ::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0); + ::glRotated(90.0, 1.0, 0.0, 0.0); + ::glTranslated(0.0, 0.0, 2.0 * size); + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); + ::glPopMatrix(); + ::glPushMatrix(); + ::glTranslated(m_grabbers[0].center(0), m_grabbers[0].center(1), m_grabbers[0].center(2)); + ::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0); + ::glRotated(-90.0, 1.0, 0.0, 0.0); + ::glTranslated(0.0, 0.0, 2.0 * size); + ::gluQuadricOrientation(m_quadric, GLU_OUTSIDE); + ::gluCylinder(m_quadric, 0.75 * size, 0.0, 3.0 * size, 36, 1); + ::gluQuadricOrientation(m_quadric, GLU_INSIDE); + ::gluDisk(m_quadric, 0.0, 0.75 * size, 36, 1); + ::glPopMatrix(); + + if (!picking) + ::glDisable(GL_LIGHTING); +} + +void GLGizmoRotate::transform_to_local(const Selection& selection) const +{ + ::glTranslated(m_center(0), m_center(1), m_center(2)); + + if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) + { + Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + ::glMultMatrixd(orient_matrix.data()); + } + + switch (m_axis) + { + case X: + { + ::glRotatef(90.0f, 0.0f, 1.0f, 0.0f); + ::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f); + break; + } + case Y: + { + ::glRotatef(-90.0f, 0.0f, 0.0f, 1.0f); + ::glRotatef(-90.0f, 0.0f, 1.0f, 0.0f); + break; + } + default: + case Z: + { + // no rotation + break; + } + } +} + +Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const +{ + double half_pi = 0.5 * (double)PI; + + Transform3d m = Transform3d::Identity(); + + switch (m_axis) + { + case X: + { + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); + m.rotate(Eigen::AngleAxisd(-half_pi, Vec3d::UnitY())); + break; + } + case Y: + { + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitY())); + m.rotate(Eigen::AngleAxisd(half_pi, Vec3d::UnitZ())); + break; + } + default: + case Z: + { + // no rotation applied + break; + } + } + + if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) + m = m * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true).inverse(); + + m.translate(-m_center); + + return transform(mouse_ray, m).intersect_plane(0.0); +} + +#if ENABLE_SVG_ICONS +GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +#else +GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, sprite_id) +#endif // ENABLE_SVG_ICONS +{ + m_gizmos.emplace_back(parent, GLGizmoRotate::X); + m_gizmos.emplace_back(parent, GLGizmoRotate::Y); + m_gizmos.emplace_back(parent, GLGizmoRotate::Z); + + for (unsigned int i = 0; i < 3; ++i) + { + m_gizmos[i].set_group_id(i); + } +} + +bool GLGizmoRotate3D::on_init() +{ + for (GLGizmoRotate& g : m_gizmos) + { + if (!g.init()) + return false; + } + + for (unsigned int i = 0; i < 3; ++i) + { + m_gizmos[i].set_highlight_color(AXES_COLOR[i]); + } + + m_shortcut_key = WXK_CONTROL_R; + + return true; +} + +std::string GLGizmoRotate3D::on_get_name() const +{ + return L("Rotate [R]"); +} + +void GLGizmoRotate3D::on_start_dragging(const Selection& selection) +{ + if ((0 <= m_hover_id) && (m_hover_id < 3)) + m_gizmos[m_hover_id].start_dragging(selection); +} + +void GLGizmoRotate3D::on_stop_dragging() +{ + if ((0 <= m_hover_id) && (m_hover_id < 3)) + m_gizmos[m_hover_id].stop_dragging(); +} + +void GLGizmoRotate3D::on_render(const Selection& selection) const +{ + ::glClear(GL_DEPTH_BUFFER_BIT); + + if ((m_hover_id == -1) || (m_hover_id == 0)) + m_gizmos[X].render(selection); + + if ((m_hover_id == -1) || (m_hover_id == 1)) + m_gizmos[Y].render(selection); + + if ((m_hover_id == -1) || (m_hover_id == 2)) + m_gizmos[Z].render(selection); +} + +void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) +{ +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + Vec3d rotation(Geometry::rad2deg(m_gizmos[0].get_angle()), Geometry::rad2deg(m_gizmos[1].get_angle()), Geometry::rad2deg(m_gizmos[2].get_angle())); + wxString label = _(L("Rotation (deg)")); + + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->input_vec3("", rotation, 100.0f, "%.2f"); + m_imgui->end(); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +} + + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp new file mode 100644 index 000000000..f5946aa56 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -0,0 +1,142 @@ +#ifndef slic3r_GLGizmoRotate_hpp_ +#define slic3r_GLGizmoRotate_hpp_ + +#include "GLGizmoBase.hpp" + + +namespace Slic3r { +namespace GUI { + +class GLGizmoRotate : public GLGizmoBase +{ + static const float Offset; + static const unsigned int CircleResolution; + static const unsigned int AngleResolution; + static const unsigned int ScaleStepsCount; + static const float ScaleStepRad; + static const unsigned int ScaleLongEvery; + static const float ScaleLongTooth; + static const unsigned int SnapRegionsCount; + static const float GrabberOffset; + +public: + enum Axis : unsigned char + { + X, + Y, + Z + }; + +private: + Axis m_axis; + double m_angle; + + GLUquadricObj* m_quadric; + + mutable Vec3d m_center; + mutable float m_radius; + + mutable float m_snap_coarse_in_radius; + mutable float m_snap_coarse_out_radius; + mutable float m_snap_fine_in_radius; + mutable float m_snap_fine_out_radius; + +public: + GLGizmoRotate(GLCanvas3D& parent, Axis axis); + GLGizmoRotate(const GLGizmoRotate& other); + virtual ~GLGizmoRotate(); + + double get_angle() const { return m_angle; } + void set_angle(double angle); + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const { return ""; } + virtual void on_start_dragging(const Selection& selection); + virtual void on_update(const UpdateData& data, const Selection& selection); + virtual void on_render(const Selection& selection) const; + virtual void on_render_for_picking(const Selection& selection) const; + +private: + void render_circle() const; + void render_scale() const; + void render_snap_radii() const; + void render_reference_radius() const; + void render_angle() const; + void render_grabber(const BoundingBoxf3& box) const; + void render_grabber_extension(const BoundingBoxf3& box, bool picking) const; + + void transform_to_local(const Selection& selection) const; + // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate + Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const; +}; + +class GLGizmoRotate3D : public GLGizmoBase +{ + std::vector m_gizmos; + +public: +#if ENABLE_SVG_ICONS + GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + + Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } + void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const; + virtual void on_set_state() + { + for (GLGizmoRotate& g : m_gizmos) + { + g.set_state(m_state); + } + } + virtual void on_set_hover_id() + { + for (unsigned int i = 0; i < 3; ++i) + { + m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); + } + } + virtual bool on_is_activable(const Selection& selection) const { return !selection.is_wipe_tower(); } + virtual void on_enable_grabber(unsigned int id) + { + if ((0 <= id) && (id < 3)) + m_gizmos[id].enable_grabber(0); + } + virtual void on_disable_grabber(unsigned int id) + { + if ((0 <= id) && (id < 3)) + m_gizmos[id].disable_grabber(0); + } + virtual void on_start_dragging(const Selection& selection); + virtual void on_stop_dragging(); + virtual void on_update(const UpdateData& data, const Selection& selection) + { + for (GLGizmoRotate& g : m_gizmos) + { + g.update(data, selection); + } + } + virtual void on_render(const Selection& selection) const; + virtual void on_render_for_picking(const Selection& selection) const + { + for (const GLGizmoRotate& g : m_gizmos) + { + g.render_for_picking(selection); + } + } + + virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); +}; + + + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoRotate_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp new file mode 100644 index 000000000..43152b56c --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -0,0 +1,362 @@ + + +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoScale.hpp" + +#include + +namespace Slic3r { +namespace GUI { + + +const float GLGizmoScale3D::Offset = 5.0f; + +#if ENABLE_SVG_ICONS +GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +#else +GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, sprite_id) +#endif // ENABLE_SVG_ICONS + , m_scale(Vec3d::Ones()) + , m_snap_step(0.05) + , m_starting_scale(Vec3d::Ones()) +{ +} + +bool GLGizmoScale3D::on_init() +{ + for (int i = 0; i < 10; ++i) + { + m_grabbers.push_back(Grabber()); + } + + double half_pi = 0.5 * (double)PI; + + // x axis + m_grabbers[0].angles(1) = half_pi; + m_grabbers[1].angles(1) = half_pi; + + // y axis + m_grabbers[2].angles(0) = half_pi; + m_grabbers[3].angles(0) = half_pi; + + m_shortcut_key = WXK_CONTROL_S; + + return true; +} + +std::string GLGizmoScale3D::on_get_name() const +{ + return L("Scale [S]"); +} + +void GLGizmoScale3D::on_start_dragging(const Selection& selection) +{ + if (m_hover_id != -1) + { + m_starting_drag_position = m_grabbers[m_hover_id].center; + m_starting_box = selection.get_bounding_box(); + } +} + +void GLGizmoScale3D::on_update(const UpdateData& data, const Selection& selection) +{ + if ((m_hover_id == 0) || (m_hover_id == 1)) + do_scale_x(data); + else if ((m_hover_id == 2) || (m_hover_id == 3)) + do_scale_y(data); + else if ((m_hover_id == 4) || (m_hover_id == 5)) + do_scale_z(data); + else if (m_hover_id >= 6) + do_scale_uniform(data); +} + +void GLGizmoScale3D::on_render(const Selection& selection) const +{ + bool single_instance = selection.is_single_full_instance(); + bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); + bool single_selection = single_instance || single_volume; + + Vec3f scale = 100.0f * Vec3f::Ones(); + if (single_instance) + scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast(); + else if (single_volume) + scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast(); + + if ((single_selection && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging) + set_tooltip("X: " + format(scale(0), 4) + "%"); + else if (!m_grabbers[0].dragging && !m_grabbers[1].dragging && ((m_hover_id == 0) || (m_hover_id == 1))) + set_tooltip("X"); + else if ((single_selection && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging) + set_tooltip("Y: " + format(scale(1), 4) + "%"); + else if (!m_grabbers[2].dragging && !m_grabbers[3].dragging && ((m_hover_id == 2) || (m_hover_id == 3))) + set_tooltip("Y"); + else if ((single_selection && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging) + set_tooltip("Z: " + format(scale(2), 4) + "%"); + else if (!m_grabbers[4].dragging && !m_grabbers[5].dragging && ((m_hover_id == 4) || (m_hover_id == 5))) + set_tooltip("Z"); + else if ((single_selection && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) + || m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging) + { + std::string tooltip = "X: " + format(scale(0), 4) + "%\n"; + tooltip += "Y: " + format(scale(1), 4) + "%\n"; + tooltip += "Z: " + format(scale(2), 4) + "%"; + set_tooltip(tooltip); + } + else if (!m_grabbers[6].dragging && !m_grabbers[7].dragging && !m_grabbers[8].dragging && !m_grabbers[9].dragging && + ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) + set_tooltip("X/Y/Z"); + + ::glClear(GL_DEPTH_BUFFER_BIT); + ::glEnable(GL_DEPTH_TEST); + + BoundingBoxf3 box; + Transform3d transform = Transform3d::Identity(); + Vec3d angles = Vec3d::Zero(); + Transform3d offsets_transform = Transform3d::Identity(); + + Vec3d grabber_size = Vec3d::Zero(); + + if (single_instance) + { + // calculate bounding box in instance local reference system + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + for (unsigned int idx : idxs) + { + const GLVolume* vol = selection.get_volume(idx); + box.merge(vol->bounding_box.transformed(vol->get_volume_transformation().get_matrix())); + } + + // gets transform from first selected volume + const GLVolume* v = selection.get_volume(*idxs.begin()); + transform = v->get_instance_transformation().get_matrix(); + // gets angles from first selected volume + angles = v->get_instance_rotation(); + // consider rotation+mirror only components of the transform for offsets + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); + grabber_size = v->get_instance_transformation().get_matrix(true, true, false, true) * box.size(); + } + else if (single_volume) + { + const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + box = v->bounding_box; + transform = v->world_matrix(); + angles = Geometry::extract_euler_angles(transform); + // consider rotation+mirror only components of the transform for offsets + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); + grabber_size = v->get_volume_transformation().get_matrix(true, true, false, true) * box.size(); + } + else + { + box = selection.get_bounding_box(); + grabber_size = box.size(); + } + + m_box = box; + + const Vec3d& center = m_box.center(); + Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0); + Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); + Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); + + // x axis + m_grabbers[0].center = transform * Vec3d(m_box.min(0), center(1), center(2)) - offset_x; + m_grabbers[1].center = transform * Vec3d(m_box.max(0), center(1), center(2)) + offset_x; + ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); + + // y axis + m_grabbers[2].center = transform * Vec3d(center(0), m_box.min(1), center(2)) - offset_y; + m_grabbers[3].center = transform * Vec3d(center(0), m_box.max(1), center(2)) + offset_y; + ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[3].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); + + // z axis + m_grabbers[4].center = transform * Vec3d(center(0), center(1), m_box.min(2)) - offset_z; + m_grabbers[5].center = transform * Vec3d(center(0), center(1), m_box.max(2)) + offset_z; + ::memcpy((void*)m_grabbers[4].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[5].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); + + // uniform + m_grabbers[6].center = transform * Vec3d(m_box.min(0), m_box.min(1), center(2)) - offset_x - offset_y; + m_grabbers[7].center = transform * Vec3d(m_box.max(0), m_box.min(1), center(2)) + offset_x - offset_y; + m_grabbers[8].center = transform * Vec3d(m_box.max(0), m_box.max(1), center(2)) + offset_x + offset_y; + m_grabbers[9].center = transform * Vec3d(m_box.min(0), m_box.max(1), center(2)) - offset_x + offset_y; + for (int i = 6; i < 10; ++i) + { + ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 3 * sizeof(float)); + } + + // sets grabbers orientation + for (int i = 0; i < 10; ++i) + { + m_grabbers[i].angles = angles; + } + + ::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f); + + float grabber_mean_size = (float)(grabber_size(0) + grabber_size(1) + grabber_size(2)) / 3.0f; + + if (m_hover_id == -1) + { + // draw connections + if (m_grabbers[0].enabled && m_grabbers[1].enabled) + { + ::glColor3fv(m_grabbers[0].color); + render_grabbers_connection(0, 1); + } + if (m_grabbers[2].enabled && m_grabbers[3].enabled) + { + ::glColor3fv(m_grabbers[2].color); + render_grabbers_connection(2, 3); + } + if (m_grabbers[4].enabled && m_grabbers[5].enabled) + { + ::glColor3fv(m_grabbers[4].color); + render_grabbers_connection(4, 5); + } + ::glColor3fv(m_base_color); + render_grabbers_connection(6, 7); + render_grabbers_connection(7, 8); + render_grabbers_connection(8, 9); + render_grabbers_connection(9, 6); + // draw grabbers + render_grabbers(grabber_mean_size); + } + else if ((m_hover_id == 0) || (m_hover_id == 1)) + { + // draw connection + ::glColor3fv(m_grabbers[0].color); + render_grabbers_connection(0, 1); + // draw grabbers + m_grabbers[0].render(true, grabber_mean_size); + m_grabbers[1].render(true, grabber_mean_size); + } + else if ((m_hover_id == 2) || (m_hover_id == 3)) + { + // draw connection + ::glColor3fv(m_grabbers[2].color); + render_grabbers_connection(2, 3); + // draw grabbers + m_grabbers[2].render(true, grabber_mean_size); + m_grabbers[3].render(true, grabber_mean_size); + } + else if ((m_hover_id == 4) || (m_hover_id == 5)) + { + // draw connection + ::glColor3fv(m_grabbers[4].color); + render_grabbers_connection(4, 5); + // draw grabbers + m_grabbers[4].render(true, grabber_mean_size); + m_grabbers[5].render(true, grabber_mean_size); + } + else if (m_hover_id >= 6) + { + // draw connection + ::glColor3fv(m_drag_color); + render_grabbers_connection(6, 7); + render_grabbers_connection(7, 8); + render_grabbers_connection(8, 9); + render_grabbers_connection(9, 6); + // draw grabbers + for (int i = 6; i < 10; ++i) + { + m_grabbers[i].render(true, grabber_mean_size); + } + } +} + +void GLGizmoScale3D::on_render_for_picking(const Selection& selection) const +{ + ::glDisable(GL_DEPTH_TEST); + + render_grabbers_for_picking(selection.get_bounding_box()); +} + +void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) +{ +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + bool single_instance = selection.is_single_full_instance(); + wxString label = _(L("Scale (%)")); + + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->input_vec3("", m_scale * 100.f, 100.0f, "%.2f"); + m_imgui->end(); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +} + +void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const +{ + unsigned int grabbers_count = (unsigned int)m_grabbers.size(); + if ((id_1 < grabbers_count) && (id_2 < grabbers_count)) + { + ::glBegin(GL_LINES); + ::glVertex3dv(m_grabbers[id_1].center.data()); + ::glVertex3dv(m_grabbers[id_2].center.data()); + ::glEnd(); + } +} + +void GLGizmoScale3D::do_scale_x(const UpdateData& data) +{ + double ratio = calc_ratio(data); + if (ratio > 0.0) + m_scale(0) = m_starting_scale(0) * ratio; +} + +void GLGizmoScale3D::do_scale_y(const UpdateData& data) +{ + double ratio = calc_ratio(data); + if (ratio > 0.0) + m_scale(1) = m_starting_scale(1) * ratio; +} + +void GLGizmoScale3D::do_scale_z(const UpdateData& data) +{ + double ratio = calc_ratio(data); + if (ratio > 0.0) + m_scale(2) = m_starting_scale(2) * ratio; +} + +void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) +{ + double ratio = calc_ratio(data); + if (ratio > 0.0) + m_scale = m_starting_scale * ratio; +} + +double GLGizmoScale3D::calc_ratio(const UpdateData& data) const +{ + double ratio = 0.0; + + // vector from the center to the starting position + Vec3d starting_vec = m_starting_drag_position - m_starting_box.center(); + double len_starting_vec = starting_vec.norm(); + if (len_starting_vec != 0.0) + { + Vec3d mouse_dir = data.mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + Vec3d inters_vec = inters - m_starting_drag_position; + + // finds projection of the vector along the staring direction + double proj = inters_vec.dot(starting_vec.normalized()); + + ratio = (len_starting_vec + proj) / len_starting_vec; + } + + if (data.shift_down) + ratio = m_snap_step * (double)std::round(ratio / m_snap_step); + + return ratio; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp new file mode 100644 index 000000000..6e14a361e --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -0,0 +1,62 @@ +#ifndef slic3r_GLGizmoScale_hpp_ +#define slic3r_GLGizmoScale_hpp_ + +#include "GLGizmoBase.hpp" + + +namespace Slic3r { +namespace GUI { + +class GLGizmoScale3D : public GLGizmoBase +{ + static const float Offset; + + mutable BoundingBoxf3 m_box; + + Vec3d m_scale; + + double m_snap_step; + + Vec3d m_starting_scale; + Vec3d m_starting_drag_position; + BoundingBoxf3 m_starting_box; + +public: +#if ENABLE_SVG_ICONS + GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + + double get_snap_step(double step) const { return m_snap_step; } + void set_snap_step(double step) { m_snap_step = step; } + + const Vec3d& get_scale() const { return m_scale; } + void set_scale(const Vec3d& scale) { m_starting_scale = scale; m_scale = scale; } + +protected: + virtual bool on_init(); + virtual std::string on_get_name() const; + virtual bool on_is_activable(const Selection& selection) const { return !selection.is_wipe_tower(); } + virtual void on_start_dragging(const Selection& selection); + virtual void on_update(const UpdateData& data, const Selection& selection); + virtual void on_render(const Selection& selection) const; + virtual void on_render_for_picking(const Selection& selection) const; + virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); + +private: + void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const; + + void do_scale_x(const UpdateData& data); + void do_scale_y(const UpdateData& data); + void do_scale_z(const UpdateData& data); + void do_scale_uniform(const UpdateData& data); + + double calc_ratio(const UpdateData& data) const; +}; + + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoScale_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp new file mode 100644 index 000000000..31f6b0278 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -0,0 +1,877 @@ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoSlaSupports.hpp" + +#include + +#include + +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI_ObjectSettings.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/PresetBundle.hpp" + + +namespace Slic3r { +namespace GUI { + +#if ENABLE_SVG_ICONS +GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +#else +GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id) + : GLGizmoBase(parent, sprite_id) +#endif // ENABLE_SVG_ICONS + , m_starting_center(Vec3d::Zero()), m_quadric(nullptr) +{ + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + // using GLU_FILL does not work when the instance's transformation + // contains mirroring (normals are reverted) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +} + +GLGizmoSlaSupports::~GLGizmoSlaSupports() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); +} + +bool GLGizmoSlaSupports::on_init() +{ + m_shortcut_key = WXK_CONTROL_L; + return true; +} + +void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const Selection& selection) +{ + m_starting_center = Vec3d::Zero(); + m_old_model_object = m_model_object; + m_model_object = model_object; + if (selection.is_empty()) + m_old_instance_id = -1; + + m_active_instance = selection.get_instance_idx(); + + if (model_object && selection.is_from_single_instance()) + { + if (is_mesh_update_necessary()) { + update_mesh(); + editing_mode_reload_cache(); + } + + if (m_model_object != m_old_model_object) + m_editing_mode = false; + + if (m_editing_mode_cache.empty() && m_model_object->sla_points_status != sla::PointsStatus::UserModified) + get_data_from_backend(); + + if (m_state == On) { + m_parent.toggle_model_objects_visibility(false); + m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); + } + } +} + +void GLGizmoSlaSupports::on_render(const Selection& selection) const +{ + ::glEnable(GL_BLEND); + ::glEnable(GL_DEPTH_TEST); + + render_points(selection, false); + render_selection_rectangle(); + + ::glDisable(GL_BLEND); +} + +void GLGizmoSlaSupports::render_selection_rectangle() const +{ + if (!m_selection_rectangle_active) + return; + + ::glLineWidth(1.5f); + float render_color[3] = {1.f, 0.f, 0.f}; + ::glColor3fv(render_color); + + ::glPushAttrib(GL_TRANSFORM_BIT); // remember current MatrixMode + + ::glMatrixMode(GL_MODELVIEW); // cache modelview matrix and set to identity + ::glPushMatrix(); + ::glLoadIdentity(); + + ::glMatrixMode(GL_PROJECTION); // cache projection matrix and set to identity + ::glPushMatrix(); + ::glLoadIdentity(); + + ::glOrtho(0.f, m_canvas_width, m_canvas_height, 0.f, -1.f, 1.f); // set projection matrix so that world coords = window coords + + // render the selection rectangle (window coordinates): + ::glPushAttrib(GL_ENABLE_BIT); + ::glLineStipple(4, 0xAAAA); + ::glEnable(GL_LINE_STIPPLE); + + ::glBegin(GL_LINE_LOOP); + ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); + ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_start_corner(1), (GLfloat)0.5f); + ::glVertex3f((GLfloat)m_selection_rectangle_end_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); + ::glVertex3f((GLfloat)m_selection_rectangle_start_corner(0), (GLfloat)m_selection_rectangle_end_corner(1), (GLfloat)0.5f); + ::glEnd(); + ::glPopAttrib(); + + ::glPopMatrix(); // restore former projection matrix + ::glMatrixMode(GL_MODELVIEW); + ::glPopMatrix(); // restore former modelview matrix + ::glPopAttrib(); // restore former MatrixMode +} + +void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const +{ + ::glEnable(GL_DEPTH_TEST); + + render_points(selection, true); +} + +void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const +{ + if (m_quadric == nullptr || !selection.is_from_single_instance()) + return; + + if (!picking) + ::glEnable(GL_LIGHTING); + + const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + double z_shift = vol->get_sla_shift_z(); + const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); + const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix(); + + ::glPushMatrix(); + ::glTranslated(0.0, 0.0, z_shift); + ::glMultMatrixd(instance_matrix.data()); + + float render_color[3]; + for (int i = 0; i < (int)m_editing_mode_cache.size(); ++i) + { + const sla::SupportPoint& support_point = m_editing_mode_cache[i].support_point; + const bool& point_selected = m_editing_mode_cache[i].selected; + + // First decide about the color of the point. + if (picking) { + std::array color = picking_color_component(i); + render_color[0] = color[0]; + render_color[1] = color[1]; + render_color[2] = color[2]; + } + else { + if ((m_hover_id == i && m_editing_mode)) { // ignore hover state unless editing mode is active + render_color[0] = 0.f; + render_color[1] = 1.0f; + render_color[2] = 1.0f; + } + else { // neigher hover nor picking + bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].support_point.is_new_island; + if (m_editing_mode) { + render_color[0] = point_selected ? 1.0f : (supports_new_island ? 0.3f : 0.7f); + render_color[1] = point_selected ? 0.3f : (supports_new_island ? 0.3f : 0.7f); + render_color[2] = point_selected ? 0.3f : (supports_new_island ? 1.0f : 0.7f); + } + else + for (unsigned char i=0; i<3; ++i) render_color[i] = 0.5f; + } + } + ::glColor3fv(render_color); + float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f}; + ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); + + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. + ::glPushMatrix(); + ::glTranslated(support_point.pos(0), support_point.pos(1), support_point.pos(2)); + ::glMultMatrixd(instance_scaling_matrix_inverse.data()); + + // Matrices set, we can render the point mark now. + // If in editing mode, we'll also render a cone pointing to the sphere. + if (m_editing_mode) { + if (m_editing_mode_cache[i].normal == Vec3f::Zero()) + update_cache_entry_normal(i); // in case the normal is not yet cached, find and cache it + + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_mode_cache[i].normal.cast()); + Eigen::AngleAxisd aa(q); + ::glRotated(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)); + + const float cone_radius = 0.25f; // mm + const float cone_height = 0.75f; + ::glPushMatrix(); + ::glTranslatef(0.f, 0.f, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale); + ::gluCylinder(m_quadric, 0.f, cone_radius, cone_height, 24, 1); + ::glTranslatef(0.f, 0.f, cone_height); + ::gluDisk(m_quadric, 0.0, cone_radius, 24, 1); + ::glPopMatrix(); + } + ::gluSphere(m_quadric, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale, 24, 12); + ::glPopMatrix(); + } + + { + // Reset emissive component to zero (the default value) + float render_color_emissive[4] = { 0.f, 0.f, 0.f, 1.f }; + ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); + } + + if (!picking) + ::glDisable(GL_LIGHTING); + + ::glPopMatrix(); +} + +bool GLGizmoSlaSupports::is_mesh_update_necessary() const +{ + return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) + && ((m_model_object != m_old_model_object) || m_V.size()==0); + + //if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix)) + // return false; +} + +void GLGizmoSlaSupports::update_mesh() +{ + wxBusyCursor wait; + Eigen::MatrixXf& V = m_V; + Eigen::MatrixXi& F = m_F; + // Composite mesh of all instances in the world coordinate system. + // This mesh does not account for the possible Z up SLA offset. + TriangleMesh mesh = m_model_object->raw_mesh(); + const stl_file& stl = mesh.stl; + V.resize(3 * stl.stats.number_of_facets, 3); + F.resize(stl.stats.number_of_facets, 3); + for (unsigned int i=0; ivertex[0](0); V(3*i+0, 1) = facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2); + V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) = facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2); + V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) = facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2); + F(i, 0) = 3*i+0; + F(i, 1) = 3*i+1; + F(i, 2) = 3*i+2; + } + + m_AABB = igl::AABB(); + m_AABB.init(m_V, m_F); +} + +std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) +{ + // if the gizmo doesn't have the V, F structures for igl, calculate them first: + if (m_V.size() == 0) + update_mesh(); + + Eigen::Matrix viewport; + ::glGetIntegerv(GL_VIEWPORT, viewport.data()); + Eigen::Matrix modelview_matrix; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); + Eigen::Matrix projection_matrix; + ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix.data()); + + Vec3d point1; + Vec3d point2; + ::gluUnProject(mouse_pos(0), viewport(3)-mouse_pos(1), 0.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point1(0), &point1(1), &point1(2)); + ::gluUnProject(mouse_pos(0), viewport(3)-mouse_pos(1), 1.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point2(0), &point2(1), &point2(2)); + + igl::Hit hit; + + const Selection& selection = m_parent.get_selection(); + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + double z_offset = volume->get_sla_shift_z(); + + point1(2) -= z_offset; + point2(2) -= z_offset; + + Transform3d inv = volume->get_instance_transformation().get_matrix().inverse(); + + point1 = inv * point1; + point2 = inv * point2; + + if (!m_AABB.intersect_ray(m_V, m_F, point1.cast(), (point2-point1).cast(), hit)) + throw std::invalid_argument("unproject_on_mesh(): No intersection found."); + + int fid = hit.id; // facet id + Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit + Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); + Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); + + // Calculate and return both the point and the facet normal. + return std::make_pair( + bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)), + a.cross(b) + ); +} + +// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. +// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is +// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo +// concludes that the event was not intended for it, it should return false. +bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down) +{ + if (m_editing_mode) { + + // left down - show the selection rectangle: + if (action == SLAGizmoEventType::LeftDown && shift_down) { + if (m_hover_id == -1) { + m_selection_rectangle_active = true; + m_selection_rectangle_start_corner = mouse_position; + m_selection_rectangle_end_corner = mouse_position; + m_canvas_width = m_parent.get_canvas_size().get_width(); + m_canvas_height = m_parent.get_canvas_size().get_height(); + } + else + select_point(m_hover_id); + + return true; + } + + // left down without selection rectangle - place point on the mesh: + if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle_active && !shift_down) { + // If any point is in hover state, this should initiate its move - return control back to GLCanvas: + if (m_hover_id != -1) + return false; + + // If there is some selection, don't add new point and deselect everything instead. + if (m_selection_empty) { + try { + std::pair pos_and_normal = unproject_on_mesh(mouse_position); // don't create anything if this throws + m_editing_mode_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); + m_unsaved_changes = true; + m_parent.set_as_dirty(); + m_wait_for_up_event = true; + } + catch (...) { // not clicked on object + return false; + } + } + else + select_point(NoPoints); + + return true; + } + + // left up with selection rectangle - select points inside the rectangle: + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp) && m_selection_rectangle_active) { + const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix(); + GLint viewport[4]; + ::glGetIntegerv(GL_VIEWPORT, viewport); + GLdouble modelview_matrix[16]; + ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix); + GLdouble projection_matrix[16]; + ::glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix); + + const Selection& selection = m_parent.get_selection(); + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + double z_offset = volume->get_sla_shift_z(); + + // bounding box created from the rectangle corners - will take care of order of the corners + BoundingBox rectangle(Points{Point(m_selection_rectangle_start_corner.cast()), Point(m_selection_rectangle_end_corner.cast())}); + + const Transform3d& instance_matrix_no_translation = volume->get_instance_transformation().get_matrix(true); + // we'll recover current look direction from the modelview matrix (in world coords)... + Vec3f direction_to_camera(modelview_matrix[2], modelview_matrix[6], modelview_matrix[10]); + // ...and transform it to model coords. + direction_to_camera = (instance_matrix_no_translation.inverse().cast() * direction_to_camera).normalized().eval(); + + // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh: + for (unsigned int i=0; i() * support_point.pos; + pos(2) += z_offset; + GLdouble out_x, out_y, out_z; + ::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), modelview_matrix, projection_matrix, viewport, &out_x, &out_y, &out_z); + out_y = m_canvas_height - out_y; + + if (rectangle.contains(Point(out_x, out_y))) { + bool is_obscured = false; + // Cast a ray in the direction of the camera and look for intersection with the mesh: + std::vector hits; + // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. + if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera * (support_point.head_front_radius + EPSILON), direction_to_camera, hits)) + // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. + // Also, the threshold is in mesh coordinates, not in actual dimensions. + if (hits.size() > 1 || hits.front().t > 0.001f) + is_obscured = true; + + if (!is_obscured) + select_point(i); + } + } + m_selection_rectangle_active = false; + return true; + } + + // left up with no selection rectangle + if (action == SLAGizmoEventType::LeftUp) { + if (m_wait_for_up_event) { + m_wait_for_up_event = false; + return true; + } + } + + // dragging the selection rectangle: + if (action == SLAGizmoEventType::Dragging) { + if (m_wait_for_up_event) + return true; // point has been placed and the button not released yet + // this prevents GLCanvas from starting scene rotation + + if (m_selection_rectangle_active) { + m_selection_rectangle_end_corner = mouse_position; + return true; + } + + return false; + } + + if (action == SLAGizmoEventType::Delete) { + // delete key pressed + delete_selected_points(); + return true; + } + + if (action == SLAGizmoEventType::ApplyChanges) { + editing_mode_apply_changes(); + return true; + } + + if (action == SLAGizmoEventType::DiscardChanges) { + editing_mode_discard_changes(); + return true; + } + + if (action == SLAGizmoEventType::RightDown) { + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + delete_selected_points(); + return true; + } + return false; + } + + if (action == SLAGizmoEventType::SelectAll) { + select_point(AllPoints); + return true; + } + } + + if (!m_editing_mode) { + if (action == SLAGizmoEventType::AutomaticGeneration) { + auto_generate(); + return true; + } + + if (action == SLAGizmoEventType::ManualEditing) { + switch_to_editing_mode(); + return true; + } + } + + return false; +} + +void GLGizmoSlaSupports::delete_selected_points(bool force) +{ + for (unsigned int idx=0; idxreslice_SLA_supports(*m_model_object); + } + + select_point(NoPoints); + + //m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); +} + +void GLGizmoSlaSupports::on_update(const UpdateData& data, const Selection& selection) +{ + if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) { + std::pair pos_and_normal; + try { + pos_and_normal = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); + } + catch (...) { return; } + m_editing_mode_cache[m_hover_id].support_point.pos = pos_and_normal.first; + m_editing_mode_cache[m_hover_id].support_point.is_new_island = false; + m_editing_mode_cache[m_hover_id].normal = pos_and_normal.second; + m_unsaved_changes = true; + // Do not update immediately, wait until the mouse is released. + // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } +} + +std::vector GLGizmoSlaSupports::get_config_options(const std::vector& keys) const +{ + std::vector out; + + if (!m_model_object) + return out; + + const DynamicPrintConfig& object_cfg = m_model_object->config; + const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + std::unique_ptr default_cfg = nullptr; + + for (const std::string& key : keys) { + if (object_cfg.has(key)) + out.push_back(object_cfg.option(key)); + else + if (print_cfg.has(key)) + out.push_back(print_cfg.option(key)); + else { // we must get it from defaults + if (default_cfg == nullptr) + default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); + out.push_back(default_cfg->option(key)); + } + } + + return out; +} + + +void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const +{ + int idx = 0; + Eigen::Matrix pp = m_editing_mode_cache[i].support_point.pos; + Eigen::Matrix cc; + m_AABB.squared_distance(m_V, m_F, pp, idx, cc); + Vec3f a = (m_V.row(m_F(idx, 1)) - m_V.row(m_F(idx, 0))); + Vec3f b = (m_V.row(m_F(idx, 2)) - m_V.row(m_F(idx, 0))); + m_editing_mode_cache[i].normal = a.cross(b); +} + + + +void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) +{ + if (!m_model_object) + return; + + bool first_run = true; // This is a hack to redraw the button when all points are removed, + // so it is not delayed until the background process finishes. +RENDER_AGAIN: + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + + const float scaling = m_imgui->get_style_scaling(); + const ImVec2 window_size(285.f * scaling, 300.f * scaling); + ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); + ImGui::SetNextWindowSize(ImVec2(window_size)); + + m_imgui->set_next_window_bg_alpha(0.5f); + m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + ImGui::PushItemWidth(100.0f); + + bool force_refresh = false; + bool remove_selected = false; + bool remove_all = false; + + if (m_editing_mode) { + m_imgui->text(_(L("Left mouse click - add point"))); + m_imgui->text(_(L("Right mouse click - remove point"))); + m_imgui->text(_(L("Shift + Left (+ drag) - select point(s)"))); + m_imgui->text(" "); // vertical gap + + float diameter_upper_cap = static_cast(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value; + if (m_new_point_head_diameter > diameter_upper_cap) + m_new_point_head_diameter = diameter_upper_cap; + + m_imgui->text(_(L("Head diameter: "))); + ImGui::SameLine(); + if (ImGui::SliderFloat("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f")) { + // value was changed + for (auto& cache_entry : m_editing_mode_cache) + if (cache_entry.selected) { + cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; + m_unsaved_changes = true; + } + } + + bool changed = m_lock_unique_islands; + m_imgui->checkbox(_(L("Lock supports under new islands")), m_lock_unique_islands); + force_refresh |= changed != m_lock_unique_islands; + + m_imgui->disabled_begin(m_selection_empty); + remove_selected = m_imgui->button(_(L("Remove selected points"))); + m_imgui->disabled_end(); + + m_imgui->disabled_begin(m_editing_mode_cache.empty()); + remove_all = m_imgui->button(_(L("Remove all points"))); + m_imgui->disabled_end(); + + m_imgui->text(" "); // vertical gap + + if (m_imgui->button(_(L("Apply changes")))) { + editing_mode_apply_changes(); + force_refresh = true; + } + ImGui::SameLine(); + bool discard_changes = m_imgui->button(_(L("Discard changes"))); + if (discard_changes) { + editing_mode_discard_changes(); + force_refresh = true; + } + } + else { // not in editing mode: + ImGui::PushItemWidth(100.0f); + m_imgui->text(_(L("Minimal points distance: "))); + ImGui::SameLine(); + + std::vector opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); + float density = static_cast(opts[0])->value; + float minimal_point_distance = static_cast(opts[1])->value; + + bool value_changed = ImGui::SliderFloat("", &minimal_point_distance, 0.f, 20.f, "%.f mm"); + if (value_changed) + m_model_object->config.opt("support_points_minimal_distance", true)->value = minimal_point_distance; + + m_imgui->text(_(L("Support points density: "))); + ImGui::SameLine(); + if (ImGui::SliderFloat(" ", &density, 0.f, 200.f, "%.f %%")) { + value_changed = true; + m_model_object->config.opt("support_points_density_relative", true)->value = (int)density; + } + + if (value_changed) { // Update side panel + wxTheApp->CallAfter([]() { + wxGetApp().obj_settings()->UpdateAndShow(true); + wxGetApp().obj_list()->update_settings_items(); + }); + } + + bool generate = m_imgui->button(_(L("Auto-generate points [A]"))); + + if (generate) + auto_generate(); + + m_imgui->text(""); + if (m_imgui->button(_(L("Manual editing [M]")))) + switch_to_editing_mode(); + + m_imgui->disabled_begin(m_editing_mode_cache.empty()); + remove_all = m_imgui->button(_(L("Remove all points"))); + m_imgui->disabled_end(); + + m_imgui->text(""); + + m_imgui->text(m_model_object->sla_points_status == sla::PointsStatus::None ? "No points (will be autogenerated)" : + (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? "Autogenerated points (no modifications)" : + (m_model_object->sla_points_status == sla::PointsStatus::UserModified ? "User-modified points" : + (m_model_object->sla_points_status == sla::PointsStatus::Generating ? "Generation in progress..." : "UNKNOWN STATUS")))); + } + + m_imgui->end(); + + if (m_editing_mode != m_old_editing_state) { // user toggled between editing/non-editing mode + m_parent.toggle_sla_auxiliaries_visibility(!m_editing_mode, m_model_object, m_active_instance); + force_refresh = true; + } + m_old_editing_state = m_editing_mode; + + if (remove_selected || remove_all) { + force_refresh = false; + m_parent.set_as_dirty(); + if (remove_all) + select_point(AllPoints); + delete_selected_points(remove_all); + if (remove_all && !m_editing_mode) + editing_mode_apply_changes(); + if (first_run) { + first_run = false; + goto RENDER_AGAIN; + } + } + + if (force_refresh) + m_parent.set_as_dirty(); +} + +bool GLGizmoSlaSupports::on_is_activable(const Selection& selection) const +{ + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA + || !selection.is_from_single_instance()) + return false; + + // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. + const Selection::IndicesList& list = selection.get_volume_idxs(); + for (const auto& idx : list) + if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) + return false; + + return true; +} + +bool GLGizmoSlaSupports::on_is_selectable() const +{ + return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); +} + +std::string GLGizmoSlaSupports::on_get_name() const +{ + return L("SLA Support Points [L]"); +} + +void GLGizmoSlaSupports::on_set_state() +{ + if (m_state == On && m_old_state != On) { // the gizmo was just turned on + if (is_mesh_update_necessary()) + update_mesh(); + + // we'll now reload support points: + if (m_model_object) + editing_mode_reload_cache(); + + m_parent.toggle_model_objects_visibility(false); + if (m_model_object) + m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); + + // Set default head diameter from config. + const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; + } + if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off + if (m_model_object) { + if (m_unsaved_changes) { + wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L("Do you want to save your manually edited support points ?\n")), + _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) + editing_mode_apply_changes(); + else + editing_mode_discard_changes(); + } + } + + m_parent.toggle_model_objects_visibility(true); + m_editing_mode = false; // so it is not active next time the gizmo opens + m_editing_mode_cache.clear(); + } + m_old_state = m_state; +} + + + +void GLGizmoSlaSupports::on_start_dragging(const Selection& selection) +{ + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + } +} + + + +void GLGizmoSlaSupports::select_point(int i) +{ + if (i == AllPoints || i == NoPoints) { + for (auto& point_and_selection : m_editing_mode_cache) + point_and_selection.selected = ( i == AllPoints ); + m_selection_empty = (i == NoPoints); + + if (i == AllPoints) + m_new_point_head_diameter = m_editing_mode_cache[0].support_point.head_front_radius * 2.f; + } + else { + m_editing_mode_cache[i].selected = true; + m_selection_empty = false; + m_new_point_head_diameter = m_editing_mode_cache[i].support_point.head_front_radius * 2.f; + } +} + + + +void GLGizmoSlaSupports::editing_mode_discard_changes() +{ + m_editing_mode_cache.clear(); + for (const sla::SupportPoint& point : m_model_object->sla_support_points) + m_editing_mode_cache.emplace_back(point, false); + m_editing_mode = false; + m_unsaved_changes = false; +} + + + +void GLGizmoSlaSupports::editing_mode_apply_changes() +{ + // If there are no changes, don't touch the front-end. The data in the cache could have been + // taken from the backend and copying them to ModelObject would needlessly invalidate them. + if (m_unsaved_changes) { + m_model_object->sla_points_status = sla::PointsStatus::UserModified; + m_model_object->sla_support_points.clear(); + for (const CacheEntry& cache_entry : m_editing_mode_cache) + m_model_object->sla_support_points.push_back(cache_entry.support_point); + + // Recalculate support structures once the editing mode is left. + // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + wxGetApp().plater()->reslice_SLA_supports(*m_model_object); + } + m_editing_mode = false; + m_unsaved_changes = false; +} + + + +void GLGizmoSlaSupports::editing_mode_reload_cache() +{ + m_editing_mode_cache.clear(); + for (const sla::SupportPoint& point : m_model_object->sla_support_points) + m_editing_mode_cache.emplace_back(point, false); + + m_unsaved_changes = false; +} + + + +void GLGizmoSlaSupports::get_data_from_backend() +{ + for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { + if (po->model_object()->id() == m_model_object->id() && po->is_step_done(slaposSupportPoints)) { + m_editing_mode_cache.clear(); + const std::vector& points = po->get_support_points(); + auto mat = po->trafo().inverse().cast(); + for (unsigned int i=0; isla_points_status != sla::PointsStatus::UserModified) + m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated; + + break; + } + } + m_unsaved_changes = false; + + // We don't copy the data into ModelObject, as this would stop the background processing. +} + + + +void GLGizmoSlaSupports::auto_generate() +{ + wxMessageDialog dlg(GUI::wxGetApp().plater(), _(L( + "Autogeneration will erase all manually edited points.\n\n" + "Are you sure you want to do it?\n" + )), _(L("Warning")), wxICON_WARNING | wxYES | wxNO); + + if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_editing_mode_cache.empty() || dlg.ShowModal() == wxID_YES) { + m_model_object->sla_support_points.clear(); + m_model_object->sla_points_status = sla::PointsStatus::Generating; + m_editing_mode_cache.clear(); + wxGetApp().plater()->reslice_SLA_supports(*m_model_object); + } +} + + + +void GLGizmoSlaSupports::switch_to_editing_mode() +{ + if (m_model_object->sla_points_status != sla::PointsStatus::AutoGenerated) + editing_mode_reload_cache(); + m_unsaved_changes = false; + m_editing_mode = true; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp new file mode 100644 index 000000000..993ab06bf --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -0,0 +1,127 @@ +#ifndef slic3r_GLGizmoSlaSupports_hpp_ +#define slic3r_GLGizmoSlaSupports_hpp_ + +#include "GLGizmoBase.hpp" + +// There is an L function in igl that would be overridden by our localization macro - let's undefine it... +#undef L +#include +#include "slic3r/GUI/I18N.hpp" // ...and redefine again when we are done with the igl code + +#include "libslic3r/SLA/SLACommon.hpp" +#include "libslic3r/SLAPrint.hpp" + + +namespace Slic3r { +namespace GUI { + + +class GLGizmoSlaSupports : public GLGizmoBase +{ +private: + ModelObject* m_model_object = nullptr; + ModelObject* m_old_model_object = nullptr; + int m_active_instance = -1; + int m_old_instance_id = -1; + std::pair unproject_on_mesh(const Vec2d& mouse_pos); + + const float RenderPointScale = 1.f; + + GLUquadricObj* m_quadric; + Eigen::MatrixXf m_V; // vertices + Eigen::MatrixXi m_F; // facets indices + igl::AABB m_AABB; + + struct SourceDataSummary { + Geometry::Transformation transformation; + }; + + class CacheEntry { + public: + CacheEntry(const sla::SupportPoint& point, bool sel, const Vec3f& norm = Vec3f::Zero()) : + support_point(point), selected(sel), normal(norm) {} + + sla::SupportPoint support_point; + bool selected; // whether the point is selected + Vec3f normal; + }; + + // This holds information to decide whether recalculation is necessary: + SourceDataSummary m_source_data; + + mutable Vec3d m_starting_center; + +public: +#if ENABLE_SVG_ICONS + GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); +#else + GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id); +#endif // ENABLE_SVG_ICONS + virtual ~GLGizmoSlaSupports(); + void set_sla_support_data(ModelObject* model_object, const Selection& selection); + bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down); + void delete_selected_points(bool force = false); + std::pair get_sla_clipping_plane() const; + +private: + bool on_init(); + void on_update(const UpdateData& data, const Selection& selection); + virtual void on_render(const Selection& selection) const; + virtual void on_render_for_picking(const Selection& selection) const; + + void render_selection_rectangle() const; + void render_points(const Selection& selection, bool picking = false) const; + bool is_mesh_update_necessary() const; + void update_mesh(); + void update_cache_entry_normal(unsigned int i) const; + + bool m_lock_unique_islands = false; + bool m_editing_mode = false; // Is editing mode active? + bool m_old_editing_state = false; // To keep track of whether the user toggled between the modes (needed for imgui refreshes). + float m_new_point_head_diameter; // Size of a new point. + float m_minimal_point_distance = 20.f; + float m_density = 100.f; + mutable std::vector m_editing_mode_cache; // a support point and whether it is currently selected + float m_clipping_plane_distance = 0.f; + + bool m_selection_rectangle_active = false; + Vec2d m_selection_rectangle_start_corner; + Vec2d m_selection_rectangle_end_corner; + bool m_wait_for_up_event = false; + bool m_unsaved_changes = false; // Are there unsaved changes in manual mode? + bool m_selection_empty = true; + EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) + int m_canvas_width; + int m_canvas_height; + + std::vector get_config_options(const std::vector& keys) const; + + // Methods that do the model_object and editing cache synchronization, + // editing mode selection, etc: + enum { + AllPoints = -2, + NoPoints, + }; + void select_point(int i); + void editing_mode_apply_changes(); + void editing_mode_discard_changes(); + void editing_mode_reload_cache(); + void get_data_from_backend(); + void auto_generate(); + void switch_to_editing_mode(); + +protected: + void on_set_state() override; + void on_start_dragging(const Selection& selection) override; + virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) override; + + virtual std::string on_get_name() const; + virtual bool on_is_activable(const Selection& selection) const; + virtual bool on_is_selectable() const; +}; + + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoSlaSupports_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmos.hpp b/src/slic3r/GUI/Gizmos/GLGizmos.hpp new file mode 100644 index 000000000..8c5e25669 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmos.hpp @@ -0,0 +1,11 @@ +#ifndef slic3r_GLGizmos_hpp_ +#define slic3r_GLGizmos_hpp_ + +#include "slic3r/GUI/Gizmos/GLGizmoMove.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoScale.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoRotate.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp" + +#endif //slic3r_GLGizmos_hpp_ diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 7f95b6c28..0050b6463 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -107,11 +107,11 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) ImGuiIO& io = ImGui::GetIO(); io.MousePos = ImVec2((float)evt.GetX(), (float)evt.GetY()); - io.MouseDown[0] = evt.LeftDown(); - io.MouseDown[1] = evt.RightDown(); - io.MouseDown[2] = evt.MiddleDown(); + io.MouseDown[0] = evt.LeftIsDown(); + io.MouseDown[1] = evt.RightIsDown(); + io.MouseDown[2] = evt.MiddleIsDown(); - unsigned buttons = (evt.LeftDown() ? 1 : 0) | (evt.RightDown() ? 2 : 0) | (evt.MiddleDown() ? 4 : 0); + unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0); m_mouse_buttons = buttons; new_frame(); @@ -239,32 +239,44 @@ bool ImGuiWrapper::checkbox(const wxString &label, bool &value) return ImGui::Checkbox(label_utf8.c_str(), &value); } -void ImGuiWrapper::text(const wxString &label) +void ImGuiWrapper::text(const char *label) { - auto label_utf8 = into_u8(label); - ImGui::Text(label_utf8.c_str(), NULL); + ImGui::Text(label, NULL); } - -bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, wxString& selection) +void ImGuiWrapper::text(const std::string &label) { - std::string selection_u8 = into_u8(selection); + this->text(label.c_str()); +} +void ImGuiWrapper::text(const wxString &label) +{ + this->text(into_u8(label).c_str()); +} + +bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, int& selection) +{ // this is to force the label to the left of the widget: text(label); ImGui::SameLine(); - - if (ImGui::BeginCombo("", selection_u8.c_str())) { - for (const wxString& option : options) { - std::string option_u8 = into_u8(option); - bool is_selected = (selection_u8.empty()) ? false : (option_u8 == selection_u8); - if (ImGui::Selectable(option_u8.c_str(), is_selected)) - selection = option_u8; + + int selection_out = -1; + bool res = false; + + const char *selection_str = selection < options.size() ? options[selection].c_str() : ""; + if (ImGui::BeginCombo("", selection_str)) { + for (int i = 0; i < options.size(); i++) { + if (ImGui::Selectable(options[i].c_str(), i == selection)) { + selection_out = i; + } } + ImGui::EndCombo(); - return true; + res = true; } - return false; + + selection = selection_out; + return res; } void ImGuiWrapper::disabled_begin(bool disabled) @@ -429,6 +441,10 @@ void ImGuiWrapper::init_style() set_color(ImGuiCol_Header, COL_ORANGE_DARK); set_color(ImGuiCol_HeaderHovered, COL_ORANGE_LIGHT); set_color(ImGuiCol_HeaderActive, COL_ORANGE_LIGHT); + + // Slider + set_color(ImGuiCol_SliderGrab, COL_ORANGE_DARK); + set_color(ImGuiCol_SliderGrabActive, COL_ORANGE_LIGHT); } void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 2cadc773c..35d0b5636 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -42,6 +42,8 @@ public: bool update_mouse_data(wxMouseEvent &evt); bool update_key_data(wxKeyEvent &evt); + float get_style_scaling() const { return m_style_scaling; } + void new_frame(); void render(); @@ -57,8 +59,10 @@ public: bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); bool checkbox(const wxString &label, bool &value); + void text(const char *label); + void text(const std::string &label); void text(const wxString &label); - bool combo(const wxString& label, const std::vector& options, wxString& current_selection); + bool combo(const wxString& label, const std::vector& options, int& selection); // Use -1 to not mark any option as selected void disabled_begin(bool disabled); void disabled_end(); diff --git a/src/slic3r/GUI/KBShortcutsDialog.hpp b/src/slic3r/GUI/KBShortcutsDialog.hpp index c5f60f0b8..d8905e1ce 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.hpp +++ b/src/slic3r/GUI/KBShortcutsDialog.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 8d7fa13b1..6dae35352 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -56,7 +56,8 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL // initialize default width_unit according to the width of the one symbol ("x") of the current system font const wxSize size = GetTextExtent("m"); - wxGetApp().set_em_unit(size.x-1); +// wxGetApp().set_em_unit(size.x-1); + wxGetApp().set_em_unit(std::max(10, size.x - 1)); // initialize tabpanel and menubar init_tabpanel(); @@ -76,12 +77,14 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL sizer->SetSizeHints(this); SetSizer(sizer); Fit(); + + const wxSize min_size = wxSize(76*wxGetApp().em_unit(), 49*wxGetApp().em_unit()); #ifdef __APPLE__ // Using SetMinSize() on Mac messes up the window position in some cases // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0 - SetSize(wxSize(760, 490)); + SetSize(min_size/*wxSize(760, 490)*/); #else - SetMinSize(wxSize(760, 490)); + SetMinSize(min_size/*wxSize(760, 490)*/); SetSize(GetMinSize()); #endif Layout(); @@ -93,6 +96,12 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL return; } + // Weird things happen as the Paint messages are floating around the windows being destructed. + // Avoid the Paint messages by hiding the main window. + // Also the application closes much faster without these unnecessary screen refreshes. + // In addition, there were some crashes due to the Paint events sent to already destructed windows. + this->Show(false); + // 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. wxGetApp().app_config->save(); @@ -124,7 +133,9 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL void MainFrame::init_tabpanel() { - m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); + // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 + // with multiple high resolution displays connected. + m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) { auto panel = m_tabpanel->GetCurrentPage(); @@ -885,9 +896,10 @@ void MainFrame::on_config_changed(DynamicPrintConfig* config) const // Update the UI based on the current preferences. void MainFrame::update_ui_from_settings() { - bool bp_on = wxGetApp().app_config->get("background_processing") == "1"; + const bool bp_on = wxGetApp().app_config->get("background_processing") == "1"; // m_menu_item_reslice_now->Enable(!bp_on); m_plater->sidebar().show_reslice(!bp_on); + m_plater->sidebar().show_export(bp_on); m_plater->sidebar().Layout(); if (m_plater) m_plater->update_ui_from_settings(); diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 176d19fb4..961888455 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -24,12 +24,11 @@ namespace GUI { MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id) : -// MsgDialog(parent, title, headline, wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG), button_id) MsgDialog(parent, title, headline, create_scaled_bitmap("Slic3r_192px.png"), button_id) {} MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id) : - wxDialog(parent, wxID_ANY, title), + wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxRESIZE_BORDER), boldfont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)), content_sizer(new wxBoxSizer(wxVERTICAL)), btn_sizer(new wxBoxSizer(wxHORIZONTAL)) @@ -70,7 +69,6 @@ MsgDialog::~MsgDialog() {} ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) : MsgDialog(parent, _(L("Slic3r error")), _(L("Slic3r has encountered an error")), -// wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG), create_scaled_bitmap("Slic3r_192px_grayscale.png"), wxID_NONE) , msg(msg) diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index b5c118c3b..68414fb9a 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -23,18 +23,18 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co // is the normal type. if (opt.gui_type.compare("select") == 0) { } else if (opt.gui_type.compare("select_open") == 0) { - m_fields.emplace(id, std::move(Choice::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(Choice::Create(this->ctrl_parent(), opt, id))); } else if (opt.gui_type.compare("color") == 0) { - m_fields.emplace(id, std::move(ColourPicker::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(ColourPicker::Create(this->ctrl_parent(), opt, id))); } else if (opt.gui_type.compare("f_enum_open") == 0 || opt.gui_type.compare("i_enum_open") == 0 || opt.gui_type.compare("i_enum_closed") == 0) { - m_fields.emplace(id, std::move(Choice::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(Choice::Create(this->ctrl_parent(), opt, id))); } else if (opt.gui_type.compare("slider") == 0) { - m_fields.emplace(id, std::move(SliderCtrl::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(SliderCtrl::Create(this->ctrl_parent(), opt, id))); } else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl } else if (opt.gui_type.compare("legend") == 0) { // StaticText - m_fields.emplace(id, std::move(StaticText::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(StaticText::Create(this->ctrl_parent(), opt, id))); } else { switch (opt.type) { case coFloatOrPercent: @@ -44,21 +44,21 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co case coPercents: case coString: case coStrings: - m_fields.emplace(id, std::move(TextCtrl::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(TextCtrl::Create(this->ctrl_parent(), opt, id))); break; case coBool: case coBools: - m_fields.emplace(id, std::move(CheckBox::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(CheckBox::Create(this->ctrl_parent(), opt, id))); break; case coInt: case coInts: - m_fields.emplace(id, std::move(SpinCtrl::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(SpinCtrl::Create(this->ctrl_parent(), opt, id))); break; case coEnum: - m_fields.emplace(id, std::move(Choice::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(Choice::Create(this->ctrl_parent(), opt, id))); break; case coPoints: - m_fields.emplace(id, std::move(PointCtrl::Create(parent(), opt, id))); + m_fields.emplace(id, std::move(PointCtrl::Create(this->ctrl_parent(), opt, id))); break; case coNone: break; default: @@ -119,7 +119,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n return; } if (line.widget != nullptr) { - sizer->Add(line.widget(m_parent), 0, wxEXPAND | wxALL, wxOSX ? 0 : 15); + sizer->Add(line.widget(this->ctrl_parent()), 0, wxEXPAND | wxALL, wxOSX ? 0 : 15); return; } } @@ -138,7 +138,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) { wxSizer* tmp_sizer; -#ifdef __WXGTK__ +#if 0//#ifdef __WXGTK__ tmp_sizer = new wxBoxSizer(wxVERTICAL); m_panel->SetSizer(tmp_sizer); m_panel->Layout(); @@ -160,14 +160,14 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n } auto grid_sizer = m_grid_sizer; -#ifdef __WXGTK__ +#if 0//#ifdef __WXGTK__ m_panel->SetSizer(m_grid_sizer); m_panel->Layout(); #endif /* __WXGTK__ */ // if we have an extra column, build it if (extra_column) - grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 3); + grid_sizer->Add(extra_column(this->ctrl_parent(), line), 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 3); // Build a label if we have it wxStaticText* label=nullptr; @@ -179,18 +179,21 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // Text is properly aligned only when Ellipsize is checked. label_style |= staticbox ? 0 : wxST_ELLIPSIZE_END; #endif /* __WXGTK__ */ - label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"), + label = new wxStaticText(this->ctrl_parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"), wxDefaultPosition, wxSize(label_width, -1), label_style); + label->SetBackgroundStyle(wxBG_STYLE_PAINT); label->SetFont(label_font); label->Wrap(label_width); // avoid a Linux/GTK bug if (!line.near_label_widget) grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, line.label.IsEmpty() ? 0 : 5); + else if (line.near_label_widget && line.label.IsEmpty()) + grid_sizer->Add(line.near_label_widget(this->ctrl_parent()), 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 7); else { // If we're here, we have some widget near the label // so we need a horizontal sizer to arrange these things auto sizer = new wxBoxSizer(wxHORIZONTAL); grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1); - sizer->Add(line.near_label_widget(parent()), 0, wxRIGHT, 7); + sizer->Add(line.near_label_widget(this->ctrl_parent()), 0, wxRIGHT, 7); sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5); } if (line.label_tooltip.compare("") != 0) @@ -201,7 +204,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n *full_Label = label; // Initiate the pointer to the control of the full label, if we need this one. // If there's a widget, build it and add the result to the sizer. if (line.widget != nullptr) { - auto wgt = line.widget(parent()); + auto wgt = line.widget(this->ctrl_parent()); // If widget doesn't have label, don't use border grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, (wxOSX || line.label.IsEmpty()) ? 0 : 5); return; @@ -237,7 +240,8 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n wxString str_label = (option.label == "Top" || option.label == "Bottom") ? _CTX(option.label, "Layers") : _(option.label); - label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize); + label = new wxStaticText(this->ctrl_parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize); + label->SetBackgroundStyle(wxBG_STYLE_PAINT); label->SetFont(label_font); sizer_tmp->Add(label, 0, /*wxALIGN_RIGHT |*/ wxALIGN_CENTER_VERTICAL, 0); } @@ -262,8 +266,9 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // add sidetext if any if (option.sidetext != "") { - auto sidetext = new wxStaticText( parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, + auto sidetext = new wxStaticText( this->ctrl_parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, wxSize(sidetext_width, -1)/*wxDefaultSize*/, wxALIGN_LEFT); + sidetext->SetBackgroundStyle(wxBG_STYLE_PAINT); sidetext->SetFont(sidetext_font); sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); field->set_side_text_ptr(sidetext); @@ -271,7 +276,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // add side widget if any if (opt.side_widget != nullptr) { - sizer_tmp->Add(opt.side_widget(parent())/*!.target()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification + sizer_tmp->Add(opt.side_widget(this->ctrl_parent())/*!.target()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification } if (opt.opt_id != option_set.back().opt_id) //! istead of (opt != option_set.back()) @@ -287,11 +292,11 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // extra widget for non-staticbox option group (like for the frequently used parameters on the sidebar) should be wxALIGN_RIGHT const auto v_sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(v_sizer, 1, wxEXPAND); - v_sizer->Add(extra_widget(parent()), 0, wxALIGN_RIGHT); + v_sizer->Add(extra_widget(this->ctrl_parent()), 0, wxALIGN_RIGHT); return; } - sizer->Add(extra_widget(parent())/*!.target()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); //! requires verification + sizer->Add(extra_widget(this->ctrl_parent())/*!.target()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); //! requires verification } } @@ -438,7 +443,7 @@ void ConfigOptionsGroup::Hide() void ConfigOptionsGroup::Show(const bool show) { sizer->ShowItems(show); -#ifdef __WXGTK__ +#if 0//#ifdef __WXGTK__ m_panel->Show(show); m_grid_sizer->Show(show); #endif /* __WXGTK__ */ diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index d9ff6a01a..dbe1ea1a2 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -100,18 +100,22 @@ public: /// Accessor function is because users are not allowed to change the parent /// but defining it as const means a lot of const_casts to deal with wx functions. inline wxWindow* parent() const { -#ifdef __WXGTK__ +#if 0//#ifdef __WXGTK__ return m_panel; #else return m_parent; #endif /* __WXGTK__ */ } -#ifdef __WXGTK__ +#if 0//#ifdef __WXGTK__ wxWindow* get_parent() const { return m_parent; } #endif /* __WXGTK__ */ + wxWindow* ctrl_parent() const { + return this->stb ? (wxWindow*)this->stb : this->parent(); + } + void append_line(const Line& line, wxStaticText** full_Label = nullptr); Line create_single_option_line(const Option& option) const; void append_single_option_line(const Option& option) { append_line(create_single_option_line(option)); } @@ -161,8 +165,10 @@ public: staticbox(title!=""), extra_column(extra_clmn) { if (staticbox) { stb = new wxStaticBox(_parent, wxID_ANY, title); + stb->SetBackgroundStyle(wxBG_STYLE_PAINT); stb->SetFont(wxGetApp().bold_font()); - } + } else + stb = nullptr; sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); auto num_columns = 1U; if (label_width != 0) num_columns++; @@ -170,7 +176,7 @@ public: m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1,0); static_cast(m_grid_sizer)->SetFlexibleDirection(wxBOTH/*wxHORIZONTAL*/); static_cast(m_grid_sizer)->AddGrowableCol(label_width == 0 ? 0 : !extra_column ? 1 : 2 ); -#ifdef __WXGTK__ +#if 0//#ifdef __WXGTK__ m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); sizer->Fit(m_panel); sizer->Add(m_panel, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); @@ -198,7 +204,7 @@ protected: // This panel is needed for correct showing of the ToolTips for Button, StaticText and CheckBox // Tooltips on GTK doesn't work inside wxStaticBoxSizer unless you insert a panel // inside it before you insert the other controls. -#ifdef __WXGTK__ +#if 0//#ifdef__WXGTK__ wxPanel* m_panel {nullptr}; #endif /* __WXGTK__ */ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 065b20125..bc6128abc 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -47,14 +47,17 @@ #include "MainFrame.hpp" #include "3DScene.hpp" #include "GLCanvas3D.hpp" +#include "Selection.hpp" #include "GLToolbar.hpp" #include "GUI_Preview.hpp" #include "3DBed.hpp" +#include "Camera.hpp" #include "Tab.hpp" #include "PresetBundle.hpp" #include "BackgroundSlicingProcess.hpp" #include "ProgressStatusBar.hpp" #include "PrintHostDialogs.hpp" +#include "ConfigWizard.hpp" #include "../Utils/ASCIIFolding.hpp" #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" @@ -231,9 +234,11 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * auto selected_item = this->GetSelection(); auto marker = reinterpret_cast(this->GetClientData(selected_item)); - if (marker == LABEL_ITEM_MARKER) { + if (marker == LABEL_ITEM_MARKER || marker == LABEL_ITEM_CONFIG_WIZARD) { this->SetSelection(this->last_selected); evt.StopPropagation(); + if (marker == LABEL_ITEM_CONFIG_WIZARD) + wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); }); } else if ( this->last_selected != selected_item || wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) { this->last_selected = selected_item; @@ -274,20 +279,55 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * dialog->Destroy(); }); } + + edit_btn = new wxButton(parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); +#ifdef __WINDOWS__ + edit_btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif + edit_btn->SetBitmap(create_scaled_bitmap("cog.png")); + edit_btn->SetToolTip(_(L("Click to edit preset"))); + + edit_btn->Bind(wxEVT_BUTTON, ([preset_type, this](wxCommandEvent) + { + Tab* tab = wxGetApp().get_tab(preset_type); + if (!tab) + return; + + int page_id = wxGetApp().tab_panel()->FindPage(tab); + if (page_id == wxNOT_FOUND) + return; + + wxGetApp().tab_panel()->ChangeSelection(page_id); + + /* In a case of a multi-material printing, for editing another Filament Preset + * it's needed to select this preset for the "Filament settings" Tab + */ + if (preset_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_cnt() > 1) + { + const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data(); + + // Call select_preset() only if there is new preset and not just modified + if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) ) + tab->select_preset(selected_preset); + } + })); } -PresetComboBox::~PresetComboBox() {} - - -void PresetComboBox::set_label_marker(int item) +PresetComboBox::~PresetComboBox() { - this->SetClientData(item, (void*)LABEL_ITEM_MARKER); + if (edit_btn) + edit_btn->Destroy(); +} + + +void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type) +{ + this->SetClientData(item, (void*)label_item_type); } void PresetComboBox::check_selection() { - if (this->last_selected != GetSelection()) - this->last_selected = GetSelection(); + this->last_selected = GetSelection(); } // Frequently changed parameters @@ -364,20 +404,20 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : Line line = Line { "", "" }; - ConfigOptionDef def; - def.label = L("Supports"); - def.type = coStrings; - def.gui_type = "select_open"; - def.tooltip = L("Select what kind of support do you need"); - def.enum_labels.push_back(L("None")); - def.enum_labels.push_back(L("Support on build plate only")); - def.enum_labels.push_back(L("Everywhere")); - const std::string selection = !config->opt_bool("support_material") ? - "None" : config->opt_bool("support_material_buildplate_only") ? - "Support on build plate only" : - "Everywhere"; - def.default_value = new ConfigOptionStrings{ selection }; - Option option = Option(def, "support"); + ConfigOptionDef support_def; + support_def.label = L("Supports"); + support_def.type = coStrings; + support_def.gui_type = "select_open"; + support_def.tooltip = L("Select what kind of support do you need"); + support_def.enum_labels.push_back(L("None")); + support_def.enum_labels.push_back(L("Support on build plate only")); + support_def.enum_labels.push_back(L("Everywhere")); + std::string selection = !config->opt_bool("support_material") ? + "None" : config->opt_bool("support_material_buildplate_only") ? + "Support on build plate only" : + "Everywhere"; + support_def.default_value = new ConfigOptionStrings{ selection }; + Option option = Option(support_def, "support"); option.opt.full_width = true; line.append_option(option); m_og->append_line(line); @@ -392,6 +432,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : line.append_option(option); m_brim_width = config->opt_float("brim_width"); + ConfigOptionDef def; def.label = L("Brim"); def.type = coBool; def.tooltip = L("This flag enables the brim that will be printed around each object on the first layer."); @@ -427,6 +468,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : m_og->append_line(line); + // Frequently changed parameters for SLA_technology m_og_sla = std::make_shared(parent, ""); DynamicPrintConfig* config_sla = &wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; @@ -437,20 +479,43 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT); if (!tab) return; - tab->set_value(opt_key, value); + if (opt_key == "pad_enable") { + tab->set_value(opt_key, value); + tab->update(); + } + else //(opt_key == "support") + { + DynamicPrintConfig new_conf = *config_sla; + const wxString& selection = boost::any_cast(value); + + const bool supports_enable = selection == _("None") ? false : true; + new_conf.set_key_value("supports_enable", new ConfigOptionBool(supports_enable)); + + if (selection == _("Everywhere")) + new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(false)); + else if (selection == _("Support on build plate only")) + new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(true)); + + tab->load_config(new_conf); + } - DynamicPrintConfig new_conf = *config_sla; - new_conf.set_key_value(opt_key, new ConfigOptionBool(boost::any_cast(value))); - tab->load_config(new_conf); tab->update_dirty(); - }; - + }; line = Line{ "", "" }; - option = m_og_sla->get_option("supports_enable"); - option.opt.sidetext = " "; + selection = !config_sla->opt_bool("supports_enable") ? + "None" : config_sla->opt_bool("support_buildplate_only") ? + "Support on build plate only" : + "Everywhere"; + support_def.default_value = new ConfigOptionStrings{ selection }; + option = Option(support_def, "support"); + option.opt.full_width = true; line.append_option(option); + m_og_sla->append_line(line); + + + line = Line{ "", "" }; option = m_og_sla->get_option("pad_enable"); option.opt.sidetext = " "; @@ -487,11 +552,18 @@ ConfigOptionsGroup* FreqChangedParams::get_og(const bool is_fff) // Sidebar / private +enum class ActionButtonType : int { + abReslice, + abExport, + abSendGCode +}; + struct Sidebar::priv { Plater *plater; wxScrolledWindow *scrolled; + wxPanel* presets_panel; // Used for MSW better layouts PrusaModeSizer *mode_sizer; wxFlexGridSizer *sizer_presets; @@ -523,8 +595,6 @@ void Sidebar::priv::show_preset_comboboxes() { const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA; - wxWindowUpdateLocker noUpdates_scrolled(scrolled->GetParent()); - for (size_t i = 0; i < 2; ++i) sizer_presets->Show(i, !showSLA); @@ -548,6 +618,7 @@ Sidebar::Sidebar(Plater *parent) p->scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(40 * wxGetApp().em_unit(), -1)); p->scrolled->SetScrollbars(0, 20, 1, 2); + // Sizer in the scrolled area auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL); p->scrolled->SetSizer(scrolled_sizer); @@ -559,21 +630,39 @@ Sidebar::Sidebar(Plater *parent) p->sizer_presets = new wxFlexGridSizer(10, 1, 1, 2); p->sizer_presets->AddGrowableCol(0, 1); p->sizer_presets->SetFlexibleDirection(wxBOTH); + + bool is_msw = false; +#ifdef __WINDOWS__ + p->scrolled->SetDoubleBuffered(true); + + p->presets_panel = new wxPanel(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + p->presets_panel->SetSizer(p->sizer_presets); + + is_msw = true; +#else + p->presets_panel = p->scrolled; +#endif //__WINDOWS__ + p->sizer_filaments = new wxBoxSizer(wxVERTICAL); auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { // do not print these albels. if you re-enabled these, don't forget to *2 the size in show_preset_comboboxes() - //auto *text = new wxStaticText(p->scrolled, wxID_ANY, label+" :"); + //auto *text = new wxStaticText(p->presets_panel, wxID_ANY, label+" :"); //text->SetFont(wxGetApp().small_font()); - *combo = new PresetComboBox(p->scrolled, preset_type); + *combo = new PresetComboBox(p->presets_panel, preset_type); + + auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL); + combo_and_btn_sizer->Add(*combo, 1, wxEXPAND); + if ((*combo)->edit_btn) + combo_and_btn_sizer->Add((*combo)->edit_btn, 0, wxLEFT|wxRIGHT, int(0.3*wxGetApp().em_unit())); auto *sizer_presets = this->p->sizer_presets; auto *sizer_filaments = this->p->sizer_filaments; //sizer_presets->Add(text, 0, wxALIGN_LEFT | wxEXPAND | wxRIGHT, 4); if (! filament) { - sizer_presets->Add(*combo, 0, wxEXPAND | wxBOTTOM, 1); + sizer_presets->Add(combo_and_btn_sizer, 0, wxEXPAND | wxBOTTOM, 1); } else { - sizer_filaments->Add(*combo, 0, wxEXPAND | wxBOTTOM, 1); + sizer_filaments->Add(combo_and_btn_sizer, 0, wxEXPAND | wxBOTTOM, 1); (*combo)->set_extruder_idx(0); sizer_presets->Add(sizer_filaments, 1, wxEXPAND); } @@ -614,9 +703,7 @@ Sidebar::Sidebar(Plater *parent) p->object_settings->Hide(); p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); - wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG); p->btn_send_gcode = new wxButton(this, wxID_ANY, _(L("Send to printer"))); - p->btn_send_gcode->SetBitmap(arrow_up); p->btn_send_gcode->SetFont(wxGetApp().bold_font()); p->btn_send_gcode->Hide(); @@ -626,7 +713,9 @@ Sidebar::Sidebar(Plater *parent) // Sizer in the scrolled area scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/); - scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5); + is_msw ? + scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) : + scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5); scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND | wxLEFT, margin_5); scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5); scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5); @@ -650,21 +739,32 @@ Sidebar::Sidebar(Plater *parent) // Events p->btn_export_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(); }); - p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->reslice(); }); + p->btn_reslice->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) + { + const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT); + if (export_gcode_after_slicing) + p->plater->export_gcode(); + else + p->plater->reslice(); + }); p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); } Sidebar::~Sidebar() {} void Sidebar::init_filament_combo(PresetComboBox **combo, const int extr_idx) { - *combo = new PresetComboBox(p->scrolled, Slic3r::Preset::TYPE_FILAMENT); + *combo = new PresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT); // # copy icons from first choice // $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; (*combo)->set_extruder_idx(extr_idx); + auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL); + combo_and_btn_sizer->Add(*combo, 1, wxEXPAND); + combo_and_btn_sizer->Add((*combo)->edit_btn, 0, wxLEFT | wxRIGHT, int(0.3*wxGetApp().em_unit())); + auto /***/sizer_filaments = this->p->sizer_filaments; - sizer_filaments->Add(*combo, 1, wxEXPAND | wxBOTTOM, 1); + sizer_filaments->Add(combo_and_btn_sizer, 1, wxEXPAND | wxBOTTOM, 1); } void Sidebar::remove_unused_filament_combos(const int current_extruder_count) @@ -719,6 +819,8 @@ void Sidebar::update_presets(Preset::Type preset_type) case Preset::TYPE_PRINTER: { +// wxWindowUpdateLocker noUpdates_scrolled(p->scrolled); + // Update the print choosers to only contain the compatible presets, update the dirty flags. if (print_tech == ptFFF) preset_bundle.prints.update_platter_ui(p->combo_print); @@ -727,10 +829,7 @@ void Sidebar::update_presets(Preset::Type preset_type) preset_bundle.sla_materials.update_platter_ui(p->combo_sla_material); } // Update the printer choosers, update the dirty flags. - auto prev_selection = p->combo_printer->GetSelection(); preset_bundle.printers.update_platter_ui(p->combo_printer); - if (prev_selection != p->combo_printer->GetSelection()) - p->combo_printer->check_selection(); // Update the filament choosers to only contain the compatible presets, update the color preview, // update the dirty flags. if (print_tech == ptFFF) { @@ -748,9 +847,15 @@ void Sidebar::update_presets(Preset::Type preset_type) wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); } -void Sidebar::update_mode_sizer(const Slic3r::ConfigOptionMode& mode) +void Sidebar::update_mode_sizer() const { - p->mode_sizer->SetMode(mode); + p->mode_sizer->SetMode(m_mode); +} + +void Sidebar::update_reslice_btn_tooltip() const +{ + const wxString tooltip = m_mode == comSimple ? wxString("") : _(L("Hold Shift to Slice & Export G-code")); + p->btn_reslice->SetToolTip(tooltip); } ObjectManipulation* Sidebar::obj_manipul() @@ -773,6 +878,11 @@ wxScrolledWindow* Sidebar::scrolled_panel() return p->scrolled; } +wxPanel* Sidebar::presets_panel() +{ + return p->presets_panel; +} + ConfigOptionsGroup* Sidebar::og_freq_chng_params(const bool is_fff) { return p->frequently_changed_parameters->get_og(is_fff); @@ -814,7 +924,7 @@ void Sidebar::show_info_sizer() p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast(model_object->materials_count()))); auto& stats = model_object->volumes.front()->mesh.stl.stats; - p->object_info->info_volume->SetLabel(wxString::Format("%.2f", size(0) * size(1) * size(2))); + p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume)); p->object_info->info_facets->SetLabel(wxString::Format(_(L("%d (%d shells)")), static_cast(model_object->facets_count()), stats.number_of_parts)); int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + @@ -943,8 +1053,9 @@ void Sidebar::enable_buttons(bool enable) p->btn_send_gcode->Enable(enable); } -void Sidebar::show_reslice(bool show) { p->btn_reslice->Show(show); } -void Sidebar::show_send(bool show) { p->btn_send_gcode->Show(show); } +void Sidebar::show_reslice(bool show) const { p->btn_reslice->Show(show); } +void Sidebar::show_export(bool show) const { p->btn_export_gcode->Show(show); } +void Sidebar::show_send(bool show) const { p->btn_send_gcode->Show(show); } bool Sidebar::is_multifilament() { @@ -952,6 +1063,24 @@ bool Sidebar::is_multifilament() } +void Sidebar::update_mode() +{ + m_mode = wxGetApp().get_mode(); + + update_reslice_btn_tooltip(); + update_mode_sizer(); + + wxWindowUpdateLocker noUpdates(this); + + p->object_list->get_sizer()->Show(m_mode > comSimple); + + p->object_list->unselect_objects(); + p->object_list->update_selections(); + p->object_list->update_object_menu(); + + Layout(); +} + std::vector& Sidebar::combos_filament() { return p->combos_filament; @@ -1006,6 +1135,15 @@ struct Plater::priv // SLA-Object popup menu PrusaMenu sla_object_menu; + // Removed/Prepended Items according to the view mode + std::vector items_increase; + std::vector items_decrease; + std::vector items_set_number_of_copies; + enum MenuIdentifier { + miObjectFFF=0, + miObjectSLA + }; + // Data Slic3r::DynamicPrintConfig *config; // FIXME: leak? Slic3r::Print fff_print; @@ -1015,11 +1153,12 @@ struct Plater::priv Slic3r::GCodePreviewData gcode_preview_data; // GUI elements - wxSizer* panel_sizer; - wxPanel* current_panel; + wxSizer* panel_sizer{ nullptr }; + wxPanel* current_panel{ nullptr }; std::vector panels; Sidebar *sidebar; Bed3D bed; + Camera camera; View3D* view3D; GLToolbar view_toolbar; Preview *preview; @@ -1034,6 +1173,9 @@ struct Plater::priv wxTimer background_process_timer; + std::string label_btn_export; + std::string label_btn_send; + static const std::regex pattern_bundle; static const std::regex pattern_3mf; static const std::regex pattern_zip_amf; @@ -1057,8 +1199,8 @@ struct Plater::priv std::vector load_model_objects(const ModelObjectPtrs &model_objects); std::unique_ptr get_export_file(GUI::FileType file_type); - const GLCanvas3D::Selection& get_selection() const; - GLCanvas3D::Selection& get_selection(); + const Selection& get_selection() const; + Selection& get_selection(); int get_selected_object_idx() const; int get_selected_volume_idx() const; void selection_changed(); @@ -1116,13 +1258,13 @@ struct Plater::priv void on_action_layersediting(SimpleEvent&); void on_object_select(SimpleEvent&); - void on_viewport_changed(SimpleEvent&); void on_right_click(Vec2dEvent&); void on_wipetower_moved(Vec3dEvent&); void on_update_geometry(Vec3dsEvent<2>&); void on_3dcanvas_mouse_dragging_finished(SimpleEvent&); void update_object_menu(); + void show_action_buttons(const bool is_ready_to_slice) const; // Set the bed shape to a single closed 2D polygon(array of two element arrays), // triangulate the bed and store the triangles into m_bed.m_triangles, @@ -1130,6 +1272,15 @@ struct Plater::priv // Sets m_bed.m_polygon to limit the object placement. void set_bed_shape(const Pointfs& shape); + bool can_delete() const; + bool can_delete_all() const; + bool can_increase_instances() const; + bool can_decrease_instances() const; + bool can_split_to_objects() const; + bool can_split_to_volumes() const; + bool can_arrange() const; + bool can_layers_editing() const; + private: bool init_object_menu(); bool init_common_menu(wxMenu* menu, const bool is_part = false); @@ -1138,16 +1289,9 @@ private: bool complit_init_part_menu(); void init_view_toolbar(); - bool can_delete_object() const; - bool can_increase_instances() const; - bool can_decrease_instances() const; bool can_set_instance_to_object() const; - bool can_split_to_objects() const; - bool can_split_to_volumes() const; bool can_split() const; bool layers_height_allowed() const; - bool can_delete_all() const; - bool can_arrange() const; bool can_mirror() const; void update_fff_scene(); @@ -1179,7 +1323,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) , sidebar(new Sidebar(q)) , delayed_scene_refresh(false) , project_filename(wxEmptyString) +#if ENABLE_SVG_ICONS + , view_toolbar(GLToolbar::Radio, "View") +#else , view_toolbar(GLToolbar::Radio) +#endif // ENABLE_SVG_ICONS { arranging.store(false); rotoptimizing.store(false); @@ -1199,11 +1347,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) sla_print.set_status_callback(statuscb); this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this); - view3D = new View3D(q, &model, config, &background_process); - preview = new Preview(q, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); }); - - view3D->set_bed(&bed); - preview->set_bed(&bed); + view3D = new View3D(q, bed, camera, view_toolbar, &model, config, &background_process); + preview = new Preview(q, bed, camera, view_toolbar, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); }); panels.push_back(view3D); panels.push_back(preview); @@ -1235,7 +1380,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // 3DScene events: view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this); - view3D_canvas->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this); view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this); view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); @@ -1265,7 +1409,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values); }); // Preview events: - preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); @@ -1474,7 +1617,11 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (type_3mf || type_any_amf) { #endif // !ENABLE_VOLUMES_CENTERING_FIXES for (ModelObject* model_object : model.objects) { +#if ENABLE_VOLUMES_CENTERING_FIXES + model_object->center_around_origin(false); +#else model_object->center_around_origin(); +#endif // ENABLE_VOLUMES_CENTERING_FIXES model_object->ensure_on_bed(); } #if !ENABLE_VOLUMES_CENTERING_FIXES @@ -1529,7 +1676,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ // automatic selection of added objects if (!obj_idxs.empty() && (view3D != nullptr)) { - GLCanvas3D::Selection& selection = view3D->get_canvas3d()->get_selection(); + Selection& selection = view3D->get_canvas3d()->get_selection(); selection.clear(); for (size_t idx : obj_idxs) { @@ -1567,8 +1714,8 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode object->center_around_origin(); new_instances.emplace_back(object->add_instance()); #else /* AUTOPLACEMENT_ON_LOAD */ - // if object has no defined position(s) we need to rearrange everything after loading object->center_around_origin(); - need_arrange = true; + // if object has no defined position(s) we need to rearrange everything after loading + need_arrange = true; // add a default instance and center object around origin object->center_around_origin(); // also aligns object to Z = 0 ModelInstance* instance = object->add_instance(); @@ -1678,12 +1825,12 @@ std::unique_ptr Plater::priv::get_export_file(GUI::FileType return dlg; } -const GLCanvas3D::Selection& Plater::priv::get_selection() const +const Selection& Plater::priv::get_selection() const { return view3D->get_canvas3d()->get_selection(); } -GLCanvas3D::Selection& Plater::priv::get_selection() +Selection& Plater::priv::get_selection() { return view3D->get_canvas3d()->get_selection(); } @@ -1708,12 +1855,6 @@ int Plater::priv::get_selected_volume_idx() const void Plater::priv::selection_changed() { - view3D->enable_toolbar_item("delete", can_delete_object()); - view3D->enable_toolbar_item("more", can_increase_instances()); - view3D->enable_toolbar_item("fewer", can_decrease_instances()); - view3D->enable_toolbar_item("splitobjects", can_split/*_to_objects*/()); - view3D->enable_toolbar_item("splitvolumes", can_split/*_to_volumes*/()); - // if the selection is not valid to allow for layer editing, we need to turn off the tool if it is running bool enable_layer_editing = layers_height_allowed(); if (!enable_layer_editing && view3D->is_layers_editing_enabled()) { @@ -1721,18 +1862,12 @@ void Plater::priv::selection_changed() on_action_layersediting(evt); } - view3D->enable_toolbar_item("layersediting", enable_layer_editing); - // forces a frame render to update the view (to avoid a missed update if, for example, the context menu appears) view3D->render(); } void Plater::priv::object_list_changed() { - // Enable/disable buttons depending on whether there are any objects on the platter. - view3D->enable_toolbar_item("deleteall", can_delete_all()); - view3D->enable_toolbar_item("arrange", can_arrange()); - const bool export_in_progress = this->background_process.is_export_scheduled(); // || ! send_gcode_file.empty()); // XXX: is this right? const bool model_fits = view3D->check_volumes_outside_state() == ModelInstance::PVS_Inside; @@ -1811,9 +1946,6 @@ void Plater::priv::arrange() wxBusyCursor wait; - // Disable the arrange button (to prevent reentrancies, we will call wxYied) - view3D->enable_toolbar_item("arrange", can_arrange()); - this->background_process.stop(); unsigned count = 0; for(auto obj : model.objects) count += obj->instances.size(); @@ -1892,9 +2024,6 @@ void Plater::priv::arrange() statusbar()->set_cancel_callback(); // remove cancel button arranging.store(false); - // We enable back the arrange button - view3D->enable_toolbar_item("arrange", can_arrange()); - // Do a full refresh of scene tree, including regenerating all the GLVolumes. //FIXME The update function shall just reload the modified matrices. update(true); @@ -2006,9 +2135,6 @@ void Plater::priv::schedule_background_process() this->background_process_timer.Start(500, wxTIMER_ONE_SHOT); // Notify the Canvas3D that something has changed, so it may invalidate some of the layer editing stuff. this->view3D->get_canvas3d()->set_config(this->config); - // Reset gcode preview - this->preview->get_canvas3d()->reset_volumes(); - this->preview->get_canvas3d()->reset_legend_texture(); } void Plater::priv::update_print_volume_state() @@ -2097,13 +2223,36 @@ unsigned int Plater::priv::update_background_process(bool force_validation) wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); } - //FIXME update "Slice Now / Schedule background process" - //background_process.is_export_scheduled() - byl zavolan "Export G-code", background processing ma jmeno export souboru - //background_process.is_upload_scheduled() - byl zavolan "Send to OctoPrint", jeste nebylo doslajsovano (pak se preda upload fronte a background process zapomene) - //background_process.empty() - prazdna plocha - // pokud (return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0 -> doslo k chybe (gray out "Slice now") mozna "Invalid data"??? - // jinak background_process.running() -> Zobraz "Slicing ..." - // jinak pokud ! background_process.empty() && ! background_process.finished() -> je neco ke slajsovani (povol tlacitko) "Slice Now" + if ((return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0) + { + // Validation of the background data failed. + const wxString invalid_str = _(L("Invalid data")); + for (auto btn : {ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport}) + sidebar->set_btn_label(btn, invalid_str); + } + else + { + // Background data is valid. + if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 || + (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 ) + this->statusbar()->set_status_text(L("Ready to slice")); + + sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export)); + sidebar->set_btn_label(ActionButtonType::abSendGCode, _(label_btn_send)); + + const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ? + _(L("Slicing")) + dots : _(L("Slice now")); + sidebar->set_btn_label(ActionButtonType::abReslice, slice_string); + + if (background_process.finished()) + show_action_buttons(false); + else if (!background_process.empty() && + !background_process.running()) /* Do not update buttons if background process is running + * This condition is important for SLA mode especially, + * when this function is called several times during calculations + * */ + show_action_buttons(true); + } return return_state; } @@ -2234,6 +2383,10 @@ void Plater::priv::set_current_panel(wxPanel* panel) if (std::find(panels.begin(), panels.end(), panel) == panels.end()) return; +#ifdef __WXMAC__ + bool force_render = (current_panel != nullptr); +#endif // __WXMAC__ + if (current_panel == panel) return; @@ -2242,7 +2395,19 @@ void Plater::priv::set_current_panel(wxPanel* panel) for (wxPanel* p : panels) { if (p == current_panel) + { +#ifdef __WXMAC__ + // On Mac we need also to force a render to avoid flickering when changing view + if (force_render) + { + if (p == view3D) + dynamic_cast(p)->get_canvas3d()->render(); + else if (p == preview) + dynamic_cast(p)->get_canvas3d()->render(); + } +#endif // __WXMAC__ p->Show(); + } } // then set to invisible the other for (wxPanel* p : panels) @@ -2274,7 +2439,7 @@ void Plater::priv::set_current_panel(wxPanel* panel) { this->q->reslice(); // keeps current gcode preview, if any - preview->reload_print(false, true); + preview->reload_print(true); preview->set_canvas_as_dirty(); view_toolbar.select_item("Preview"); } @@ -2296,7 +2461,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) //! instead of //! combo->GetStringSelection().ToUTF8().data()); - std::string selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data(); + const std::string& selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data(); if (preset_type == Preset::TYPE_FILAMENT) { wxGetApp().preset_bundle->set_filament_preset(idx, selected_string); @@ -2308,12 +2473,8 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) wxGetApp().preset_bundle->update_platter_filament_ui(idx, combo); } else { - for (Tab* tab : wxGetApp().tabs_list) { - if (tab->type() == preset_type) { - tab->select_preset(selected_string); - break; - } - } + wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); + wxGetApp().get_tab(preset_type)->select_preset(selected_string); } // update plater with new config @@ -2324,8 +2485,10 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) { - this->statusbar()->set_progress(evt.status.percent); - this->statusbar()->set_status_text(_(L(evt.status.text)) + wxString::FromUTF8("…")); + if (evt.status.percent >= -1) { + this->statusbar()->set_progress(evt.status.percent); + this->statusbar()->set_status_text(_(L(evt.status.text)) + wxString::FromUTF8("…")); + } if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SCENE) { switch (this->printer_technology) { case ptFFF: @@ -2343,6 +2506,10 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) // Update SLA gizmo (reload_scene calls update_gizmos_data) q->canvas3D()->reload_scene(true); } + if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) { + // Update the SLA preview + this->preview->reload_print(); + } } void Plater::priv::on_slicing_completed(wxCommandEvent &) @@ -2369,14 +2536,17 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) this->statusbar()->reset_cancel_callback(); this->statusbar()->stop_busy(); - bool canceled = evt.GetInt() < 0; - bool success = evt.GetInt() > 0; + const bool canceled = evt.GetInt() < 0; + const bool error = evt.GetInt() == 0; + const bool success = evt.GetInt() > 0; // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. this->background_process.reset_export(); - if (! success) { + + if (error) { wxString message = evt.GetString(); if (message.IsEmpty()) message = _(L("Export failed")); + show_error(q, message); this->statusbar()->set_status_text(message); } if (canceled) @@ -2401,15 +2571,19 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) this->update_sla_scene(); break; } + + if (canceled) { + if (wxGetApp().get_mode() == comSimple) + sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now"); + show_action_buttons(true); + } + else if (wxGetApp().get_mode() == comSimple) + show_action_buttons(false); } void Plater::priv::on_layer_editing_toggled(bool enable) { view3D->enable_layers_editing(enable); - if (enable && !view3D->is_layers_editing_enabled()) { - // Initialization of the OpenGL shaders failed. Disable the tool. - view3D->enable_toolbar_item("layersediting", false); - } view3D->set_as_dirty(); } @@ -2431,10 +2605,7 @@ void Plater::priv::on_action_split_volumes(SimpleEvent&) void Plater::priv::on_action_layersediting(SimpleEvent&) { - bool enable = !view3D->is_layers_editing_enabled(); - view3D->enable_layers_editing(enable); - if (enable && !view3D->is_layers_editing_enabled()) - view3D->enable_toolbar_item("layersediting", false); + view3D->enable_layers_editing(!view3D->is_layers_editing_enabled()); } void Plater::priv::on_object_select(SimpleEvent& evt) @@ -2443,15 +2614,6 @@ void Plater::priv::on_object_select(SimpleEvent& evt) selection_changed(); } -void Plater::priv::on_viewport_changed(SimpleEvent& evt) -{ - wxObject* o = evt.GetEventObject(); - if (o == preview->get_wxglcanvas()) - preview->set_viewport_into_scene(view3D->get_canvas3d()); - else if (o == view3D->get_wxglcanvas()) - preview->set_viewport_from_scene(view3D->get_canvas3d()); -} - void Plater::priv::on_right_click(Vec2dEvent& evt) { int obj_idx = get_selected_object_idx(); @@ -2464,6 +2626,32 @@ void Plater::priv::on_right_click(Vec2dEvent& evt) sidebar->obj_list()->append_menu_item_settings(menu); + /* Remove/Prepend "increase/decrease instances" menu items according to the view mode. + * Suppress to show those items for a Simple mode + */ + const MenuIdentifier id = printer_technology == ptSLA ? miObjectSLA : miObjectFFF; + if (wxGetApp().get_mode() == comSimple) { + if (menu->FindItem(_(L("Increase copies"))) != wxNOT_FOUND) + { + /* Detach an items from the menu, but don't delete them + * so that they can be added back later + * (after switching to the Advanced/Expert mode) + */ + menu->Remove(items_increase[id]); + menu->Remove(items_decrease[id]); + menu->Remove(items_set_number_of_copies[id]); + } + } + else { + if (menu->FindItem(_(L("Increase copies"))) == wxNOT_FOUND) + { + // Prepend items to the menu, if those aren't not there + menu->Prepend(items_set_number_of_copies[id]); + menu->Prepend(items_decrease[id]); + menu->Prepend(items_increase[id]); + } + } + if (q != nullptr) { #ifdef __linux__ // For some reason on Linux the menu isn't displayed if position is specified @@ -2500,6 +2688,10 @@ void Plater::priv::on_3dcanvas_mouse_dragging_finished(SimpleEvent&) bool Plater::priv::init_object_menu() { + items_increase.reserve(2); + items_decrease.reserve(2); + items_set_number_of_copies.reserve(2); + init_common_menu(&object_menu); complit_init_object_menu(); @@ -2514,9 +2706,11 @@ bool Plater::priv::init_object_menu() bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/) { - wxMenuItem* item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), - [this](wxCommandEvent&) { q->remove_selected(); }, "brick_delete.png"); - if (!is_part){ + wxMenuItem* item_delete = nullptr; + if (is_part) { + item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), + [this](wxCommandEvent&) { q->remove_selected(); }, "brick_delete.png"); + } else { wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _(L("Increase copies")) + "\t+", _(L("Place one more copy of the selected object")), [this](wxCommandEvent&) { q->increase_instances(); }, "add.png"); wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _(L("Decrease copies")) + "\t-", _(L("Remove one copy of the selected object")), @@ -2524,6 +2718,14 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _(L("Set number of copies")) + dots, _(L("Change the number of copies of the selected object")), [this](wxCommandEvent&) { q->set_number_of_copies(); }, "textfield.png"); + items_increase.push_back(item_increase); + items_decrease.push_back(item_decrease); + items_set_number_of_copies.push_back(item_set_number_of_copies); + + // Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake. + item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")), + [this](wxCommandEvent&) { q->remove_selected(); }, "brick_delete.png"); + menu->AppendSeparator(); wxMenuItem* item_instance_to_object = sidebar->obj_list()->append_menu_item_instance_to_object(menu); @@ -2559,11 +2761,11 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ wxMenuItem* item_mirror = append_submenu(menu, mirror_menu, wxID_ANY, _(L("Mirror")), _(L("Mirror the selected object"))); - // ui updates needs to be binded to the parent panel + // ui updates needs to be bound to the parent panel if (q != nullptr) { q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_mirror()); }, item_mirror->GetId()); - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete_object()); }, item_delete->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete()); }, item_delete->GetId()); } return true; @@ -2590,7 +2792,7 @@ bool Plater::priv::complit_init_object_menu() { q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split()); }, item_split->GetId()); q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split()); }, item_split_objects->GetId()); - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split()); }, item_split_volumes->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split() && wxGetApp().get_mode() > comSimple); }, item_split_volumes->GetId()); } return true; } @@ -2636,11 +2838,11 @@ bool Plater::priv::complit_init_part_menu() void Plater::priv::init_view_toolbar() { +#if !ENABLE_SVG_ICONS ItemsIconsTexture::Metadata icons_data; icons_data.filename = "view_toolbar.png"; icons_data.icon_size = 64; - icons_data.icon_border_size = 0; - icons_data.icon_gap_size = 0; +#endif // !ENABLE_SVG_ICONS BackgroundTexture::Metadata background_data; background_data.filename = "toolbar_background.png"; @@ -2649,7 +2851,11 @@ void Plater::priv::init_view_toolbar() background_data.right = 16; background_data.bottom = 16; +#if ENABLE_SVG_ICONS + if (!view_toolbar.init(background_data)) +#else if (!view_toolbar.init(icons_data, background_data)) +#endif // ENABLE_SVG_ICONS return; view_toolbar.set_layout_orientation(GLToolbar::Layout::Bottom); @@ -2659,41 +2865,29 @@ void Plater::priv::init_view_toolbar() GLToolbarItem::Data item; item.name = "3D"; +#if ENABLE_SVG_ICONS + item.icon_filename = "editor.svg"; +#endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("3D editor view") + " [" + GUI::shortkey_ctrl_prefix() + "5]"; item.sprite_id = 0; - item.action_event = EVT_GLVIEWTOOLBAR_3D; + item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; item.is_toggable = false; if (!view_toolbar.add_item(item)) return; item.name = "Preview"; +#if ENABLE_SVG_ICONS + item.icon_filename = "preview.svg"; +#endif // ENABLE_SVG_ICONS item.tooltip = GUI::L_str("Preview") + " [" + GUI::shortkey_ctrl_prefix() + "6]"; item.sprite_id = 1; - item.action_event = EVT_GLVIEWTOOLBAR_PREVIEW; + item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; item.is_toggable = false; if (!view_toolbar.add_item(item)) return; - view_toolbar.enable_item("3D"); - view_toolbar.enable_item("Preview"); - view_toolbar.select_item("3D"); view_toolbar.set_enabled(true); - - view3D->set_view_toolbar(&view_toolbar); - preview->set_view_toolbar(&view_toolbar); -} - -bool Plater::priv::can_delete_object() const -{ - int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()); -} - -bool Plater::priv::can_increase_instances() const -{ - int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()); } bool Plater::priv::can_set_instance_to_object() const @@ -2702,27 +2896,6 @@ bool Plater::priv::can_set_instance_to_object() const return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1); } -bool Plater::priv::can_decrease_instances() const -{ - int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1); -} - -bool Plater::priv::can_split_to_objects() const -{ - int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && !model.objects[obj_idx]->is_multiparts(); -} - -bool Plater::priv::can_split_to_volumes() const -{ - if (printer_technology == ptSLA) - return false; -// int obj_idx = get_selected_object_idx(); -// return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && !model.objects[obj_idx]->is_multiparts(); - return sidebar->obj_list()->is_splittable(); -} - bool Plater::priv::can_split() const { return sidebar->obj_list()->is_splittable(); @@ -2734,16 +2907,6 @@ bool Plater::priv::layers_height_allowed() const return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed(); } -bool Plater::priv::can_delete_all() const -{ - return !model.objects.empty(); -} - -bool Plater::priv::can_arrange() const -{ - return !model.objects.empty() && !arranging.load(); -} - bool Plater::priv::can_mirror() const { return get_selection().is_from_single_instance(); @@ -2759,13 +2922,83 @@ void Plater::priv::set_bed_shape(const Pointfs& shape) } } +bool Plater::priv::can_delete() const +{ + return !get_selection().is_empty(); +} + +bool Plater::priv::can_delete_all() const +{ + return !model.objects.empty(); +} + +bool Plater::priv::can_increase_instances() const +{ + int obj_idx = get_selected_object_idx(); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()); +} + +bool Plater::priv::can_decrease_instances() const +{ + int obj_idx = get_selected_object_idx(); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1); +} + +bool Plater::priv::can_split_to_objects() const +{ + return can_split(); +} + +bool Plater::priv::can_split_to_volumes() const +{ + return (printer_technology != ptSLA) && can_split(); +} + +bool Plater::priv::can_arrange() const +{ + return !model.objects.empty() && !arranging.load(); +} + +bool Plater::priv::can_layers_editing() const +{ + return layers_height_allowed(); +} + void Plater::priv::update_object_menu() { sidebar->obj_list()->append_menu_items_add_volume(&object_menu); -#if ENABLE_MODE_AWARE_TOOLBAR_ITEMS - if (view3D != nullptr) - view3D->update_toolbar_items_visibility(); -#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS +} + +void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const +{ + wxWindowUpdateLocker noUpdater(sidebar); + const auto prin_host_opt = config->option("print_host"); + const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty(); + + // when a background processing is ON, export_btn and/or send_btn are showing + if (wxGetApp().app_config->get("background_processing") == "1") + { + sidebar->show_reslice(false); + sidebar->show_export(true); + sidebar->show_send(send_gcode_shown); + } + else + { + sidebar->show_reslice(is_ready_to_slice); + sidebar->show_export(!is_ready_to_slice); + sidebar->show_send(send_gcode_shown && !is_ready_to_slice); + } + sidebar->Layout(); +} + +void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const +{ + switch (btn_type) + { + case ActionButtonType::abReslice: p->btn_reslice->SetLabelText(label); break; + case ActionButtonType::abExport: p->btn_export_gcode->SetLabelText(label); break; + case ActionButtonType::abSendGCode: p->btn_send_gcode->SetLabelText(label); break; + } } // Plater / Public @@ -3094,6 +3327,22 @@ void Plater::reslice() this->p->background_process.set_task(PrintBase::TaskParams()); // Only restarts if the state is valid. this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART); + + if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) + return; + + if (p->background_process.running()) + { + if (wxGetApp().get_mode() == comSimple) + p->sidebar->set_btn_label(ActionButtonType::abReslice, _(L("Slicing")) + dots); + else + { + p->sidebar->set_btn_label(ActionButtonType::abReslice, _(L("Slice now"))); + p->show_action_buttons(false); + } + } + else if (!p->background_process.empty() && !p->background_process.idle()) + p->show_action_buttons(true); } void Plater::reslice_SLA_supports(const ModelObject &object) @@ -3144,7 +3393,7 @@ void Plater::send_gcode() } default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); - PrintHostSendDialog dlg(default_output_file); + PrintHostSendDialog dlg(default_output_file, upload_job.printhost->can_start_print()); if (dlg.ShowModal() == wxID_OK) { upload_job.upload_data.upload_path = dlg.filename(); upload_job.upload_data.start_print = dlg.start_print(); @@ -3157,8 +3406,10 @@ void Plater::on_extruders_change(int num_extruders) { auto& choices = sidebar().combos_filament(); + if (num_extruders == choices.size()) + return; + wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/); -// sidebar().scrolled_panel()->Freeze(); int i = choices.size(); while ( i < num_extruders ) @@ -3198,14 +3449,10 @@ void Plater::on_config_change(const DynamicPrintConfig &config) } else if(opt_key == "variable_layer_height") { if (p->config->opt_bool("variable_layer_height") != true) { - p->view3D->enable_toolbar_item("layersediting", false); p->view3D->enable_layers_editing(false); p->view3D->set_as_dirty(); } - else if (p->view3D->is_layers_editing_allowed()) { - p->view3D->enable_toolbar_item("layersediting", true); - } - } + } else if(opt_key == "extruder_colour") { update_scheduled = true; p->preview->set_number_extruders(p->config->option(opt_key)->values.size()); @@ -3217,9 +3464,6 @@ void Plater::on_config_change(const DynamicPrintConfig &config) bed_shape_changed = true; update_scheduled = true; } - else if (opt_key == "host_type" && this->p->printer_technology == ptSLA) { - p->config->option>(opt_key)->value = htSL1; - } } { @@ -3295,6 +3539,9 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology) } //FIXME for SLA synchronize //p->background_process.apply(Model)! + + p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export"); + p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer"); } void Plater::changed_object(int obj_idx) @@ -3326,4 +3573,13 @@ void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) void Plater::update_object_menu() { p->update_object_menu(); } +bool Plater::can_delete() const { return p->can_delete(); } +bool Plater::can_delete_all() const { return p->can_delete_all(); } +bool Plater::can_increase_instances() const { return p->can_increase_instances(); } +bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); } +bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); } +bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); } +bool Plater::can_arrange() const { return p->can_arrange(); } +bool Plater::can_layers_editing() const { return p->can_layers_editing(); } + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 4d489c82a..f830edce3 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -38,6 +38,7 @@ class GLCanvas3D; using t_optgroups = std::vector >; class Plater; +enum class ActionButtonType : int; class PresetComboBox : public wxBitmapComboBox { @@ -45,14 +46,20 @@ public: PresetComboBox(wxWindow *parent, Preset::Type preset_type); ~PresetComboBox(); - void set_label_marker(int item); + wxButton* edit_btn { nullptr }; + + enum LabelItemType { + LABEL_ITEM_MARKER = 0x4d, + LABEL_ITEM_CONFIG_WIZARD = 0x4e + }; + + void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER); void set_extruder_idx(const int extr_idx) { extruder_idx = extr_idx; } int get_extruder_idx() const { return extruder_idx; } void check_selection(); private: typedef std::size_t Marker; - enum { LABEL_ITEM_MARKER = 0x4d }; Preset::Type preset_type; int last_selected; @@ -61,7 +68,7 @@ private: class Sidebar : public wxPanel { - /*ConfigOptionMode*/int m_mode; + ConfigOptionMode m_mode; public: Sidebar(Plater *parent); Sidebar(Sidebar &&) = delete; @@ -73,12 +80,14 @@ public: void init_filament_combo(PresetComboBox **combo, const int extr_idx); void remove_unused_filament_combos(const int current_extruder_count); void update_presets(Slic3r::Preset::Type preset_type); - void update_mode_sizer(const Slic3r::ConfigOptionMode& mode); + void update_mode_sizer() const; + void update_reslice_btn_tooltip() const; ObjectManipulation* obj_manipul(); ObjectList* obj_list(); ObjectSettings* obj_settings(); wxScrolledWindow* scrolled_panel(); + wxPanel* presets_panel(); ConfigOptionsGroup* og_freq_chng_params(const bool is_fff); wxButton* get_wiping_dialog_button(); @@ -86,10 +95,12 @@ public: void show_info_sizer(); void show_sliced_info_sizer(const bool show); void enable_buttons(bool enable); - void show_reslice(bool show); - void show_send(bool show); + void set_btn_label(const ActionButtonType btn_type, const wxString& label) const; + void show_reslice(bool show) const; + void show_export(bool show) const; + void show_send(bool show) const; bool is_multifilament(); - void set_mode_value(const /*ConfigOptionMode*/int mode) { m_mode = mode; } + void update_mode(); std::vector& combos_filament(); private: @@ -171,6 +182,15 @@ public: PrinterTechnology printer_technology() const; void set_printer_technology(PrinterTechnology printer_technology); + bool can_delete() const; + bool can_delete_all() const; + bool can_increase_instances() const; + bool can_decrease_instances() const; + bool can_split_to_objects() const; + bool can_split_to_volumes() const; + bool can_arrange() const; + bool can_layers_editing() const; + private: struct priv; std::unique_ptr p; diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 67779113c..1aba82ef6 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -511,6 +511,7 @@ const std::vector& Preset::sla_print_options() "support_base_height", "support_critical_angle", "support_max_bridge_length", + "support_max_pillar_link_distance", "support_object_elevation", "support_points_density_relative", "support_points_minimal_distance", @@ -572,6 +573,7 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vector make it visible. + preset.is_visible = true; } else { // Creating a new preset. Preset &preset = *m_presets.insert(it, m_edited_preset); @@ -814,18 +820,20 @@ void PresetCollection::save_current_preset(const std::string &new_name) preset.is_default = false; preset.is_system = false; preset.is_external = false; - } + // The newly saved preset will be activated -> make it visible. + preset.is_visible = true; + } // 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() +bool PresetCollection::delete_current_preset() { const Preset &selected = this->get_selected_preset(); - if (selected.is_default) - return; + if (selected.is_default) + return false; if (! selected.is_external && ! selected.is_system) { // Erase the preset file. boost::nowide::remove(selected.file.c_str()); @@ -839,6 +847,7 @@ void PresetCollection::delete_current_preset() 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); + return true; } bool PresetCollection::load_bitmap_default(const std::string &file_name) @@ -846,6 +855,11 @@ 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); } +bool PresetCollection::load_bitmap_add(const std::string &file_name) +{ + return m_bitmap_add->LoadFile(wxString::FromUTF8(Slic3r::var(file_name).c_str()), wxBITMAP_TYPE_PNG); +} + const Preset* PresetCollection::get_selected_preset_parent() const { const std::string &inherits = this->get_edited_preset().inherits(); @@ -896,7 +910,9 @@ 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() > m_num_default_presets && m_idx_selected > 0); + bool default_visible = ! default_suppressed || m_idx_selected < m_num_default_presets; + for (size_t i = 0; i < m_num_default_presets; ++ i) + m_presets[i].is_visible = default_visible; } } @@ -934,6 +950,7 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) { if (ui == nullptr) return; + // Otherwise fill in the list from scratch. ui->Freeze(); ui->Clear(); @@ -947,8 +964,8 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) std::map nonsys_presets; wxString selected = ""; if (!this->m_presets.front().is_visible) - ui->set_label_marker(ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap)); - for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) { + ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); + for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; 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; @@ -987,20 +1004,45 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } if (i + 1 == m_num_default_presets) - ui->set_label_marker(ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap)); + ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); } if (!nonsys_presets.empty()) { - ui->set_label_marker(ui->Append("------- " + _(L("User presets")) + " -------", wxNullBitmap)); + ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap)); for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { ui->Append(it->first, *it->second); if (it->first == selected) selected_preset_item = ui->GetCount() - 1; } } + if (m_type == Preset::TYPE_PRINTER) { + std::string bitmap_key = ""; + // 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 += "wide,"; + bitmap_key += "add_printer"; + wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(m_bitmap_cache->mkclear(16, 16)); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(4, 16)); + bmps.emplace_back(*m_bitmap_main_frame); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(6, 16)); + bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add a new printer")), *bmp), GUI::PresetComboBox::LABEL_ITEM_CONFIG_WIZARD); + } ui->SetSelection(selected_preset_item); ui->SetToolTip(ui->GetString(selected_preset_item)); + ui->check_selection(); ui->Thaw(); } @@ -1015,7 +1057,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati std::map nonsys_presets; wxString selected = ""; if (!this->m_presets.front().is_visible) - ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); + ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; 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)) @@ -1046,18 +1088,29 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati if (i == m_idx_selected) selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); } - if (i + 1 == m_num_default_presets) - ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap); + if (i + 1 == m_num_default_presets) + ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); } if (!nonsys_presets.empty()) { - ui->Append("------- " + _(L("User presets")) + " -------", wxNullBitmap); + ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap); for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { ui->Append(it->first, *it->second); if (it->first == selected) selected_preset_item = ui->GetCount() - 1; } } + if (m_type == Preset::TYPE_PRINTER) { + wxBitmap *bmp = m_bitmap_cache->find("add_printer_tab"); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + bmps.emplace_back(*m_bitmap_main_frame); + bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap); + bmp = m_bitmap_cache->insert("add_printer_tab", bmps); + } + ui->Append(PresetCollection::separator("Add a new printer"), *bmp); + } ui->SetSelection(selected_preset_item); ui->SetToolTip(ui->GetString(selected_preset_item)); ui->Thaw(); @@ -1166,7 +1219,9 @@ Preset& PresetCollection::select_preset(size_t idx) 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; + bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets; + for (size_t i = 0; i < m_num_default_presets; ++i) + m_presets[i].is_visible = default_visible; return m_presets[idx]; } @@ -1284,6 +1339,11 @@ std::string PresetCollection::path_from_name(const std::string &new_name) const return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } +wxString PresetCollection::separator(const std::string &label) +{ + return wxString::FromUTF8(PresetCollection::separator_head()) + _(label) + wxString::FromUTF8(PresetCollection::separator_tail()); +} + const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const { const ConfigOptionEnumGeneric *opt_printer_technology = config.opt("printer_technology"); diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp index 75303c704..716ca7854 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/slic3r/GUI/Preset.hpp @@ -11,9 +11,10 @@ #include "slic3r/Utils/Semver.hpp" class wxBitmap; -class wxChoice; class wxBitmapComboBox; +class wxChoice; class wxItemContainer; +class wxString; namespace Slic3r { @@ -117,6 +118,8 @@ public: // Is this preset compatible with the currently active printer? bool is_compatible = true; + bool is_user() const { return ! this->is_default && ! this->is_system; } + // 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, @@ -203,6 +206,7 @@ public: static const std::vector& sla_print_options(); static void update_suffix_modified(); + static const std::string& suffix_modified(); static void normalize(DynamicPrintConfig &config); // Report configuration fields, which are misplaced into a wrong group, remove them from the config. static std::string remove_invalid_keys(DynamicPrintConfig &config, const DynamicPrintConfig &default_config); @@ -212,7 +216,6 @@ protected: friend class PresetBundle; // 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); }; @@ -271,11 +274,15 @@ public: void save_current_preset(const std::string &new_name); // Delete the current preset, activate the first visible preset. - void delete_current_preset(); + // returns true if the preset was deleted successfully. + bool delete_current_preset(); // Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame. bool load_bitmap_default(const std::string &file_name); + // Load "add new printer" bitmap to be placed at the wxBitmapComboBox of a MainFrame. + bool load_bitmap_add(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; } @@ -406,6 +413,15 @@ public: // 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; +#ifdef __linux__ + static const char* separator_head() { return "------- "; } + static const char* separator_tail() { return " -------"; } +#else /* __linux__ */ + static const char* separator_head() { return "————— "; } + static const char* separator_tail() { return " —————"; } +#endif /* __linux__ */ + static wxString separator(const std::string &label); + protected: // Select a preset, if it exists. If it does not exist, select an invalid (-1) index. // This is a temporary state, which shall be fixed immediately by the following step. @@ -466,6 +482,8 @@ private: // Marks placed at the wxBitmapComboBox of a MainFrame. // These bitmaps are owned by PresetCollection. wxBitmap *m_bitmap_main_frame; + // "Add printer profile" icon, owned by PresetCollection. + wxBitmap *m_bitmap_add; // Path to the directory to store the config files into. std::string m_dir_path; diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index bf166143c..c81d4425e 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -107,6 +107,7 @@ PresetBundle::PresetBundle() : this->filaments .load_bitmap_default("spool.png"); this->sla_materials.load_bitmap_default("package_green.png"); this->printers .load_bitmap_default("printer_empty.png"); + this->printers .load_bitmap_add("add.png"); this->load_compatible_bitmaps(); // Re-activate the default presets, so their "edited" preset copies will be updated with the additional configuration values above. @@ -1289,7 +1290,7 @@ void PresetBundle::update_compatible(bool select_other_if_incompatible) { const Preset &printer_preset = this->printers.get_edited_preset(); - switch (printers.get_edited_preset().printer_technology()) { + switch (printer_preset.printer_technology()) { case ptFFF: { assert(printer_preset.config.has("default_print_profile")); @@ -1308,19 +1309,23 @@ void PresetBundle::update_compatible(bool select_other_if_incompatible) { return std::find(prefered_filament_profiles.begin(), prefered_filament_profiles.end(), profile_name) != prefered_filament_profiles.end(); }); if (select_other_if_incompatible) { // Verify validity of the current filament presets. - this->filament_presets.front() = this->filaments.get_edited_preset().name; - for (size_t idx = 1; idx < this->filament_presets.size(); ++ idx) { - std::string &filament_name = this->filament_presets[idx]; - Preset *preset = this->filaments.find_preset(filament_name, false); - if (preset == nullptr || ! preset->is_compatible) { - // Pick a compatible profile. If there are prefered_filament_profiles, use them. - if (prefered_filament_profiles.empty()) - filament_name = this->filaments.first_compatible().name; - else { - const std::string &preferred = (idx < prefered_filament_profiles.size()) ? - prefered_filament_profiles[idx] : prefered_filament_profiles.front(); - filament_name = this->filaments.first_compatible( - [&preferred](const std::string& profile_name) { return profile_name == preferred; }).name; + if (this->filament_presets.size() == 1) + this->filament_presets.front() = this->filaments.get_edited_preset().name; + else + { + for (size_t idx = 0; idx < this->filament_presets.size(); ++idx) { + std::string &filament_name = this->filament_presets[idx]; + Preset *preset = this->filaments.find_preset(filament_name, false); + if (preset == nullptr || !preset->is_compatible) { + // Pick a compatible profile. If there are prefered_filament_profiles, use them. + if (prefered_filament_profiles.empty()) + filament_name = this->filaments.first_compatible().name; + else { + const std::string &preferred = (idx < prefered_filament_profiles.size()) ? + prefered_filament_profiles[idx] : prefered_filament_profiles.front(); + filament_name = this->filaments.first_compatible( + [&preferred](const std::string& profile_name) { return profile_name == preferred; }).name; + } } } } @@ -1401,7 +1406,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst // an optional "(modified)" suffix will be removed from the filament name. void PresetBundle::set_filament_preset(size_t idx, const std::string &name) { - if (name.find_first_of("-------") == 0) + if (name.find_first_of(PresetCollection::separator_head()) == 0) return; if (idx >= filament_presets.size()) @@ -1457,7 +1462,7 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr std::map nonsys_presets; wxString selected_str = ""; if (!this->filaments().front().is_visible) - ui->set_label_marker(ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap)); + ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); 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; @@ -1510,12 +1515,12 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr selected_str = wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()); } if (preset.is_default) - ui->set_label_marker(ui->Append("------- " + _(L("System presets")) + " -------", wxNullBitmap)); + ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); } if (!nonsys_presets.empty()) { - ui->set_label_marker(ui->Append("------- " + _(L("User presets")) + " -------", wxNullBitmap)); + ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap)); for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { ui->Append(it->first, *it->second); if (it->first == selected_str) @@ -1524,6 +1529,7 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr } ui->SetSelection(selected_preset_item); ui->SetToolTip(ui->GetString(selected_preset_item)); + ui->check_selection(); ui->Thaw(); } diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 1ec1c3a6a..76185b1ba 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -28,14 +28,15 @@ namespace GUI { static const char *CONFIG_KEY_PATH = "printhost_path"; static const char *CONFIG_KEY_PRINT = "printhost_print"; -PrintHostSendDialog::PrintHostSendDialog(const fs::path &path) +PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print) : MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE) , txt_filename(new wxTextCtrl(this, wxID_ANY)) - , box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))) + , box_print(can_start_print ? new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload"))) : nullptr) { #ifdef __APPLE__ txt_filename->OSXDisableAllSmartSubstitutions(); #endif + const AppConfig *app_config = wxGetApp().app_config; auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed."))); label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit()); @@ -43,12 +44,13 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path) content_sizer->Add(txt_filename, 0, wxEXPAND); content_sizer->Add(label_dir_hint); content_sizer->AddSpacer(VERT_SPACING); - content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING); + if (box_print != nullptr) { + content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING); + box_print->SetValue(app_config->get("recent", CONFIG_KEY_PRINT) == "1"); + } btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL)); - const AppConfig *app_config = wxGetApp().app_config; - box_print->SetValue(app_config->get("recent", CONFIG_KEY_PRINT) == "1"); wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH)); if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') { @@ -80,7 +82,7 @@ fs::path PrintHostSendDialog::filename() const bool PrintHostSendDialog::start_print() const { - return box_print->GetValue(); + return box_print != nullptr ? box_print->GetValue() : false; } void PrintHostSendDialog::EndModal(int ret) @@ -94,8 +96,7 @@ void PrintHostSendDialog::EndModal(int ret) wxGetApp().app_config->set("recent", CONFIG_KEY_PATH, into_u8(path)); } - bool print = box_print->GetValue(); - GUI::get_app_config()->set("recent", CONFIG_KEY_PRINT, print ? "1" : "0"); + GUI::get_app_config()->set("recent", CONFIG_KEY_PRINT, start_print() ? "1" : "0"); } MsgDialog::EndModal(ret); diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp index e7c84fefd..105899cf0 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -29,7 +29,7 @@ namespace GUI { class PrintHostSendDialog : public GUI::MsgDialog { public: - PrintHostSendDialog(const boost::filesystem::path &path); + PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print); boost::filesystem::path filename() const; bool start_print() const; diff --git a/src/slic3r/GUI/ProgressStatusBar.cpp b/src/slic3r/GUI/ProgressStatusBar.cpp index 8cdb53c9d..83c5e5b24 100644 --- a/src/slic3r/GUI/ProgressStatusBar.cpp +++ b/src/slic3r/GUI/ProgressStatusBar.cpp @@ -5,8 +5,11 @@ #include #include #include + #include "GUI_App.hpp" +#include "I18N.hpp" + #include namespace Slic3r { @@ -22,7 +25,7 @@ ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id): wxDefaultSize)), m_cancelbutton(new wxButton(self, -1, - "Cancel", + _(L("Cancel")), wxDefaultPosition, wxDefaultSize)) { @@ -33,6 +36,9 @@ ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id): int w[] = {-1, 150, 155}; self->SetStatusWidths(3, w); + wxSize s = m_cancelbutton->GetTextExtent(m_cancelbutton->GetLabel()); + self->SetMinHeight(int(2 * self->GetBorderY() + 1.2 * s.GetHeight())); + self->Bind(wxEVT_TIMER, [this](const wxTimerEvent&) { if (m_prog->IsShown()) m_timer->Stop(); if(is_busy()) m_prog->Pulse(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp new file mode 100644 index 000000000..e5d221a5f --- /dev/null +++ b/src/slic3r/GUI/Selection.cpp @@ -0,0 +1,1670 @@ +#include "libslic3r/libslic3r.h" +#include "Selection.hpp" + +#include "GLCanvas3D.hpp" +#include "GUI_App.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "GUI_ObjectList.hpp" +#include "Gizmos/GLGizmoBase.hpp" + +#include + +#include + +static const float UNIFORM_SCALE_COLOR[3] = { 1.0f, 0.38f, 0.0f }; + +namespace Slic3r { +namespace GUI { + +Selection::VolumeCache::TransformCache::TransformCache() + : position(Vec3d::Zero()) + , rotation(Vec3d::Zero()) + , scaling_factor(Vec3d::Ones()) + , mirror(Vec3d::Ones()) + , rotation_matrix(Transform3d::Identity()) + , scale_matrix(Transform3d::Identity()) + , mirror_matrix(Transform3d::Identity()) + , full_matrix(Transform3d::Identity()) +{ +} + +Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) + : position(transform.get_offset()) + , rotation(transform.get_rotation()) + , scaling_factor(transform.get_scaling_factor()) + , mirror(transform.get_mirror()) + , full_matrix(transform.get_matrix()) +{ + rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); + scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); + mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); +} + +Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) + : m_volume(volume_transform) + , m_instance(instance_transform) +{ +} + +Selection::Selection() + : m_volumes(nullptr) + , m_model(nullptr) + , m_mode(Instance) + , m_type(Empty) + , m_valid(false) + , m_bounding_box_dirty(true) + , m_curved_arrow(16) + , m_scale_factor(1.0f) +{ +#if ENABLE_RENDER_SELECTION_CENTER + m_quadric = ::gluNewQuadric(); + if (m_quadric != nullptr) + ::gluQuadricDrawStyle(m_quadric, GLU_FILL); +#endif // ENABLE_RENDER_SELECTION_CENTER +} + +#if ENABLE_RENDER_SELECTION_CENTER +Selection::~Selection() +{ + if (m_quadric != nullptr) + ::gluDeleteQuadric(m_quadric); +} +#endif // ENABLE_RENDER_SELECTION_CENTER + +void Selection::set_volumes(GLVolumePtrs* volumes) +{ + m_volumes = volumes; + _update_valid(); +} + +bool Selection::init(bool useVBOs) +{ + if (!m_arrow.init(useVBOs)) + return false; + + m_arrow.set_scale(5.0 * Vec3d::Ones()); + + if (!m_curved_arrow.init(useVBOs)) + return false; + + m_curved_arrow.set_scale(5.0 * Vec3d::Ones()); + return true; +} + +void Selection::set_model(Model* model) +{ + m_model = model; + _update_valid(); +} + +void Selection::add(unsigned int volume_idx, bool as_single_selection) +{ + if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) + return; + + const GLVolume* volume = (*m_volumes)[volume_idx]; + // wipe tower is already selected + if (is_wipe_tower() && volume->is_wipe_tower) + return; + + // resets the current list if needed + bool needs_reset = as_single_selection; + needs_reset |= volume->is_wipe_tower; + needs_reset |= is_wipe_tower() && !volume->is_wipe_tower; + needs_reset |= !is_modifier() && volume->is_modifier; + needs_reset |= is_modifier() && !volume->is_modifier; + + if (needs_reset) + clear(); + + if (volume->is_modifier) + m_mode = Volume; + else if (!contains_volume(volume_idx)) + m_mode = Instance; + // else -> keep current mode + + switch (m_mode) + { + case Volume: + { + if (volume->volume_idx() >= 0 && (is_empty() || (volume->instance_idx() == get_instance_idx()))) + _add_volume(volume_idx); + + break; + } + case Instance: + { + _add_instance(volume->object_idx(), volume->instance_idx()); + break; + } + } + + _update_type(); + m_bounding_box_dirty = true; +} + +void Selection::remove(unsigned int volume_idx) +{ + if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) + return; + + GLVolume* volume = (*m_volumes)[volume_idx]; + + switch (m_mode) + { + case Volume: + { + _remove_volume(volume_idx); + break; + } + case Instance: + { + _remove_instance(volume->object_idx(), volume->instance_idx()); + break; + } + } + + _update_type(); + m_bounding_box_dirty = true; +} + +void Selection::add_object(unsigned int object_idx, bool as_single_selection) +{ + if (!m_valid) + return; + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = Instance; + + _add_object(object_idx); + + _update_type(); + m_bounding_box_dirty = true; +} + +void Selection::remove_object(unsigned int object_idx) +{ + if (!m_valid) + return; + + _remove_object(object_idx); + + _update_type(); + m_bounding_box_dirty = true; +} + +void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, bool as_single_selection) +{ + if (!m_valid) + return; + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = Instance; + + _add_instance(object_idx, instance_idx); + + _update_type(); + m_bounding_box_dirty = true; +} + +void Selection::remove_instance(unsigned int object_idx, unsigned int instance_idx) +{ + if (!m_valid) + return; + + _remove_instance(object_idx, instance_idx); + + _update_type(); + m_bounding_box_dirty = true; +} + +void Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection) +{ + if (!m_valid) + return; + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = Volume; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->volume_idx() == volume_idx)) + { + if ((instance_idx != -1) && (v->instance_idx() == instance_idx)) + _add_volume(i); + } + } + + _update_type(); + m_bounding_box_dirty = true; +} + +void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) +{ + if (!m_valid) + return; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->volume_idx() == volume_idx)) + _remove_volume(i); + } + + _update_type(); + m_bounding_box_dirty = true; +} + +void Selection::add_all() +{ + if (!m_valid) + return; + + m_mode = Instance; + clear(); + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + if (!(*m_volumes)[i]->is_wipe_tower) + _add_volume(i); + } + + _update_type(); + m_bounding_box_dirty = true; +} + +void Selection::clear() +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) + { + (*m_volumes)[i]->selected = false; + } + + m_list.clear(); + + _update_type(); + m_bounding_box_dirty = true; + + // resets the cache in the sidebar + wxGetApp().obj_manipul()->reset_cache(); +} + +// Update the selection based on the map from old indices to new indices after m_volumes changed. +// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances. +void Selection::volumes_changed(const std::vector &map_volume_old_to_new) +{ + assert(m_valid); + + // 1) Update the selection set. + IndicesList list_new; + std::vector> model_instances; + for (unsigned int idx : m_list) { + if (map_volume_old_to_new[idx] != size_t(-1)) { + unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx]; + list_new.insert(new_idx); + if (m_mode == Instance) { + // Save the object_idx / instance_idx pair of selected old volumes, + // so we may add the newly added volumes of the same object_idx / instance_idx pair + // to the selection. + const GLVolume *volume = (*m_volumes)[new_idx]; + model_instances.emplace_back(volume->object_idx(), volume->instance_idx()); + } + } + } + m_list = std::move(list_new); + + if (!model_instances.empty()) { + // Instance selection mode. Add the newly added volumes of the same object_idx / instance_idx pair + // to the selection. + assert(m_mode == Instance); + sort_remove_duplicates(model_instances); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + const GLVolume* volume = (*m_volumes)[i]; + for (const std::pair &model_instance : model_instances) + if (volume->object_idx() == model_instance.first && volume->instance_idx() == model_instance.second) + this->_add_volume(i); + } + } + + _update_type(); + m_bounding_box_dirty = true; +} + +bool Selection::is_single_full_instance() const +{ + if (m_type == SingleFullInstance) + return true; + + if (m_type == SingleFullObject) + return get_instance_idx() != -1; + + if (m_list.empty() || m_volumes->empty()) + return false; + + int object_idx = m_valid ? get_object_idx() : -1; + if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) + return false; + + int instance_idx = (*m_volumes)[*m_list.begin()]->instance_idx(); + + std::set volumes_idxs; + for (unsigned int i : m_list) + { + const GLVolume* v = (*m_volumes)[i]; + if ((object_idx != v->object_idx()) || (instance_idx != v->instance_idx())) + return false; + + int volume_idx = v->volume_idx(); + if (volume_idx >= 0) + volumes_idxs.insert(volume_idx); + } + + return m_model->objects[object_idx]->volumes.size() == volumes_idxs.size(); +} + +bool Selection::is_from_single_object() const +{ + int idx = get_object_idx(); + return (0 <= idx) && (idx < 1000); +} + +bool Selection::requires_uniform_scale() const +{ + if (is_single_full_instance() || is_single_modifier() || is_single_volume()) + return false; + + return true; +} + +int Selection::get_object_idx() const +{ + return (m_cache.content.size() == 1) ? m_cache.content.begin()->first : -1; +} + +int Selection::get_instance_idx() const +{ + if (m_cache.content.size() == 1) + { + const InstanceIdxsList& idxs = m_cache.content.begin()->second; + if (idxs.size() == 1) + return *idxs.begin(); + } + + return -1; +} + +const Selection::InstanceIdxsList& Selection::get_instance_idxs() const +{ + assert(m_cache.content.size() == 1); + return m_cache.content.begin()->second; +} + +const GLVolume* Selection::get_volume(unsigned int volume_idx) const +{ + return (m_valid && (volume_idx < (unsigned int)m_volumes->size())) ? (*m_volumes)[volume_idx] : nullptr; +} + +const BoundingBoxf3& Selection::get_bounding_box() const +{ + if (m_bounding_box_dirty) + _calc_bounding_box(); + + return m_bounding_box; +} + +void Selection::start_dragging() +{ + if (!m_valid) + return; + + _set_caches(); +} + +void Selection::translate(const Vec3d& displacement, bool local) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) + { + if ((m_mode == Volume) || (*m_volumes)[i]->is_wipe_tower) + { + if (local) + (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); + else + { + Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); + } + } + else if (m_mode == Instance) + (*m_volumes)[i]->set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + _synchronize_unselected_instances(SYNC_ROTATION_NONE); + else if (m_mode == Volume) + _synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + m_bounding_box_dirty = true; +} + +static Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) +{ + return + // From the current coordinate system to world. + Eigen::AngleAxisd(rot_xyz_to(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(rot_xyz_to(1), Vec3d::UnitY()) * Eigen::AngleAxisd(rot_xyz_to(0), Vec3d::UnitX()) * + // From world to the initial coordinate system. + Eigen::AngleAxisd(-rot_xyz_from(0), Vec3d::UnitX()) * Eigen::AngleAxisd(-rot_xyz_from(1), Vec3d::UnitY()) * Eigen::AngleAxisd(-rot_xyz_from(2), Vec3d::UnitZ()); +} + +// This should only be called if it is known, that the two rotations only differ in rotation around the Z axis. +static double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) +{ + Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); + Vec3d axis = angle_axis.axis(); + double angle = angle_axis.angle(); +#ifndef NDEBUG + if (std::abs(angle) > 1e-8) { + assert(std::abs(axis.x()) < 1e-8); + assert(std::abs(axis.y()) < 1e-8); + } +#endif /* NDEBUG */ + return (axis.z() < 0) ? -angle : angle; +} + +// Rotate an object around one of the axes. Only one rotation component is expected to be changing. +void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) +{ + if (!m_valid) + return; + + // Only relative rotation values are allowed in the world coordinate system. + assert(!transformation_type.world() || transformation_type.relative()); + + int rot_axis_max = 0; + if (rotation.isApprox(Vec3d::Zero())) + { + for (unsigned int i : m_list) + { + GLVolume &volume = *(*m_volumes)[i]; + if (m_mode == Instance) + { + volume.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation()); + volume.set_instance_offset(m_cache.volumes_data[i].get_instance_position()); + } + else if (m_mode == Volume) + { + volume.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation()); + volume.set_volume_offset(m_cache.volumes_data[i].get_volume_position()); + } + } + } + else + { + //FIXME this does not work for absolute rotations (transformation_type.absolute() is true) + rotation.cwiseAbs().maxCoeff(&rot_axis_max); + + // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. + std::vector object_instance_first(m_model->objects.size(), -1); + auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { + int first_volume_idx = object_instance_first[volume.object_idx()]; + if (rot_axis_max != 2 && first_volume_idx != -1) { + // Generic rotation, but no rotation around the Z axis. + // Always do a local rotation (do not consider the selection to be a rigid body). + assert(is_approx(rotation.z(), 0.0)); + const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; + const Vec3d &rotation = first_volume.get_instance_rotation(); + double z_diff = rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); + volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + } + else { + // extracts rotations from the composed transformation + Vec3d new_rotation = transformation_type.world() ? + Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : + transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); + if (rot_axis_max == 2 && transformation_type.joint()) { + // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. + Vec3d offset = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, new_rotation(2) - m_cache.volumes_data[i].get_instance_rotation()(2))) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); + volume.set_instance_offset(m_cache.dragging_center + offset); + } + volume.set_instance_rotation(new_rotation); + object_instance_first[volume.object_idx()] = i; + } + }; + + for (unsigned int i : m_list) + { + GLVolume &volume = *(*m_volumes)[i]; + if (is_single_full_instance()) + rotate_instance(volume, i); + else if (is_single_volume() || is_single_modifier()) + { + if (transformation_type.independent()) + volume.set_volume_rotation(volume.get_volume_rotation() + rotation); + else + { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + volume.set_volume_rotation(new_rotation); + } + } + else + { + if (m_mode == Instance) + rotate_instance(volume, i); + else if (m_mode == Volume) + { + // extracts rotations from the composed transformation + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + if (transformation_type.joint()) + { + Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; + Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); + volume.set_volume_offset(local_pivot + offset); + } + volume.set_volume_rotation(new_rotation); + } + } + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + _synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); + else if (m_mode == Volume) + _synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + m_bounding_box_dirty = true; +} + +void Selection::flattening_rotate(const Vec3d& normal) +{ + // We get the normal in untransformed coordinates. We must transform it using the instance matrix, find out + // how to rotate the instance so it faces downwards and do the rotation. All that for all selected instances. + // The function assumes that is_from_single_object() holds. + + if (!m_valid) + return; + + for (unsigned int i : m_list) + { + Transform3d wst = m_cache.volumes_data[i].get_instance_scale_matrix(); + Vec3d scaling_factor = Vec3d(1. / wst(0, 0), 1. / wst(1, 1), 1. / wst(2, 2)); + + Transform3d wmt = m_cache.volumes_data[i].get_instance_mirror_matrix(); + Vec3d mirror(wmt(0, 0), wmt(1, 1), wmt(2, 2)); + + Vec3d rotation = Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix()); + Vec3d transformed_normal = Geometry::assemble_transform(Vec3d::Zero(), rotation, scaling_factor, mirror) * normal; + transformed_normal.normalize(); + + Vec3d axis = transformed_normal(2) > 0.999f ? Vec3d(1., 0., 0.) : Vec3d(transformed_normal.cross(Vec3d(0., 0., -1.))); + axis.normalize(); + + Transform3d extra_rotation = Transform3d::Identity(); + extra_rotation.rotate(Eigen::AngleAxisd(acos(-transformed_normal(2)), axis)); + + Vec3d new_rotation = Geometry::extract_euler_angles(extra_rotation * m_cache.volumes_data[i].get_instance_rotation_matrix()); + (*m_volumes)[i]->set_instance_rotation(new_rotation); + } + +#if !DISABLE_INSTANCES_SYNCH + // we want to synchronize z-rotation as well, otherwise the flattening behaves funny + // when applied on one of several identical instances + if (m_mode == Instance) + _synchronize_unselected_instances(SYNC_ROTATION_FULL); +#endif // !DISABLE_INSTANCES_SYNCH + + m_bounding_box_dirty = true; +} + +void Selection::scale(const Vec3d& scale, bool local) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) + { + if (is_single_full_instance()) + (*m_volumes)[i]->set_instance_scaling_factor(scale); + else if (is_single_volume() || is_single_modifier()) + (*m_volumes)[i]->set_volume_scaling_factor(scale); + else + { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); + if (m_mode == Instance) + { + Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); + // extracts scaling factors from the composed transformation + Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + if (!local) + (*m_volumes)[i]->set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + + (*m_volumes)[i]->set_instance_scaling_factor(new_scale); + } + else if (m_mode == Volume) + { + Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); + // extracts scaling factors from the composed transformation + Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + if (!local) + { + Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); + (*m_volumes)[i]->set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); + } + (*m_volumes)[i]->set_volume_scaling_factor(new_scale); + } + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + _synchronize_unselected_instances(SYNC_ROTATION_NONE); + else if (m_mode == Volume) + _synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + _ensure_on_bed(); + + m_bounding_box_dirty = true; +} + +void Selection::mirror(Axis axis) +{ + if (!m_valid) + return; + + bool single_full_instance = is_single_full_instance(); + + for (unsigned int i : m_list) + { + if (single_full_instance) + (*m_volumes)[i]->set_instance_mirror(axis, -(*m_volumes)[i]->get_instance_mirror(axis)); + else if (m_mode == Volume) + (*m_volumes)[i]->set_volume_mirror(axis, -(*m_volumes)[i]->get_volume_mirror(axis)); + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + _synchronize_unselected_instances(SYNC_ROTATION_NONE); + else if (m_mode == Volume) + _synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + m_bounding_box_dirty = true; +} + +void Selection::translate(unsigned int object_idx, const Vec3d& displacement) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) + { + GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == object_idx) + v->set_instance_offset(v->get_instance_offset() + displacement); + } + + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) + { + if (done.size() == m_volumes->size()) + break; + + int object_idx = (*m_volumes)[i]->object_idx(); + if (object_idx >= 1000) + continue; + + // Process unselected volumes of the object. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) + { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume* v = (*m_volumes)[j]; + if (v->object_idx() != object_idx) + continue; + + v->set_instance_offset(v->get_instance_offset() + displacement); + done.insert(j); + } + } + + m_bounding_box_dirty = true; +} + +void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) + { + GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) + v->set_instance_offset(v->get_instance_offset() + displacement); + } + + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) + { + if (done.size() == m_volumes->size()) + break; + + int object_idx = (*m_volumes)[i]->object_idx(); + if (object_idx >= 1000) + continue; + + // Process unselected volumes of the object. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) + { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume* v = (*m_volumes)[j]; + if ((v->object_idx() != object_idx) || (v->instance_idx() != instance_idx)) + continue; + + v->set_instance_offset(v->get_instance_offset() + displacement); + done.insert(j); + } + } + + m_bounding_box_dirty = true; +} + +void Selection::erase() +{ + if (!m_valid) + return; + + if (is_single_full_object()) + wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itObject, get_object_idx(), 0); + else if (is_multiple_full_object()) + { + std::vector items; + items.reserve(m_cache.content.size()); + for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) + { + items.emplace_back(ItemType::itObject, it->first, 0); + } + wxGetApp().obj_list()->delete_from_model_and_list(items); + } + else if (is_multiple_full_instance()) + { + std::set> instances_idxs; + for (ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.begin(); obj_it != m_cache.content.end(); ++obj_it) + { + for (InstanceIdxsList::reverse_iterator inst_it = obj_it->second.rbegin(); inst_it != obj_it->second.rend(); ++inst_it) + { + instances_idxs.insert(std::make_pair(obj_it->first, *inst_it)); + } + } + + std::vector items; + items.reserve(instances_idxs.size()); + for (const std::pair& i : instances_idxs) + { + items.emplace_back(ItemType::itInstance, i.first, i.second); + } + wxGetApp().obj_list()->delete_from_model_and_list(items); + } + else if (is_single_full_instance()) + wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itInstance, get_object_idx(), get_instance_idx()); + else if (is_mixed()) + { + std::set items_set; + std::map volumes_in_obj; + + for (auto i : m_list) { + const auto gl_vol = (*m_volumes)[i]; + const auto glv_obj_idx = gl_vol->object_idx(); + const auto model_object = m_model->objects[glv_obj_idx]; + + if (model_object->instances.size() == 1) { + if (model_object->volumes.size() == 1) + items_set.insert(ItemForDelete(ItemType::itObject, glv_obj_idx, -1)); + else { + items_set.insert(ItemForDelete(ItemType::itVolume, glv_obj_idx, gl_vol->volume_idx())); + int idx = (volumes_in_obj.find(glv_obj_idx) == volumes_in_obj.end()) ? 0 : volumes_in_obj.at(glv_obj_idx); + volumes_in_obj[glv_obj_idx] = ++idx; + } + continue; + } + + const auto glv_ins_idx = gl_vol->instance_idx(); + + for (auto obj_ins : m_cache.content) { + if (obj_ins.first == glv_obj_idx) { + if (obj_ins.second.find(glv_ins_idx) != obj_ins.second.end()) { + if (obj_ins.second.size() == model_object->instances.size()) + items_set.insert(ItemForDelete(ItemType::itVolume, glv_obj_idx, gl_vol->volume_idx())); + else + items_set.insert(ItemForDelete(ItemType::itInstance, glv_obj_idx, glv_ins_idx)); + + break; + } + } + } + } + + std::vector items; + items.reserve(items_set.size()); + for (const ItemForDelete& i : items_set) { + if (i.type == ItemType::itVolume) { + const int vol_in_obj_cnt = volumes_in_obj.find(i.obj_idx) == volumes_in_obj.end() ? 0 : volumes_in_obj.at(i.obj_idx); + if (vol_in_obj_cnt == m_model->objects[i.obj_idx]->volumes.size()) { + if (i.sub_obj_idx == vol_in_obj_cnt - 1) + items.emplace_back(ItemType::itObject, i.obj_idx, 0); + continue; + } + } + items.emplace_back(i.type, i.obj_idx, i.sub_obj_idx); + } + + wxGetApp().obj_list()->delete_from_model_and_list(items); + } + else + { + std::set> volumes_idxs; + for (unsigned int i : m_list) + { + const GLVolume* v = (*m_volumes)[i]; + // Only remove volumes associated with ModelVolumes from the object list. + // Temporary meshes (SLA supports or pads) are not managed by the object list. + if (v->volume_idx() >= 0) + volumes_idxs.insert(std::make_pair(v->object_idx(), v->volume_idx())); + } + + std::vector items; + items.reserve(volumes_idxs.size()); + for (const std::pair& v : volumes_idxs) + { + items.emplace_back(ItemType::itVolume, v.first, v.second); + } + + wxGetApp().obj_list()->delete_from_model_and_list(items); + } +} + +void Selection::render(float scale_factor) const +{ + if (!m_valid || is_empty()) + return; + + m_scale_factor = scale_factor; + + // render cumulative bounding box of selected volumes + _render_selected_volumes(); + _render_synchronized_volumes(); +} + +#if ENABLE_RENDER_SELECTION_CENTER +void Selection::render_center() const +{ + if (!m_valid || is_empty() || (m_quadric == nullptr)) + return; + + const Vec3d& center = get_bounding_box().center(); + + ::glDisable(GL_DEPTH_TEST); + + ::glEnable(GL_LIGHTING); + + ::glColor3f(1.0f, 1.0f, 1.0f); + ::glPushMatrix(); + ::glTranslated(center(0), center(1), center(2)); + ::gluSphere(m_quadric, 0.75, 32, 32); + ::glPopMatrix(); + + ::glDisable(GL_LIGHTING); +} +#endif // ENABLE_RENDER_SELECTION_CENTER + +void Selection::render_sidebar_hints(const std::string& sidebar_field) const +{ + if (sidebar_field.empty()) + return; + + ::glClear(GL_DEPTH_BUFFER_BIT); + ::glEnable(GL_DEPTH_TEST); + + ::glEnable(GL_LIGHTING); + + ::glPushMatrix(); + + const Vec3d& center = get_bounding_box().center(); + + if (is_single_full_instance()) + { + ::glTranslated(center(0), center(1), center(2)); + if (!boost::starts_with(sidebar_field, "position")) + { + Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + ::glMultMatrixd(orient_matrix.data()); + } + } + else if (is_single_volume() || is_single_modifier()) + { + ::glTranslated(center(0), center(1), center(2)); + Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + if (!boost::starts_with(sidebar_field, "position")) + orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); + + ::glMultMatrixd(orient_matrix.data()); + } + else + { + ::glTranslated(center(0), center(1), center(2)); + if (requires_local_axes()) + { + Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + ::glMultMatrixd(orient_matrix.data()); + } + } + + if (boost::starts_with(sidebar_field, "position")) + _render_sidebar_position_hints(sidebar_field); + else if (boost::starts_with(sidebar_field, "rotation")) + _render_sidebar_rotation_hints(sidebar_field); + else if (boost::starts_with(sidebar_field, "scale")) + _render_sidebar_scale_hints(sidebar_field); + else if (boost::starts_with(sidebar_field, "size")) + _render_sidebar_size_hints(sidebar_field); + + ::glPopMatrix(); + + ::glDisable(GL_LIGHTING); +} + +bool Selection::requires_local_axes() const +{ + return (m_mode == Volume) && is_from_single_instance(); +} + +void Selection::_update_valid() +{ + m_valid = (m_volumes != nullptr) && (m_model != nullptr); +} + +void Selection::_update_type() +{ + m_cache.content.clear(); + m_type = Mixed; + + for (unsigned int i : m_list) + { + const GLVolume* volume = (*m_volumes)[i]; + int obj_idx = volume->object_idx(); + int inst_idx = volume->instance_idx(); + ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.find(obj_idx); + if (obj_it == m_cache.content.end()) + obj_it = m_cache.content.insert(ObjectIdxsToInstanceIdxsMap::value_type(obj_idx, InstanceIdxsList())).first; + + obj_it->second.insert(inst_idx); + } + + bool requires_disable = false; + + if (!m_valid) + m_type = Invalid; + else + { + if (m_list.empty()) + m_type = Empty; + else if (m_list.size() == 1) + { + const GLVolume* first = (*m_volumes)[*m_list.begin()]; + if (first->is_wipe_tower) + m_type = WipeTower; + else if (first->is_modifier) + { + m_type = SingleModifier; + requires_disable = true; + } + else + { + const ModelObject* model_object = m_model->objects[first->object_idx()]; + unsigned int volumes_count = (unsigned int)model_object->volumes.size(); + unsigned int instances_count = (unsigned int)model_object->instances.size(); + if (volumes_count * instances_count == 1) + { + m_type = SingleFullObject; + // ensures the correct mode is selected + m_mode = Instance; + } + else if (volumes_count == 1) // instances_count > 1 + { + m_type = SingleFullInstance; + // ensures the correct mode is selected + m_mode = Instance; + } + else + { + m_type = SingleVolume; + requires_disable = true; + } + } + } + else + { + if (m_cache.content.size() == 1) // single object + { + const ModelObject* model_object = m_model->objects[m_cache.content.begin()->first]; + unsigned int model_volumes_count = (unsigned int)model_object->volumes.size(); + unsigned int sla_volumes_count = 0; + for (unsigned int i : m_list) + { + if ((*m_volumes)[i]->volume_idx() < 0) + ++sla_volumes_count; + } + unsigned int volumes_count = model_volumes_count + sla_volumes_count; + unsigned int instances_count = (unsigned int)model_object->instances.size(); + unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); + if (volumes_count * instances_count == (unsigned int)m_list.size()) + { + m_type = SingleFullObject; + // ensures the correct mode is selected + m_mode = Instance; + } + else if (selected_instances_count == 1) + { + if (volumes_count == (unsigned int)m_list.size()) + { + m_type = SingleFullInstance; + // ensures the correct mode is selected + m_mode = Instance; + } + else + { + unsigned int modifiers_count = 0; + for (unsigned int i : m_list) + { + if ((*m_volumes)[i]->is_modifier) + ++modifiers_count; + } + + if (modifiers_count == 0) + { + m_type = MultipleVolume; + requires_disable = true; + } + else if (modifiers_count == (unsigned int)m_list.size()) + { + m_type = MultipleModifier; + requires_disable = true; + } + } + } + else if ((selected_instances_count > 1) && (selected_instances_count * volumes_count == (unsigned int)m_list.size())) + { + m_type = MultipleFullInstance; + // ensures the correct mode is selected + m_mode = Instance; + } + } + else + { + int sels_cntr = 0; + for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) + { + const ModelObject* model_object = m_model->objects[it->first]; + unsigned int volumes_count = (unsigned int)model_object->volumes.size(); + unsigned int instances_count = (unsigned int)model_object->instances.size(); + sels_cntr += volumes_count * instances_count; + } + if (sels_cntr == (unsigned int)m_list.size()) + { + m_type = MultipleFullObject; + // ensures the correct mode is selected + m_mode = Instance; + } + } + } + } + + int object_idx = get_object_idx(); + int instance_idx = get_instance_idx(); + for (GLVolume* v : *m_volumes) + { + v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false; + } + +#if ENABLE_SELECTION_DEBUG_OUTPUT + std::cout << "Selection: "; + std::cout << "mode: "; + switch (m_mode) + { + case Volume: + { + std::cout << "Volume"; + break; + } + case Instance: + { + std::cout << "Instance"; + break; + } + } + + std::cout << " - type: "; + + switch (m_type) + { + case Invalid: + { + std::cout << "Invalid" << std::endl; + break; + } + case Empty: + { + std::cout << "Empty" << std::endl; + break; + } + case WipeTower: + { + std::cout << "WipeTower" << std::endl; + break; + } + case SingleModifier: + { + std::cout << "SingleModifier" << std::endl; + break; + } + case MultipleModifier: + { + std::cout << "MultipleModifier" << std::endl; + break; + } + case SingleVolume: + { + std::cout << "SingleVolume" << std::endl; + break; + } + case MultipleVolume: + { + std::cout << "MultipleVolume" << std::endl; + break; + } + case SingleFullObject: + { + std::cout << "SingleFullObject" << std::endl; + break; + } + case MultipleFullObject: + { + std::cout << "MultipleFullObject" << std::endl; + break; + } + case SingleFullInstance: + { + std::cout << "SingleFullInstance" << std::endl; + break; + } + case MultipleFullInstance: + { + std::cout << "MultipleFullInstance" << std::endl; + break; + } + case Mixed: + { + std::cout << "Mixed" << std::endl; + break; + } + } +#endif // ENABLE_SELECTION_DEBUG_OUTPUT +} + +void Selection::_set_caches() +{ + m_cache.volumes_data.clear(); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + const GLVolume* v = (*m_volumes)[i]; + m_cache.volumes_data.emplace(i, VolumeCache(v->get_volume_transformation(), v->get_instance_transformation())); + } + m_cache.dragging_center = get_bounding_box().center(); +} + +void Selection::_add_volume(unsigned int volume_idx) +{ + m_list.insert(volume_idx); + (*m_volumes)[volume_idx]->selected = true; +} + +void Selection::_add_instance(unsigned int object_idx, unsigned int instance_idx) +{ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) + _add_volume(i); + } +} + +void Selection::_add_object(unsigned int object_idx) +{ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == object_idx) + _add_volume(i); + } +} + +void Selection::_remove_volume(unsigned int volume_idx) +{ + IndicesList::iterator v_it = m_list.find(volume_idx); + if (v_it == m_list.end()) + return; + + m_list.erase(v_it); + + (*m_volumes)[volume_idx]->selected = false; +} + +void Selection::_remove_instance(unsigned int object_idx, unsigned int instance_idx) +{ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) + _remove_volume(i); + } +} + +void Selection::_remove_object(unsigned int object_idx) +{ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == object_idx) + _remove_volume(i); + } +} + +void Selection::_calc_bounding_box() const +{ + m_bounding_box = BoundingBoxf3(); + if (m_valid) + { + for (unsigned int i : m_list) + { + m_bounding_box.merge((*m_volumes)[i]->transformed_convex_hull_bounding_box()); + } + } + m_bounding_box_dirty = false; +} + +void Selection::_render_selected_volumes() const +{ + float color[3] = { 1.0f, 1.0f, 1.0f }; + _render_bounding_box(get_bounding_box(), color); +} + +void Selection::_render_synchronized_volumes() const +{ + if (m_mode == Instance) + return; + + float color[3] = { 1.0f, 1.0f, 0.0f }; + + for (unsigned int i : m_list) + { + const GLVolume* volume = (*m_volumes)[i]; + int object_idx = volume->object_idx(); + int instance_idx = volume->instance_idx(); + int volume_idx = volume->volume_idx(); + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) + { + if (i == j) + continue; + + const GLVolume* v = (*m_volumes)[j]; + if ((v->object_idx() != object_idx) || (v->volume_idx() != volume_idx)) + continue; + + _render_bounding_box(v->transformed_convex_hull_bounding_box(), color); + } + } +} + +void Selection::_render_bounding_box(const BoundingBoxf3& box, float* color) const +{ + if (color == nullptr) + return; + + Vec3f b_min = box.min.cast(); + Vec3f b_max = box.max.cast(); + Vec3f size = 0.2f * box.size().cast(); + + ::glEnable(GL_DEPTH_TEST); + + ::glColor3fv(color); + ::glLineWidth(2.0f * m_scale_factor); + + ::glBegin(GL_LINES); + + ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_min(2)); + ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_min(2)); + ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1), b_min(2) + size(2)); + + ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_min(2)); + ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_min(2)); + ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1), b_min(2) + size(2)); + + ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_min(2)); + ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_min(2)); + ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1), b_min(2) + size(2)); + + ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_min(2)); + ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_min(2)); + ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1), b_min(2) + size(2)); + + ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_max(2)); + ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_max(2)); + ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1), b_max(2) - size(2)); + + ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_max(2)); + ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_max(2)); + ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1), b_max(2) - size(2)); + + ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_max(2)); + ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_max(2)); + ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1), b_max(2) - size(2)); + + ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_max(2)); + ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_max(2)); + ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1), b_max(2) - size(2)); + + ::glEnd(); +} + +void Selection::_render_sidebar_position_hints(const std::string& sidebar_field) const +{ + if (boost::ends_with(sidebar_field, "x")) + { + ::glRotated(-90.0, 0.0, 0.0, 1.0); + _render_sidebar_position_hint(X); + } + else if (boost::ends_with(sidebar_field, "y")) + _render_sidebar_position_hint(Y); + else if (boost::ends_with(sidebar_field, "z")) + { + ::glRotated(90.0, 1.0, 0.0, 0.0); + _render_sidebar_position_hint(Z); + } +} + +void Selection::_render_sidebar_rotation_hints(const std::string& sidebar_field) const +{ + if (boost::ends_with(sidebar_field, "x")) + { + ::glRotated(90.0, 0.0, 1.0, 0.0); + _render_sidebar_rotation_hint(X); + } + else if (boost::ends_with(sidebar_field, "y")) + { + ::glRotated(-90.0, 1.0, 0.0, 0.0); + _render_sidebar_rotation_hint(Y); + } + else if (boost::ends_with(sidebar_field, "z")) + _render_sidebar_rotation_hint(Z); +} + +void Selection::_render_sidebar_scale_hints(const std::string& sidebar_field) const +{ + bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); + + if (boost::ends_with(sidebar_field, "x") || uniform_scale) + { + ::glPushMatrix(); + ::glRotated(-90.0, 0.0, 0.0, 1.0); + _render_sidebar_scale_hint(X); + ::glPopMatrix(); + } + + if (boost::ends_with(sidebar_field, "y") || uniform_scale) + { + ::glPushMatrix(); + _render_sidebar_scale_hint(Y); + ::glPopMatrix(); + } + + if (boost::ends_with(sidebar_field, "z") || uniform_scale) + { + ::glPushMatrix(); + ::glRotated(90.0, 1.0, 0.0, 0.0); + _render_sidebar_scale_hint(Z); + ::glPopMatrix(); + } +} + +void Selection::_render_sidebar_size_hints(const std::string& sidebar_field) const +{ + _render_sidebar_scale_hints(sidebar_field); +} + +void Selection::_render_sidebar_position_hint(Axis axis) const +{ + m_arrow.set_color(AXES_COLOR[axis], 3); + m_arrow.render(); +} + +void Selection::_render_sidebar_rotation_hint(Axis axis) const +{ + m_curved_arrow.set_color(AXES_COLOR[axis], 3); + m_curved_arrow.render(); + + ::glRotated(180.0, 0.0, 0.0, 1.0); + m_curved_arrow.render(); +} + +void Selection::_render_sidebar_scale_hint(Axis axis) const +{ + m_arrow.set_color(((requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis]), 3); + + ::glTranslated(0.0, 5.0, 0.0); + m_arrow.render(); + + ::glTranslated(0.0, -10.0, 0.0); + ::glRotated(180.0, 0.0, 0.0, 1.0); + m_arrow.render(); +} + +void Selection::_render_sidebar_size_hint(Axis axis, double length) const +{ +} + +#ifndef NDEBUG +static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) +{ + Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); + Vec3d axis = angle_axis.axis(); + double angle = angle_axis.angle(); + if (std::abs(angle) < 1e-8) + return true; + assert(std::abs(axis.x()) < 1e-8); + assert(std::abs(axis.y()) < 1e-8); + assert(std::abs(std::abs(axis.z()) - 1.) < 1e-8); + return std::abs(axis.x()) < 1e-8 && std::abs(axis.y()) < 1e-8 && std::abs(std::abs(axis.z()) - 1.) < 1e-8; +} + +static void verify_instances_rotation_synchronized(const Model &model, const GLVolumePtrs &volumes) +{ + for (size_t idx_object = 0; idx_object < model.objects.size(); ++idx_object) { + int idx_volume_first = -1; + for (int i = 0; i < (int)volumes.size(); ++i) { + if (volumes[i]->object_idx() == idx_object) { + idx_volume_first = i; + break; + } + } + assert(idx_volume_first != -1); // object without instances? + if (idx_volume_first == -1) + continue; + const Vec3d &rotation0 = volumes[idx_volume_first]->get_instance_rotation(); + for (int i = idx_volume_first + 1; i < (int)volumes.size(); ++i) + if (volumes[i]->object_idx() == idx_object) { + const Vec3d &rotation = volumes[i]->get_instance_rotation(); + assert(is_rotation_xy_synchronized(rotation, rotation0)); + } + } +} +#endif /* NDEBUG */ + +void Selection::_synchronize_unselected_instances(SyncRotationType sync_rotation_type) +{ + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) + { + if (done.size() == m_volumes->size()) + break; + + const GLVolume* volume = (*m_volumes)[i]; + int object_idx = volume->object_idx(); + if (object_idx >= 1000) + continue; + + int instance_idx = volume->instance_idx(); + const Vec3d& rotation = volume->get_instance_rotation(); + const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); + const Vec3d& mirror = volume->get_instance_mirror(); + + // Process unselected instances. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) + { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume* v = (*m_volumes)[j]; + if ((v->object_idx() != object_idx) || (v->instance_idx() == instance_idx)) + continue; + + assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); + switch (sync_rotation_type) { + case SYNC_ROTATION_NONE: + // z only rotation -> keep instance z + // The X,Y rotations should be synchronized from start to end of the rotation. + assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); + break; + case SYNC_ROTATION_FULL: + // rotation comes from place on face -> force given z + v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2))); + break; + case SYNC_ROTATION_GENERAL: + // generic rotation -> update instance z with the delta of the rotation. + double z_diff = rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); + v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + break; + } + + v->set_instance_scaling_factor(scaling_factor); + v->set_instance_mirror(mirror); + + done.insert(j); + } + } + +#ifndef NDEBUG + verify_instances_rotation_synchronized(*m_model, *m_volumes); +#endif /* NDEBUG */ +} + +void Selection::_synchronize_unselected_volumes() +{ + for (unsigned int i : m_list) + { + const GLVolume* volume = (*m_volumes)[i]; + int object_idx = volume->object_idx(); + if (object_idx >= 1000) + continue; + + int volume_idx = volume->volume_idx(); + const Vec3d& offset = volume->get_volume_offset(); + const Vec3d& rotation = volume->get_volume_rotation(); + const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); + const Vec3d& mirror = volume->get_volume_mirror(); + + // Process unselected volumes. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) + { + if (j == i) + continue; + + GLVolume* v = (*m_volumes)[j]; + if ((v->object_idx() != object_idx) || (v->volume_idx() != volume_idx)) + continue; + + v->set_volume_offset(offset); + v->set_volume_rotation(rotation); + v->set_volume_scaling_factor(scaling_factor); + v->set_volume_mirror(mirror); + } + } +} + +void Selection::_ensure_on_bed() +{ + typedef std::map, double> InstancesToZMap; + InstancesToZMap instances_min_z; + + for (GLVolume* volume : *m_volumes) + { + if (!volume->is_wipe_tower && !volume->is_modifier) + { + double min_z = volume->transformed_convex_hull_bounding_box().min(2); + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it == instances_min_z.end()) + it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; + + it->second = std::min(it->second, min_z); + } + } + + for (GLVolume* volume : *m_volumes) + { + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it != instances_min_z.end()) + volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second); + } +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp new file mode 100644 index 000000000..3a21122df --- /dev/null +++ b/src/slic3r/GUI/Selection.hpp @@ -0,0 +1,301 @@ +#ifndef slic3r_GUI_Selection_hpp_ +#define slic3r_GUI_Selection_hpp_ + +#include +#include "libslic3r/Geometry.hpp" +#include "3DScene.hpp" + +namespace Slic3r { +namespace GUI { + +class TransformationType +{ +public: + enum Enum { + // Transforming in a world coordinate system + World = 0, + // Transforming in a local coordinate system + Local = 1, + // Absolute transformations, allowed in local coordinate system only. + Absolute = 0, + // Relative transformations, allowed in both local and world coordinate system. + Relative = 2, + // For group selection, the transformation is performed as if the group made a single solid body. + Joint = 0, + // For group selection, the transformation is performed on each object independently. + Independent = 4, + + World_Relative_Joint = World | Relative | Joint, + World_Relative_Independent = World | Relative | Independent, + Local_Absolute_Joint = Local | Absolute | Joint, + Local_Absolute_Independent = Local | Absolute | Independent, + Local_Relative_Joint = Local | Relative | Joint, + Local_Relative_Independent = Local | Relative | Independent, + }; + + TransformationType() : m_value(World) {} + TransformationType(Enum value) : m_value(value) {} + TransformationType& operator=(Enum value) { m_value = value; return *this; } + + Enum operator()() const { return m_value; } + bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; } + + void set_world() { this->remove(Local); } + void set_local() { this->add(Local); } + void set_absolute() { this->remove(Relative); } + void set_relative() { this->add(Relative); } + void set_joint() { this->remove(Independent); } + void set_independent() { this->add(Independent); } + + bool world() const { return !this->has(Local); } + bool local() const { return this->has(Local); } + bool absolute() const { return !this->has(Relative); } + bool relative() const { return this->has(Relative); } + bool joint() const { return !this->has(Independent); } + bool independent() const { return this->has(Independent); } + +private: + void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); } + void remove(Enum v) { m_value = Enum((unsigned int)m_value & (~(unsigned int)v)); } + + Enum m_value; +}; + +class Selection +{ +public: + typedef std::set IndicesList; + + enum EMode : unsigned char + { + Volume, + Instance + }; + + enum EType : unsigned char + { + Invalid, + Empty, + WipeTower, + SingleModifier, + MultipleModifier, + SingleVolume, + MultipleVolume, + SingleFullObject, + MultipleFullObject, + SingleFullInstance, + MultipleFullInstance, + Mixed + }; + +private: + struct VolumeCache + { + private: + struct TransformCache + { + Vec3d position; + Vec3d rotation; + Vec3d scaling_factor; + Vec3d mirror; + Transform3d rotation_matrix; + Transform3d scale_matrix; + Transform3d mirror_matrix; + Transform3d full_matrix; + + TransformCache(); + explicit TransformCache(const Geometry::Transformation& transform); + }; + + TransformCache m_volume; + TransformCache m_instance; + + public: + VolumeCache() {} + VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform); + + const Vec3d& get_volume_position() const { return m_volume.position; } + const Vec3d& get_volume_rotation() const { return m_volume.rotation; } + const Vec3d& get_volume_scaling_factor() const { return m_volume.scaling_factor; } + const Vec3d& get_volume_mirror() const { return m_volume.mirror; } + const Transform3d& get_volume_rotation_matrix() const { return m_volume.rotation_matrix; } + const Transform3d& get_volume_scale_matrix() const { return m_volume.scale_matrix; } + const Transform3d& get_volume_mirror_matrix() const { return m_volume.mirror_matrix; } + const Transform3d& get_volume_full_matrix() const { return m_volume.full_matrix; } + + const Vec3d& get_instance_position() const { return m_instance.position; } + const Vec3d& get_instance_rotation() const { return m_instance.rotation; } + const Vec3d& get_instance_scaling_factor() const { return m_instance.scaling_factor; } + const Vec3d& get_instance_mirror() const { return m_instance.mirror; } + const Transform3d& get_instance_rotation_matrix() const { return m_instance.rotation_matrix; } + const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; } + const Transform3d& get_instance_mirror_matrix() const { return m_instance.mirror_matrix; } + const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; } + }; + + typedef std::map VolumesCache; + typedef std::set InstanceIdxsList; + typedef std::map ObjectIdxsToInstanceIdxsMap; + + struct Cache + { + // Cache of GLVolume derived transformation matrices, valid during mouse dragging. + VolumesCache volumes_data; + // Center of the dragged selection, valid during mouse dragging. + Vec3d dragging_center; + // Map from indices of ModelObject instances in Model::objects + // to a set of indices of ModelVolume instances in ModelObject::instances + // Here the index means a position inside the respective std::vector, not ModelID. + ObjectIdxsToInstanceIdxsMap content; + }; + + // Volumes owned by GLCanvas3D. + GLVolumePtrs* m_volumes; + // Model, not owned. + Model* m_model; + + bool m_valid; + EMode m_mode; + EType m_type; + // set of indices to m_volumes + IndicesList m_list; + Cache m_cache; + mutable BoundingBoxf3 m_bounding_box; + mutable bool m_bounding_box_dirty; + +#if ENABLE_RENDER_SELECTION_CENTER + GLUquadricObj* m_quadric; +#endif // ENABLE_RENDER_SELECTION_CENTER + mutable GLArrow m_arrow; + mutable GLCurvedArrow m_curved_arrow; + + mutable float m_scale_factor; + +public: + Selection(); +#if ENABLE_RENDER_SELECTION_CENTER + ~Selection(); +#endif // ENABLE_RENDER_SELECTION_CENTER + + void set_volumes(GLVolumePtrs* volumes); + bool init(bool useVBOs); + + Model* get_model() const { return m_model; } + void set_model(Model* model); + + EMode get_mode() const { return m_mode; } + void set_mode(EMode mode) { m_mode = mode; } + + void add(unsigned int volume_idx, bool as_single_selection = true); + void remove(unsigned int volume_idx); + + void add_object(unsigned int object_idx, bool as_single_selection = true); + void remove_object(unsigned int object_idx); + + void add_instance(unsigned int object_idx, unsigned int instance_idx, bool as_single_selection = true); + void remove_instance(unsigned int object_idx, unsigned int instance_idx); + + void add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection = true); + void remove_volume(unsigned int object_idx, unsigned int volume_idx); + + void add_all(); + + // Update the selection based on the map from old indices to new indices after m_volumes changed. + // If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances. + void volumes_changed(const std::vector &map_volume_old_to_new); + void clear(); + + bool is_empty() const { return m_type == Empty; } + bool is_wipe_tower() const { return m_type == WipeTower; } + bool is_modifier() const { return (m_type == SingleModifier) || (m_type == MultipleModifier); } + bool is_single_modifier() const { return m_type == SingleModifier; } + bool is_multiple_modifier() const { return m_type == MultipleModifier; } + bool is_single_full_instance() const; + bool is_multiple_full_instance() const { return m_type == MultipleFullInstance; } + bool is_single_full_object() const { return m_type == SingleFullObject; } + bool is_multiple_full_object() const { return m_type == MultipleFullObject; } + bool is_single_volume() const { return m_type == SingleVolume; } + bool is_multiple_volume() const { return m_type == MultipleVolume; } + bool is_mixed() const { return m_type == Mixed; } + bool is_from_single_instance() const { return get_instance_idx() != -1; } + bool is_from_single_object() const; + + bool contains_volume(unsigned int volume_idx) const { return std::find(m_list.begin(), m_list.end(), volume_idx) != m_list.end(); } + bool requires_uniform_scale() const; + + // Returns the the object id if the selection is from a single object, otherwise is -1 + int get_object_idx() const; + // Returns the instance id if the selection is from a single object and from a single instance, otherwise is -1 + int get_instance_idx() const; + // Returns the indices of selected instances. + // Can only be called if selection is from a single object. + const InstanceIdxsList& get_instance_idxs() const; + + const IndicesList& get_volume_idxs() const { return m_list; } + const GLVolume* get_volume(unsigned int volume_idx) const; + + const ObjectIdxsToInstanceIdxsMap& get_content() const { return m_cache.content; } + + unsigned int volumes_count() const { return (unsigned int)m_list.size(); } + const BoundingBoxf3& get_bounding_box() const; + + void start_dragging(); + + void translate(const Vec3d& displacement, bool local = false); + void rotate(const Vec3d& rotation, TransformationType transformation_type); + void flattening_rotate(const Vec3d& normal); + void scale(const Vec3d& scale, bool local); + void mirror(Axis axis); + + void translate(unsigned int object_idx, const Vec3d& displacement); + void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); + + void erase(); + + void render(float scale_factor = 1.0) const; +#if ENABLE_RENDER_SELECTION_CENTER + void render_center() const; +#endif // ENABLE_RENDER_SELECTION_CENTER + void render_sidebar_hints(const std::string& sidebar_field) const; + + bool requires_local_axes() const; + +private: + void _update_valid(); + void _update_type(); + void _set_caches(); + void _add_volume(unsigned int volume_idx); + void _add_instance(unsigned int object_idx, unsigned int instance_idx); + void _add_object(unsigned int object_idx); + void _remove_volume(unsigned int volume_idx); + void _remove_instance(unsigned int object_idx, unsigned int instance_idx); + void _remove_object(unsigned int object_idx); + void _calc_bounding_box() const; + void _render_selected_volumes() const; + void _render_synchronized_volumes() const; + void _render_bounding_box(const BoundingBoxf3& box, float* color) const; + void _render_sidebar_position_hints(const std::string& sidebar_field) const; + void _render_sidebar_rotation_hints(const std::string& sidebar_field) const; + void _render_sidebar_scale_hints(const std::string& sidebar_field) const; + void _render_sidebar_size_hints(const std::string& sidebar_field) const; + void _render_sidebar_position_hint(Axis axis) const; + void _render_sidebar_rotation_hint(Axis axis) const; + void _render_sidebar_scale_hint(Axis axis) const; + void _render_sidebar_size_hint(Axis axis, double length) const; + enum SyncRotationType { + // Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis. + SYNC_ROTATION_NONE = 0, + // Synchronize fully. Used from "place on bed" feature. + SYNC_ROTATION_FULL = 1, + // Synchronize after rotation by an axis not parallel with Z. + SYNC_ROTATION_GENERAL = 2, + }; + void _synchronize_unselected_instances(SyncRotationType sync_rotation_type); + void _synchronize_unselected_volumes(); + void _ensure_on_bed(); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GUI_Selection_hpp_ diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 7b2b55743..af3b358d3 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -29,6 +29,7 @@ #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" +#include "ConfigWizard.hpp" namespace Slic3r { namespace GUI { @@ -58,6 +59,13 @@ Tab::Tab(wxNotebook* parent, const wxString& title, const char* name) : wxGetApp().tabs_list.push_back(this); m_em_unit = wxGetApp().em_unit(); + + Bind(wxEVT_SIZE, ([this](wxSizeEvent &evt) { + for (auto page : m_pages) + if (! page.get()->IsShown()) + page->layout_valid = false; + evt.Skip(); + })); } void Tab::set_type() @@ -73,6 +81,10 @@ void Tab::set_type() // sub new void Tab::create_preset_tab() { +#ifdef __WINDOWS__ + SetDoubleBuffered(true); +#endif //__WINDOWS__ + m_preset_bundle = wxGetApp().preset_bundle; // Vertical sizer to hold the choice menu and the rest of the page. @@ -237,10 +249,12 @@ void Tab::create_preset_tab() return; if (selected_item >= 0) { std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); - if (selected_string.find("-------") == 0 + if (selected_string.find(PresetCollection::separator_head()) == 0 /*selected_string == "------- System presets -------" || selected_string == "------- User presets -------"*/) { m_presets_choice->SetSelection(m_selected_preset_item); + if (wxString::FromUTF8(selected_string.c_str()) == PresetCollection::separator(L("Add a new printer"))) + wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); }); return; } m_selected_preset_item = selected_item; @@ -257,6 +271,7 @@ void Tab::create_preset_tab() // Initialize the DynamicPrintConfig by default keys/values. build(); rebuild_page_tree(); + m_complited = true; } void Tab::load_initial_data() @@ -289,6 +304,11 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str auto panel = this; #endif PageShp page(new Page(panel, title, icon_idx)); +// page->SetBackgroundStyle(wxBG_STYLE_SYSTEM); +#ifdef __WINDOWS__ +// page->SetDoubleBuffered(true); +#endif //__WINDOWS__ + page->SetScrollbars(1, 20, 1, 2); page->Hide(); m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); @@ -314,7 +334,7 @@ void Tab::OnActivate() void Tab::update_labels_colour() { - Freeze(); +// Freeze(); //update options "decoration" for (const auto opt : m_options_list) { @@ -341,7 +361,7 @@ void Tab::update_labels_colour() if (field == nullptr) continue; field->set_label_colour_force(color); } - Thaw(); +// Thaw(); auto cur_item = m_treectrl->GetFirstVisibleItem(); while (cur_item) { @@ -368,7 +388,7 @@ void Tab::update_changed_ui() if (m_postpone_update_ui) return; - const bool deep_compare = (m_name == "printer" || m_name == "sla_material"); + const bool deep_compare = (m_type == Slic3r::Preset::TYPE_PRINTER || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL); auto dirty_options = m_presets->current_dirty_options(deep_compare); auto nonsys_options = m_presets->current_different_from_parent_options(deep_compare); if (m_type == Slic3r::Preset::TYPE_PRINTER) { @@ -385,7 +405,7 @@ void Tab::update_changed_ui() for (auto opt_key : dirty_options) m_options_list[opt_key] &= ~osInitValue; for (auto opt_key : nonsys_options) m_options_list[opt_key] &= ~osSystemValue; - Freeze(); +// Freeze(); //update options "decoration" for (const auto opt : m_options_list) { @@ -435,7 +455,7 @@ void Tab::update_changed_ui() field->set_undo_to_sys_tooltip(sys_tt); field->set_label_colour(color); } - Thaw(); +// Thaw(); wxTheApp->CallAfter([this]() { update_changed_tree_ui(); @@ -684,31 +704,34 @@ void Tab::load_config(const DynamicPrintConfig& config) // Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. void Tab::reload_config() { - Freeze(); +// Freeze(); for (auto page : m_pages) page->reload_config(); - Thaw(); +// Thaw(); +} + +void Tab::update_mode() +{ + m_mode = wxGetApp().get_mode(); + + // update mode for ModeSizer + m_mode_sizer->SetMode(m_mode); + + update_visibility(); } void Tab::update_visibility() { - const ConfigOptionMode mode = wxGetApp().get_mode(); - Freeze(); + Freeze(); // There is needed Freeze/Thaw to avoid a flashing after Show/Layout for (auto page : m_pages) - page->update_visibility(mode); + page->update_visibility(m_mode); update_page_tree_visibility(); - // update mode for ModeSizer - m_mode_sizer->SetMode(mode); - Layout(); Thaw(); - // to update tree items color -// wxTheApp->CallAfter([this]() { - update_changed_tree_ui(); -// }); + update_changed_tree_ui(); } Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const @@ -753,21 +776,29 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo void Tab::on_value_change(const std::string& opt_key, const boost::any& value) { - ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(supports_printer_technology(ptFFF)); - if (opt_key == "fill_density" || opt_key == "supports_enable" || opt_key == "pad_enable") + if (wxGetApp().plater() == nullptr) { + return; + } + + const bool is_fff = supports_printer_technology(ptFFF); + ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); + if (opt_key == "fill_density" || opt_key == "pad_enable") { boost::any val = og_freq_chng_params->get_config_value(*m_config, opt_key); og_freq_chng_params->set_value(opt_key, val); } - if (opt_key == "support_material" || opt_key == "support_material_buildplate_only") + + if ( is_fff && (opt_key == "support_material" || opt_key == "support_material_buildplate_only") || + !is_fff && (opt_key == "supports_enable" || opt_key == "support_buildplate_only")) { - wxString new_selection = !m_config->opt_bool("support_material") ? - _("None") : - m_config->opt_bool("support_material_buildplate_only") ? - _("Support on build plate only") : - _("Everywhere"); + const std::string support = is_fff ? "support_material" : "supports_enable"; + const std::string buildplate_only = is_fff ? "support_material_buildplate_only" : "support_buildplate_only"; + wxString new_selection = !m_config->opt_bool(support) ? _("None") : + m_config->opt_bool(buildplate_only) ? _("Support on build plate only") : + _("Everywhere"); og_freq_chng_params->set_value("support", new_selection); } + if (opt_key == "brim_width") { bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; @@ -781,25 +812,6 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) wxGetApp().plater()->on_extruders_change(boost::any_cast(value)); update(); - - // #ys_FIXME_to_delete - // Post event to the Plater after updating of the all dirty options - // It helps to avoid needless schedule_background_processing -// if (update_completed()) -// if (m_update_stack.empty()) -// { -// // wxCommandEvent event(EVT_TAB_VALUE_CHANGED); -// // event.SetEventObject(this); -// // event.SetString(opt_key); -// // if (opt_key == "extruders_count") -// // { -// // const int val = boost::any_cast(value); -// // event.SetInt(val); -// // } -// // -// // wxPostEvent(this, event); -// wxGetApp().mainframe->on_value_changed(m_config); -// } } // Show/hide the 'purging volumes' button @@ -822,9 +834,17 @@ void Tab::update_wiping_button_visibility() { // 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. +// to update number of "filament" selection boxes when the number of extruders change. void Tab::on_presets_changed() { + if (wxGetApp().plater() == nullptr) { + return; + } + + // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets + wxGetApp().plater()->sidebar().update_presets(m_type); + update_preset_description_line(); + // Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. for (auto t: m_dependent_tabs) { @@ -835,16 +855,6 @@ void Tab::on_presets_changed() // clear m_dependent_tabs after first update from select_preset() // to avoid needless preset loading from update() function m_dependent_tabs.clear(); - - // #ys_FIXME_to_delete -// wxCommandEvent event(EVT_TAB_PRESETS_CHANGED); -// event.SetEventObject(this); -// wxPostEvent(this, event); - - // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets - wxGetApp().plater()->sidebar().update_presets(m_type); - - update_preset_description_line(); } void Tab::update_preset_description_line() @@ -1260,7 +1270,7 @@ void TabPrint::update() // return; // ! TODO Let delete this part of code after a common aplication testing m_update_cnt++; - Freeze(); +// Freeze(); double fill_density = m_config->option("fill_density")->value; @@ -1507,7 +1517,7 @@ void TabPrint::update() m_recommended_thin_wall_thickness_description_line->SetText( from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); - Thaw(); +// Thaw(); m_update_cnt--; if (m_update_cnt==0) @@ -1607,6 +1617,7 @@ void TabFilament::build() line = optgroup->create_single_option_line("filament_ramming_parameters");// { _(L("Ramming")), "" }; line.widget = [this](wxWindow* parent) { auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + ramming_dialog_btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(ramming_dialog_btn); @@ -1687,7 +1698,7 @@ void TabFilament::update() return; // ys_FIXME m_update_cnt++; - Freeze(); +// Freeze(); wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); m_cooling_description_line->SetText(text); text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); @@ -1701,7 +1712,7 @@ void TabFilament::update() for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) get_field(el)->toggle(fan_always_on); - Thaw(); +// Thaw(); m_update_cnt--; if (m_update_cnt == 0) @@ -1733,27 +1744,22 @@ bool Tab::current_preset_is_dirty() void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) { - const bool sla = m_presets->get_selected_preset().printer_technology() == ptSLA; + const PrinterTechnology tech = m_presets->get_selected_preset().printer_technology(); // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) - if (! sla) { + if (tech == ptFFF) { optgroup->append_single_option_line("host_type"); - } else { - m_config->option>("host_type", true)->value = htSL1; } - auto printhost_browse = [this, optgroup] (wxWindow* parent) { - - // TODO: SLA Bonjour - + auto printhost_browse = [=](wxWindow* parent) { auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); -// btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG)); + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); btn->SetBitmap(create_scaled_bitmap("zoom.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [this, parent, optgroup](wxCommandEvent &e) { - BonjourDialog dialog(parent); + btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + BonjourDialog dialog(parent, tech); if (dialog.show_and_lookup()) { optgroup->set_value("print_host", std::move(dialog.get_selected()), true); optgroup->get_field("print_host")->field_changed(); @@ -1766,7 +1772,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) auto print_host_test = [this](wxWindow* parent) { auto btn = m_print_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); -// btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG)); + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); btn->SetBitmap(create_scaled_bitmap("wrench.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1804,6 +1810,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); // btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("zoom.png")), wxBITMAP_TYPE_PNG)); + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); btn->SetBitmap(create_scaled_bitmap("zoom.png")); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); @@ -1842,6 +1849,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) \tOn this system, Slic3r uses HTTPS certificates from the system Certificate Store or Keychain.\n\ \tTo use a custom CA file, please import your CA file into Certificate Store / Keychain.")), ca_file_hint)); + txt->SetFont(Slic3r::GUI::wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(txt); return sizer; @@ -2083,7 +2091,7 @@ void TabPrinter::build_sla() Line line = optgroup->create_single_option_line("bed_shape");//{ _(L("Bed shape")), "" }; line.widget = [this](wxWindow* parent) { auto btn = new wxButton(parent, wxID_ANY, _(L(" Set ")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); - // btn->SetFont(Slic3r::GUI::small_font); + btn->SetFont(wxGetApp().small_font()); // btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); btn->SetBitmap(create_scaled_bitmap("printer_empty.png")); @@ -2381,7 +2389,7 @@ void TabPrinter::update_pages() else m_pages_sla.empty() ? build_sla() : m_pages.swap(m_pages_sla); - rebuild_page_tree(true); + rebuild_page_tree(); } void TabPrinter::update() @@ -2396,7 +2404,7 @@ void TabPrinter::update() void TabPrinter::update_fff() { - Freeze(); +// Freeze(); bool en; auto serial_speed = get_field("serial_speed"); @@ -2499,7 +2507,7 @@ void TabPrinter::update_fff() for (auto el : { "wipe_advanced_nozzle_melted_volume", "wipe_advanced_multiplier", "wipe_advanced_algo" }) get_field(el)->toggle(have_advanced_wipe_volume); } - Thaw(); +// Thaw(); } void TabPrinter::update_sla() @@ -2513,7 +2521,7 @@ void Tab::load_current_preset() (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); update(); - if (m_name == "printer") { + if (m_type == Slic3r::Preset::TYPE_PRINTER) { // For the printer profile, generate the extruder pages. if (preset.printer_technology() == ptFFF) on_preset_loaded(); @@ -2543,7 +2551,7 @@ void Tab::load_current_preset() update_tab_ui(); // update show/hide tabs - if (m_name == "printer") { + if (m_type == Slic3r::Preset::TYPE_PRINTER) { const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); if (printer_technology != static_cast(this)->m_printer_technology) { @@ -2576,7 +2584,7 @@ void Tab::load_current_preset() } else { on_presets_changed(); - if (m_type == Preset::TYPE_SLA_PRINT || m_type == Preset::TYPE_PRINT)// if (m_name == "print") + if (m_type == Preset::TYPE_SLA_PRINT || m_type == Preset::TYPE_PRINT) update_frequently_changed_parameters(); } @@ -2591,10 +2599,8 @@ void Tab::load_current_preset() } //Regerenerate content of the page tree. -void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) +void Tab::rebuild_page_tree() { - Freeze(); - // get label of the currently selected item const auto sel_item = m_treectrl->GetSelection(); const auto selected = sel_item ? m_treectrl->GetItemText(sel_item) : ""; @@ -2607,10 +2613,7 @@ void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); if (p->title() == selected) { -// if (!(p->title() == _(L("Machine limits")) || p->title() == _(L("Single extruder MM setup")))) // These Pages have to be updated inside OnTreeSelChange -// m_disable_tree_sel_changed_event = !tree_sel_change_event; m_treectrl->SelectItem(itemId); - m_disable_tree_sel_changed_event = false; have_selection = 1; } } @@ -2619,7 +2622,7 @@ void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) // this is triggered on first load, so we don't disable the sel change event m_treectrl->SelectItem(m_treectrl->GetFirstVisibleItem());//! (treectrl->GetFirstChild(rootItem)); } - Thaw(); +// Thaw(); } void Tab::update_page_tree_visibility() @@ -2649,20 +2652,35 @@ void Tab::update_page_tree_visibility() } -// Called by the UI combo box when the user switches profiles. +// Called by the UI combo box when the user switches profiles, and also to delete the current profile. // 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. -void Tab::select_preset(std::string preset_name) +void Tab::select_preset(std::string preset_name, bool delete_current) { - // If no name is provided, select the "-- default --" preset. - if (preset_name.empty()) - preset_name = m_presets->default_preset().name; - bool current_dirty = m_presets->current_is_dirty(); + if (preset_name.empty()) { + if (delete_current) { + // Find an alternate preset to be selected after the current preset is deleted. + const std::deque &presets = this->m_presets->get_presets(); + size_t idx_current = this->m_presets->get_idx_selected(); + // Find the next visible preset. + size_t idx_new = idx_current + 1; + if (idx_new < presets.size()) + for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; + if (idx_new == presets.size()) + for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); + preset_name = presets[idx_new].name; + } else { + // If no name is provided, select the "-- default --" preset. + preset_name = m_presets->default_preset().name; + } + } + assert(! delete_current || (m_presets->get_edited_preset().name != preset_name && m_presets->get_edited_preset().is_user())); + bool current_dirty = ! delete_current && m_presets->current_is_dirty(); bool print_tab = m_presets->type() == Preset::TYPE_PRINT || m_presets->type() == Preset::TYPE_SLA_PRINT; bool printer_tab = m_presets->type() == Preset::TYPE_PRINTER; bool canceled = false; m_dependent_tabs = {}; - if (current_dirty && !may_discard_current_dirty_preset()) { + if (current_dirty && ! may_discard_current_dirty_preset()) { canceled = true; } else if (print_tab) { // Before switching the print profile to a new one, verify, whether the currently active filament or SLA material @@ -2719,12 +2737,25 @@ void Tab::select_preset(std::string preset_name) // The preset will be switched to a different, compatible preset, or the '-- default --'. if (pu.technology == new_printer_technology) m_dependent_tabs.emplace_back(pu.tab_type); - if (pu.old_preset_dirty) + if (pu.old_preset_dirty && !pu.new_preset_compatible) pu.presets->discard_current_changes(); } } } } + + if (! canceled && delete_current) { + // Delete the file and select some other reasonable preset. + // It does not matter which preset will be made active as the preset will be re-selected from the preset_name variable. + // The 'external' presets will only be removed from the preset list, their files will not be deleted. + try { + m_presets->delete_current_preset(); + } catch (const std::exception & /* e */) { + //FIXME add some error reporting! + canceled = true; + } + } + if (canceled) { update_tab_ui(); // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, @@ -2733,15 +2764,34 @@ void Tab::select_preset(std::string preset_name) } else { if (current_dirty) m_presets->discard_current_changes(); - m_presets->select_preset_by_name(preset_name, false); + + const bool is_selected = m_presets->select_preset_by_name(preset_name, false) || delete_current; + assert(m_presets->get_edited_preset().name == preset_name || ! is_selected); // 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. - if (current_dirty || print_tab || printer_tab) + if (current_dirty || delete_current || print_tab || printer_tab) m_preset_bundle->update_compatible(true); // Initialize the UI from the current preset. if (printer_tab) static_cast(this)->update_pages(); + + if (! is_selected && printer_tab) + { + /* There is a case, when : + * after Config Wizard applying we try to select previously selected preset, but + * in a current configuration this one: + * 1. doesn't exist now, + * 2. have another printer_technology + * So, it is necessary to update list of dependent tabs + * to the corresponding printer_technology + */ + const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); + if (printer_technology == ptFFF && m_dependent_tabs.front() != Preset::Type::TYPE_PRINT) + m_dependent_tabs = { Preset::Type::TYPE_PRINT, Preset::Type::TYPE_FILAMENT }; + else if (printer_technology == ptSLA && m_dependent_tabs.front() != Preset::Type::TYPE_SLA_PRINT) + m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; + } load_current_preset(); } } @@ -2813,7 +2863,7 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) #ifdef __linux__ std::unique_ptr no_updates(new wxWindowUpdateLocker(this)); #else - wxWindowUpdateLocker noUpdates(this); +// wxWindowUpdateLocker noUpdates(this); #endif if (m_pages.empty()) @@ -2833,17 +2883,22 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) if (page == nullptr) return; for (auto& el : m_pages) - el.get()->Hide(); +// if (el.get()->IsShown()) { + el.get()->Hide(); +// break; +// } -#ifdef __linux__ - no_updates.reset(nullptr); -#endif - - page->Show(); - m_hsizer->Layout(); - Refresh(); + #ifdef __linux__ + no_updates.reset(nullptr); + #endif update_undo_buttons(); + page->Show(); +// if (! page->layout_valid) { + page->layout_valid = true; + m_hsizer->Layout(); + Refresh(); +// } } void Tab::OnKeyDown(wxKeyEvent& event) @@ -2867,11 +2922,14 @@ void Tab::save_preset(std::string name /*= ""*/) //! m_treectrl->OnSetFocus(); if (name.empty()) { - auto preset = m_presets->get_selected_preset(); + const Preset &preset = m_presets->get_selected_preset(); auto default_name = preset.is_default ? "Untitled" : preset.name; + if (preset.is_system) { + default_name += " - "; + default_name += _(L("Copy")).ToUTF8().data(); + } bool have_extention = boost::iends_with(default_name, ".ini"); - if (have_extention) - { + if (have_extention) { size_t len = default_name.length()-4; default_name.resize(len); } @@ -2915,7 +2973,7 @@ void Tab::save_preset(std::string name /*= ""*/) // If current profile is saved, "delete preset" button have to be enabled m_btn_delete_preset->Enable(true); - if (m_name == "printer") + if (m_type == Preset::TYPE_PRINTER) static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; update_changed_ui(); } @@ -2932,15 +2990,9 @@ void Tab::delete_preset() if (current_preset.is_default || wxID_YES != wxMessageDialog(parent(), msg, title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal()) return; - // 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. - try{ m_presets->delete_current_preset(); } - catch (const std::exception & /* e */) - { - return; - } - // Load the newly selected preset into the UI, update selection combo boxes with their dirty flags. - load_current_preset(); + // Select will handle of the preset dependencies, of saving & closing the depending profiles, and + // finally of deleting the preset. + this->select_preset("", true); } void Tab::toggle_show_hide_incompatible() @@ -2983,7 +3035,9 @@ void Tab::update_ui_from_settings() wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &deps) { deps.checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); + deps.checkbox->SetFont(Slic3r::GUI::wxGetApp().normal_font()); deps.btn = new wxButton(parent, wxID_ANY, _(L(" Set "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); + deps.btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); // deps.btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("printer_empty.png")), wxBITMAP_TYPE_PNG)); deps.btn->SetBitmap(create_scaled_bitmap("printer_empty.png")); @@ -3179,6 +3233,7 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la } // auto bmp = new wxStaticBitmap(parent, wxID_ANY, bmp_name.empty() ? wxNullBitmap : wxBitmap(from_u8(var(bmp_name)), wxBITMAP_TYPE_PNG)); auto bmp = new wxStaticBitmap(parent, wxID_ANY, bmp_name.empty() ? wxNullBitmap : create_scaled_bitmap(bmp_name)); + bmp->SetBackgroundStyle(wxBG_STYLE_PAINT); return bmp; }; @@ -3394,7 +3449,8 @@ void TabSLAPrint::build() optgroup->append_single_option_line("support_pillar_diameter"); optgroup->append_single_option_line("support_pillar_connection_mode"); optgroup->append_single_option_line("support_buildplate_only"); - optgroup->append_single_option_line("support_pillar_widening_factor"); + // TODO: This parameter is not used at the moment. + // optgroup->append_single_option_line("support_pillar_widening_factor"); optgroup->append_single_option_line("support_base_diameter"); optgroup->append_single_option_line("support_base_height"); optgroup->append_single_option_line("support_object_elevation"); @@ -3402,6 +3458,7 @@ void TabSLAPrint::build() optgroup = page->new_optgroup(_(L("Connection of the support sticks and junctions"))); optgroup->append_single_option_line("support_critical_angle"); optgroup->append_single_option_line("support_max_bridge_length"); + optgroup->append_single_option_line("support_max_pillar_link_distance"); optgroup = page->new_optgroup(_(L("Automatic generation"))); optgroup->append_single_option_line("support_points_density_relative"); @@ -3460,11 +3517,37 @@ void TabSLAPrint::update() return; // #ys_FIXME // #ys_FIXME -// m_update_cnt++; -// ! something to update -// m_update_cnt--; -// -// if (m_update_cnt == 0) + m_update_cnt++; + + double head_penetration = m_config->opt_float("support_head_penetration"); + double head_width = m_config->opt_float("support_head_width"); + if(head_penetration > head_width) { + wxString msg_text = _(L("Head penetration should not be greater than the head width.")); + auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK); + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_OK) { + new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width)); + } + + load_config(new_conf); + } + + double pinhead_d = m_config->opt_float("support_head_front_diameter"); + double pillar_d = m_config->opt_float("support_pillar_diameter"); + if(pinhead_d > pillar_d) { + wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter.")); + auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK); + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_OK) { + new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0)); + } + + load_config(new_conf); + } + + m_update_cnt--; + + if (m_update_cnt == 0) wxGetApp().mainframe->on_config_changed(m_config); } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index bf30dc572..1412d398c 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -65,6 +65,9 @@ public: bool m_is_modified_values{ false }; bool m_is_nonsys_values{ true }; + // Delayed layout after resizing the main window. + bool layout_valid = false; + public: std::vector m_optgroups; DynamicPrintConfig* m_config; @@ -204,6 +207,9 @@ protected: void set_type(); int m_em_unit; + // To avoid actions with no-completed Tab + bool m_complited { false }; + ConfigOptionMode m_mode = comSimple; public: PresetBundle* m_preset_bundle; @@ -226,13 +232,15 @@ public: wxString title() const { return m_title; } std::string name() const { return m_name; } Preset::Type type() const { return m_type; } + bool complited() const { return m_complited; } virtual bool supports_printer_technology(const PrinterTechnology tech) = 0; void create_preset_tab(); void load_current_preset(); - void rebuild_page_tree(bool tree_sel_change_event = false); + void rebuild_page_tree(); void update_page_tree_visibility(); - void select_preset(std::string preset_name = ""); + // Select a new preset, possibly delete the current one. + void select_preset(std::string preset_name = "", bool delete_current = false); bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = ""); bool may_switch_to_SLA_preset(); @@ -264,6 +272,7 @@ public: void update_tab_ui(); void load_config(const DynamicPrintConfig& config); virtual void reload_config(); + void update_mode(); void update_visibility(); Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; bool set_value(const t_config_option_key& opt_key, const boost::any& value); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index e83101d22..dee18ad43 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1958,21 +1958,8 @@ int PrusaDoubleSlider::get_value_from_position(const wxCoord x, const wxCoord y) return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5); } -void PrusaDoubleSlider::detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel /*= false*/) +void PrusaDoubleSlider::detect_selected_slider(const wxPoint& pt) { - if (is_mouse_wheel) - { - if (is_horizontal()) { - m_selection = pt.x <= m_rect_lower_thumb.GetRight() ? ssLower : - pt.x >= m_rect_higher_thumb.GetLeft() ? ssHigher : ssUndef; - } - else { - m_selection = pt.y >= m_rect_lower_thumb.GetTop() ? ssLower : - pt.y <= m_rect_higher_thumb.GetBottom() ? ssHigher : ssUndef; - } - return; - } - m_selection = is_point_in_rect(pt, m_rect_lower_thumb) ? ssLower : is_point_in_rect(pt, m_rect_higher_thumb) ? ssHigher : ssUndef; } @@ -2192,12 +2179,20 @@ void PrusaDoubleSlider::action_tick(const TicksAction action) void PrusaDoubleSlider::OnWheel(wxMouseEvent& event) { - wxClientDC dc(this); - wxPoint pos = event.GetLogicalPosition(dc); - detect_selected_slider(pos, true); - - if (m_selection == ssUndef) - return; + // Set nearest to the mouse thumb as a selected, if there is not selected thumb + if (m_selection == ssUndef) + { + const wxPoint& pt = event.GetLogicalPosition(wxClientDC(this)); + + if (is_horizontal()) + m_selection = abs(pt.x - m_rect_lower_thumb.GetRight()) <= + abs(pt.x - m_rect_higher_thumb.GetLeft()) ? + ssLower : ssHigher; + else + m_selection = abs(pt.y - m_rect_lower_thumb.GetTop()) <= + abs(pt.y - m_rect_higher_thumb.GetBottom()) ? + ssLower : ssHigher; + } move_current_thumb(event.GetWheelRotation() > 0); } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index e0bd5d091..a15bee256 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -771,7 +771,7 @@ protected: void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const; void update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection); - void detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel = false); + void detect_selected_slider(const wxPoint& pt); void correct_lower_value(); void correct_higher_value(); void move_current_thumb(const bool condition); diff --git a/src/slic3r/Utils/Bonjour.cpp b/src/slic3r/Utils/Bonjour.cpp index bfd9d4828..28b3b2228 100644 --- a/src/slic3r/Utils/Bonjour.cpp +++ b/src/slic3r/Utils/Bonjour.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include #include @@ -34,6 +34,11 @@ namespace Slic3r { // the implementations has been tested with AFL. +// Relevant RFCs: +// https://tools.ietf.org/html/rfc6762.txt +// https://tools.ietf.org/html/rfc6763.txt + + struct DnsName: public std::string { enum @@ -154,9 +159,9 @@ struct DnsQuestion uint16_t type; uint16_t qclass; - DnsQuestion() : - type(0), - qclass(0) + DnsQuestion() + : type(0) + , qclass(0) {} static optional decode(const std::vector &buffer, size_t &offset) @@ -185,10 +190,10 @@ struct DnsResource uint32_t ttl; std::vector data; - DnsResource() : - type(0), - rclass(0), - ttl(0) + DnsResource() + : type(0) + , rclass(0) + , ttl(0) {} static optional decode(const std::vector &buffer, size_t &offset, size_t &dataoffset) @@ -308,9 +313,9 @@ struct DnsRR_TXT TAG = 0x10, }; - std::vector values; + BonjourReply::TxtData data; - static optional decode(const DnsResource &rr) + static optional decode(const DnsResource &rr, const Bonjour::TxtKeys &txt_keys) { const size_t size = rr.data.size(); if (size < 2) { @@ -326,11 +331,21 @@ struct DnsRR_TXT } ++it; - std::string value(val_size, ' '); - std::copy(it, it + val_size, value.begin()); - res.values.push_back(std::move(value)); + const auto it_end = it + val_size; + const auto it_eq = std::find(it, it_end, '='); + if (it_eq > it && it_eq < it_end - 1) { + std::string key(it_eq - it, ' '); + std::copy(it, it_eq, key.begin()); - it += val_size; + if (txt_keys.find(key) != txt_keys.end() || key == "path") { + // This key-value has been requested for + std::string value(it_end - it_eq - 1, ' '); + std::copy(it_eq + 1, it_end, value.begin()); + res.data.insert(std::make_pair(std::move(key), std::move(value))); + } + } + + it = it_end; } return std::move(res); @@ -387,7 +402,7 @@ struct DnsMessage DnsSDMap sdmap; - static optional decode(const std::vector &buffer, optional id_wanted = boost::none) + static optional decode(const std::vector &buffer, const Bonjour::TxtKeys &txt_keys) { const auto size = buffer.size(); if (size < DnsHeader::SIZE + DnsQuestion::MIN_SIZE || size > MAX_SIZE) { @@ -397,10 +412,6 @@ struct DnsMessage DnsMessage res; res.header = DnsHeader::decode(buffer); - if (id_wanted && *id_wanted != res.header.id) { - return boost::none; - } - if (res.header.qdcount > 1 || res.header.ancount > MAX_ANS) { return boost::none; } @@ -416,14 +427,15 @@ struct DnsMessage if (!rr) { return boost::none; } else { - res.parse_rr(buffer, std::move(*rr), dataoffset); + res.parse_rr(buffer, std::move(*rr), dataoffset, txt_keys); } } return std::move(res); } + private: - void parse_rr(const std::vector &buffer, DnsResource &&rr, size_t dataoffset) + void parse_rr(const std::vector &buffer, DnsResource &&rr, size_t dataoffset, const Bonjour::TxtKeys &txt_keys) { switch (rr.type) { case DnsRR_A::TAG: DnsRR_A::decode(this->rr_a, rr); break; @@ -434,7 +446,7 @@ private: break; } case DnsRR_TXT::TAG: { - auto txt = DnsRR_TXT::decode(rr); + auto txt = DnsRR_TXT::decode(rr, txt_keys); if (txt) { this->sdmap.insert_txt(std::move(rr.name), std::move(*txt)); } break; } @@ -444,26 +456,28 @@ private: std::ostream& operator<<(std::ostream &os, const DnsMessage &msg) { - os << "DnsMessage(ID: " << msg.header.id << ", " - << "Q: " << (msg.question ? msg.question->name.c_str() : "none") << ", " - << "A: " << (msg.rr_a ? msg.rr_a->ip.to_string() : "none") << ", " - << "AAAA: " << (msg.rr_aaaa ? msg.rr_aaaa->ip.to_string() : "none") << ", " - << "services: ["; + os << boost::format("DnsMessage(ID: %1%, Q: %2%, A: %3%, AAAA: %4%, services: [") + % msg.header.id + % (msg.question ? msg.question->name.c_str() : "none") + % (msg.rr_a ? msg.rr_a->ip.to_string() : "none") + % (msg.rr_aaaa ? msg.rr_aaaa->ip.to_string() : "none"); - enum { SRV_PRINT_MAX = 3 }; - unsigned i = 0; - for (const auto &sdpair : msg.sdmap) { - os << sdpair.first << ", "; + enum { SRV_PRINT_MAX = 3 }; + unsigned i = 0; + for (const auto &sdpair : msg.sdmap) { + if (i > 0) { os << ", "; } - if (++i >= SRV_PRINT_MAX) { - os << "..."; - break; - } + if (i < SRV_PRINT_MAX) { + os << sdpair.first; + } else { + os << "..."; + break; } - os << "])"; + i++; + } - return os; + return os << "])"; } @@ -472,16 +486,12 @@ struct BonjourRequest static const asio::ip::address_v4 MCAST_IP4; static const uint16_t MCAST_PORT; - uint16_t id; std::vector data; static optional make(const std::string &service, const std::string &protocol); private: - BonjourRequest(uint16_t id, std::vector &&data) : - id(id), - data(std::move(data)) - {} + BonjourRequest(std::vector &&data) : data(std::move(data)) {} }; const asio::ip::address_v4 BonjourRequest::MCAST_IP4{0xe00000fb}; @@ -493,22 +503,15 @@ optional BonjourRequest::make(const std::string &service, const return boost::none; } - std::random_device dev; - std::uniform_int_distribution dist; - uint16_t id = dist(dev); - uint16_t id_big = endian::native_to_big(id); - const char *id_char = reinterpret_cast(&id_big); - std::vector data; data.reserve(service.size() + 18); - // Add the transaction ID - data.push_back(id_char[0]); - data.push_back(id_char[1]); - // Add metadata static const unsigned char rq_meta[] = { - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x00, 0x00, // Query ID (zero for mDNS) + 0x00, 0x00, // Flags + 0x00, 0x01, // One query + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Zero Answer, Authority, and Additional RRs }; std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data)); @@ -522,11 +525,14 @@ optional BonjourRequest::make(const std::string &service, const // Add the rest of PTR record static const unsigned char ptr_tail[] = { - 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x0c, 0x00, 0xff, + 0x05, // length of "label" + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, // "label" string and terminator + 0x00, 0x0c, // Type PTR + 0x00, 0xff, // Class ANY }; std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data)); - return BonjourRequest(id, std::move(data)); + return BonjourRequest(std::move(data)); } @@ -535,31 +541,29 @@ optional BonjourRequest::make(const std::string &service, const struct Bonjour::priv { const std::string service; - const std::string protocol; - const std::string service_dn; + std::string protocol; + std::string service_dn; + TxtKeys txt_keys; unsigned timeout; unsigned retries; - uint16_t rq_id; std::vector buffer; std::thread io_thread; Bonjour::ReplyFn replyfn; Bonjour::CompleteFn completefn; - priv(std::string service, std::string protocol); + priv(std::string &&service); std::string strip_service_dn(const std::string &service_name) const; void udp_receive(udp::endpoint from, size_t bytes); void lookup_perform(); }; -Bonjour::priv::priv(std::string service, std::string protocol) : - service(std::move(service)), - protocol(std::move(protocol)), - service_dn((boost::format("_%1%._%2%.local") % this->service % this->protocol).str()), - timeout(10), - retries(1), - rq_id(0) +Bonjour::priv::priv(std::string &&service) + : service(std::move(service)) + , protocol("tcp") + , timeout(10) + , retries(1) { buffer.resize(DnsMessage::MAX_SIZE); } @@ -585,13 +589,13 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes) } buffer.resize(bytes); - const auto dns_msg = DnsMessage::decode(buffer, rq_id); + auto dns_msg = DnsMessage::decode(buffer, txt_keys); if (dns_msg) { asio::ip::address ip = from.address(); if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip; } else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip; } - for (const auto &sdpair : dns_msg->sdmap) { + for (auto &sdpair : dns_msg->sdmap) { if (! sdpair.second.srv) { continue; } @@ -602,20 +606,12 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes) std::string path; std::string version; + BonjourReply::TxtData txt_data; if (sdpair.second.txt) { - static const std::string tag_path = "path="; - static const std::string tag_version = "version="; - - for (const auto &value : sdpair.second.txt->values) { - if (value.size() > tag_path.size() && value.compare(0, tag_path.size(), tag_path) == 0) { - path = std::move(value.substr(tag_path.size())); - } else if (value.size() > tag_version.size() && value.compare(0, tag_version.size(), tag_version) == 0) { - version = std::move(value.substr(tag_version.size())); - } - } + txt_data = std::move(sdpair.second.txt->data); } - BonjourReply reply(ip, srv.port, std::move(service_name), srv.hostname, std::move(path), std::move(version)); + BonjourReply reply(ip, srv.port, std::move(service_name), srv.hostname, std::move(txt_data)); replyfn(std::move(reply)); } } @@ -623,13 +619,14 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes) void Bonjour::priv::lookup_perform() { + service_dn = (boost::format("_%1%._%2%.local") % service % protocol).str(); + const auto brq = BonjourRequest::make(service, protocol); if (!brq) { return; } auto self = this; - rq_id = brq->id; try { boost::asio::io_service io_service; @@ -684,21 +681,29 @@ void Bonjour::priv::lookup_perform() // API - public part -BonjourReply::BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version) : - ip(std::move(ip)), - port(port), - service_name(std::move(service_name)), - hostname(std::move(hostname)), - path(path.empty() ? std::move(std::string("/")) : std::move(path)), - version(version.empty() ? std::move(std::string("Unknown")) : std::move(version)) +BonjourReply::BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, BonjourReply::TxtData txt_data) + : ip(std::move(ip)) + , port(port) + , service_name(std::move(service_name)) + , hostname(std::move(hostname)) + , txt_data(std::move(txt_data)) { std::string proto; std::string port_suffix; if (port == 443) { proto = "https://"; } if (port != 443 && port != 80) { port_suffix = std::to_string(port).insert(0, 1, ':'); } - if (this->path[0] != '/') { this->path.insert(0, 1, '/'); } + + std::string path = this->path(); + if (path[0] != '/') { path.insert(0, 1, '/'); } full_address = proto + ip.to_string() + port_suffix; - if (this->path != "/") { full_address += path; } + if (path != "/") { full_address += path; } + txt_data["path"] = std::move(path); +} + +std::string BonjourReply::path() const +{ + const auto it = txt_data.find("path"); + return it != txt_data.end() ? it->second : std::string("/"); } bool BonjourReply::operator==(const BonjourReply &other) const @@ -720,14 +725,22 @@ bool BonjourReply::operator<(const BonjourReply &other) const std::ostream& operator<<(std::ostream &os, const BonjourReply &reply) { - os << "BonjourReply(" << reply.ip.to_string() << ", " << reply.service_name << ", " - << reply.hostname << ", " << reply.path << ", " << reply.version << ")"; - return os; + os << boost::format("BonjourReply(%1%, %2%, %3%, %4%") + % reply.ip.to_string() + % reply.service_name + % reply.hostname + % reply.full_address; + + for (const auto &kv : reply.txt_data) { + os << boost::format(", %1%=%2%") % kv.first % kv.second; + } + + return os << ')'; } -Bonjour::Bonjour(std::string service, std::string protocol) : - p(new priv(std::move(service), std::move(protocol))) +Bonjour::Bonjour(std::string service) + : p(new priv(std::move(service))) {} Bonjour::Bonjour(Bonjour &&other) : p(std::move(other.p)) {} @@ -739,6 +752,18 @@ Bonjour::~Bonjour() } } +Bonjour& Bonjour::set_protocol(std::string protocol) +{ + if (p) { p->protocol = std::move(protocol); } + return *this; +} + +Bonjour& Bonjour::set_txt_keys(TxtKeys txt_keys) +{ + if (p) { p->txt_keys = std::move(txt_keys); } + return *this; +} + Bonjour& Bonjour::set_timeout(unsigned timeout) { if (p) { p->timeout = timeout; } diff --git a/src/slic3r/Utils/Bonjour.hpp b/src/slic3r/Utils/Bonjour.hpp index 63f34638c..e61cd1833 100644 --- a/src/slic3r/Utils/Bonjour.hpp +++ b/src/slic3r/Utils/Bonjour.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include @@ -13,16 +15,24 @@ namespace Slic3r { struct BonjourReply { + typedef std::unordered_map TxtData; + boost::asio::ip::address ip; uint16_t port; std::string service_name; std::string hostname; std::string full_address; - std::string path; - std::string version; + + TxtData txt_data; BonjourReply() = delete; - BonjourReply(boost::asio::ip::address ip, uint16_t port, std::string service_name, std::string hostname, std::string path, std::string version); + BonjourReply(boost::asio::ip::address ip, + uint16_t port, + std::string service_name, + std::string hostname, + TxtData txt_data); + + std::string path() const; bool operator==(const BonjourReply &other) const; bool operator<(const BonjourReply &other) const; @@ -39,11 +49,17 @@ public: typedef std::shared_ptr Ptr; typedef std::function ReplyFn; typedef std::function CompleteFn; + typedef std::set TxtKeys; - Bonjour(std::string service, std::string protocol = "tcp"); + Bonjour(std::string service); Bonjour(Bonjour &&other); ~Bonjour(); + // Set requested service protocol, "tcp" by default + Bonjour& set_protocol(std::string protocol); + // Set which TXT key-values should be collected + // Note that "path" is always collected + Bonjour& set_txt_keys(TxtKeys txt_keys); Bonjour& set_timeout(unsigned timeout); Bonjour& set_retries(unsigned retries); // ^ Note: By default there is 1 retry (meaning 1 broadcast is sent). diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp index 77137a7d2..b0d8c0b8d 100644 --- a/src/slic3r/Utils/Duet.cpp +++ b/src/slic3r/Utils/Duet.cpp @@ -34,6 +34,8 @@ Duet::Duet(DynamicPrintConfig *config) : Duet::~Duet() {} +const char* Duet::get_name() const { return "Duet"; } + bool Duet::test(wxString &msg) const { bool connected = connect(msg); @@ -119,6 +121,11 @@ bool Duet::can_test() const return true; } +bool Duet::can_start_print() const +{ + return true; +} + bool Duet::connect(wxString &msg) const { bool res = false; diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp index e1c28d149..04ceec36f 100644 --- a/src/slic3r/Utils/Duet.hpp +++ b/src/slic3r/Utils/Duet.hpp @@ -19,12 +19,15 @@ public: Duet(DynamicPrintConfig *config); virtual ~Duet(); + virtual const char* get_name() const; + virtual bool test(wxString &curl_msg) const; virtual wxString get_test_ok_msg () const; virtual wxString get_test_failed_msg (wxString &msg) const; virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const; virtual bool has_auto_discovery() const; virtual bool can_test() const; + virtual bool can_start_print() const; virtual std::string get_host() const { return host; } private: diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index 2e2e169b8..7cea198bf 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -29,25 +29,29 @@ OctoPrint::OctoPrint(DynamicPrintConfig *config) : OctoPrint::~OctoPrint() {} +const char* OctoPrint::get_name() const { return "OctoPrint"; } + bool OctoPrint::test(wxString &msg) const { // Since the request is performed synchronously here, // it is ok to refer to `msg` from within the closure + const char *name = get_name(); + bool res = true; auto url = make_url("api/version"); - BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Get version at: %1%") % url; + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url; auto http = Http::get(std::move(url)); set_auth(http); http.on_error([&](std::string body, std::string error, unsigned status) { - BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error getting version: %1%, HTTP %2%, body: `%3%`") % error % status % body; + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; res = false; msg = format_error(body, error, status); }) .on_complete([&, this](std::string body, unsigned) { - BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: Got version: %1%") % body; + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body; try { std::stringstream ss(body); @@ -88,6 +92,8 @@ wxString OctoPrint::get_test_failed_msg (wxString &msg) const bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const { + const char *name = get_name(); + const auto upload_filename = upload_data.upload_path.filename(); const auto upload_parent_path = upload_data.upload_path.parent_path(); @@ -101,7 +107,8 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro auto url = make_url("api/files/local"); - BOOST_LOG_TRIVIAL(info) << boost::format("Octoprint: Uploading file %1% at %2%, filename: %3%, path: %4%, print: %5%") + BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%") + % name % upload_data.source_path % url % upload_filename.string() @@ -114,10 +121,10 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro .form_add("path", upload_parent_path.string()) // XXX: slashes on windows ??? .form_add_file("file", upload_data.source_path.string(), upload_filename.string()) .on_complete([&](std::string body, unsigned status) { - BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body; + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body; }) .on_error([&](std::string body, std::string error, unsigned status) { - BOOST_LOG_TRIVIAL(error) << boost::format("Octoprint: Error uploading file: %1%, HTTP %2%, body: `%3%`") % error % status % body; + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; error_fn(format_error(body, error, status)); res = false; }) @@ -144,6 +151,11 @@ bool OctoPrint::can_test() const return true; } +bool OctoPrint::can_start_print() const +{ + return true; +} + bool OctoPrint::validate_version_text(const boost::optional &version_text) const { return version_text ? boost::starts_with(*version_text, "OctoPrint") : true; @@ -172,21 +184,28 @@ std::string OctoPrint::make_url(const std::string &path) const } -// SLAHost +// SL1Host -SLAHost::~SLAHost() {} +SL1Host::~SL1Host() {} -wxString SLAHost::get_test_ok_msg () const +const char* SL1Host::get_name() const { return "SL1Host"; } + +wxString SL1Host::get_test_ok_msg () const { return _(L("Connection to Prusa SLA works correctly.")); } -wxString SLAHost::get_test_failed_msg (wxString &msg) const +wxString SL1Host::get_test_failed_msg (wxString &msg) const { return wxString::Format("%s: %s", _(L("Could not connect to Prusa SLA")), msg); } -bool SLAHost::validate_version_text(const boost::optional &version_text) const +bool SL1Host::can_start_print() const +{ + return false; +} + +bool SL1Host::validate_version_text(const boost::optional &version_text) const { return version_text ? boost::starts_with(*version_text, "Prusa SLA") : false; } diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index 8da149f53..0e372c7c9 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -20,12 +20,15 @@ public: OctoPrint(DynamicPrintConfig *config); virtual ~OctoPrint(); + virtual const char* get_name() const; + virtual bool test(wxString &curl_msg) const; virtual wxString get_test_ok_msg () const; virtual wxString get_test_failed_msg (wxString &msg) const; virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const; virtual bool has_auto_discovery() const; virtual bool can_test() const; + virtual bool can_start_print() const; virtual std::string get_host() const { return host; } protected: @@ -41,14 +44,17 @@ private: }; -class SLAHost: public OctoPrint +class SL1Host: public OctoPrint { public: - SLAHost(DynamicPrintConfig *config) : OctoPrint(config) {} - virtual ~SLAHost(); + SL1Host(DynamicPrintConfig *config) : OctoPrint(config) {} + virtual ~SL1Host(); + + virtual const char* get_name() const; virtual wxString get_test_ok_msg () const; virtual wxString get_test_failed_msg (wxString &msg) const; + virtual bool can_start_print() const ; protected: virtual bool validate_version_text(const boost::optional &version_text) const; }; diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index 0c6d6bb0f..e9e39e695 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -27,14 +27,26 @@ PrintHost::~PrintHost() {} PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) { - const auto opt = config->option>("host_type"); - if (opt == nullptr) { return nullptr; } + PrinterTechnology tech = ptFFF; - switch (opt->value) { - case htOctoPrint: return new OctoPrint(config); - case htDuet: return new Duet(config); - case htSL1: return new SLAHost(config); - default: return nullptr; + { + const auto opt = config->option>("printer_technology"); + if (opt != nullptr) { + tech = opt->value; + } + } + + if (tech == ptFFF) { + const auto opt = config->option>("host_type"); + const auto host_type = opt != nullptr ? opt->value : htOctoPrint; + + switch (host_type) { + case htOctoPrint: return new OctoPrint(config); + case htDuet: return new Duet(config); + default: return nullptr; + } + } else { + return new SL1Host(config); } } diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index d740ea99e..e0aeb463c 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -32,12 +32,15 @@ public: typedef Http::ProgressFn ProgressFn; typedef std::function ErrorFn; + virtual const char* get_name() const = 0; + virtual bool test(wxString &curl_msg) const = 0; virtual wxString get_test_ok_msg () const = 0; virtual wxString get_test_failed_msg (wxString &msg) const = 0; virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const = 0; virtual bool has_auto_discovery() const = 0; virtual bool can_test() const = 0; + virtual bool can_start_print() const = 0; virtual std::string get_host() const = 0; static PrintHost* get_print_host(DynamicPrintConfig *config); diff --git a/src/slic3r_app_msvc.cpp b/src/slic3r_app_msvc.cpp index ff5ad6cfa..48c166406 100644 --- a/src/slic3r_app_msvc.cpp +++ b/src/slic3r_app_msvc.cpp @@ -206,11 +206,8 @@ int wmain(int argc, wchar_t **argv) std::vector argv_extended; argv_extended.emplace_back(argv[0]); -#ifdef SLIC3R_WRAPPER_GUI - std::wstring cmd_gui = L"--gui"; - argv_extended.emplace_back(const_cast(cmd_gui.data())); -#endif - for (int i = 1; i < argc; ++i) + // Here one may push some additional parameters based on the wrapper type. + for (int i = 1; i < argc; ++ i) argv_extended.emplace_back(argv[i]); argv_extended.emplace_back(nullptr); diff --git a/version.inc b/version.inc index b9d77933a..833c11d5c 100644 --- a/version.inc +++ b/version.inc @@ -2,7 +2,7 @@ # (the version numbers are generated by the build script from the git current label) set(SLIC3R_FORK_NAME "Slic3r++") -set(SLIC3R_VERSION "1.42.0-alpha7.0") +set(SLIC3R_VERSION "1.42.0-beta1.0") set(SLIC3R_BUILD "${SLIC3R_VERSION}+UNKNOWN") set(SLIC3R_BUILD_ID "${SLIC3R_BUILD_ID}") set(SLIC3R_RC_VERSION "1,42,0,0") diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index bcf84b957..463f7d21b 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -115,6 +115,8 @@ SV* ConfigOption_to_SV(const ConfigOption &opt, const ConfigOptionDef &def) } case coPoint: return perl_to_SV_clone_ref(static_cast(&opt)->value); + case coPoint3: + return perl_to_SV_clone_ref(static_cast(&opt)->value); case coPoints: { auto optv = static_cast(&opt); @@ -248,6 +250,9 @@ bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* v } case coPoint: return from_SV_check(value, &static_cast(opt)->value); +// case coPoint3: + // not gonna fix it, die Perl die! +// return from_SV_check(value, &static_cast(opt)->value); case coPoints: { std::vector &values = static_cast(opt)->values; diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index d5d295839..f9482bd3b 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -159,6 +159,8 @@ print_config_def() opt_type = "s@"; } else if (optdef->type == coPoint || optdef->type == coPoints) { opt_type = "point"; + } else if (optdef->type == coPoint3) { + opt_type = "point3"; } else if (optdef->type == coBool || optdef->type == coBools) { opt_type = "bool"; } else if (optdef->type == coEnum) {