diff --git a/README.md b/README.md index 2b93a47b01..7fdf11bbb4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # PrusaSlicer You may want to check the [PrusaSlicer project page](https://www.prusa3d.com/prusaslicer/). -Prebuilt Windows, OSX and Linux binaries are available through the [git releases page](https://github.com/prusa3d/PrusaSlicer/releases) or from the [Prusa3D downloads page](https://www.prusa3d.com/drivers/). +Prebuilt Windows, OSX and Linux binaries are available through the [git releases page](https://github.com/prusa3d/PrusaSlicer/releases) or from the [Prusa3D downloads page](https://www.prusa3d.com/drivers/). There are also [3rd party Linux builds available](https://github.com/prusa3d/PrusaSlicer/wiki/PrusaSlicer-on-Linux---binary-distributions). PrusaSlicer takes 3D models (STL, OBJ, AMF) and converts them into G-code instructions for FFF printers or PNG layers for mSLA 3D printers. It's diff --git a/doc/Dependencies.md b/doc/Dependencies.md index 137aaf17ba..f37f454668 100644 --- a/doc/Dependencies.md +++ b/doc/Dependencies.md @@ -11,6 +11,9 @@ * openssl * nlopt * openvdb: This library depends on other libs, namely boost, zlib, openexr, blosc (not strictly), etc... +* CGAL: Needs additional dependencies + * MPFR + * GMP ## External libraries in source tree * ad-mesh: Lots of customization, have to be bundled in the source tree. diff --git a/doc/How to build - Linux et al.md b/doc/How to build - Linux et al.md index cf47c93921..090a97e6c3 100644 --- a/doc/How to build - Linux et al.md +++ b/doc/How to build - Linux et al.md @@ -1,80 +1,103 @@ # Building PrusaSlicer on UNIX/Linux -PrusaSlicer uses the CMake build system and requires several dependencies. -The dependencies can be listed in the `deps` directory in individual subdirectories, although they don't necessarily need to be as recent -as the versions listed - generally versions available on conservative Linux distros such as Debian stable, Ubuntu LTS releases or Fedora are likely sufficient. +Please understand that PrusaSlicer team cannot support compilation on all possible Linux distros. Namely, we cannot help troubleshoot OpenGL driver issues or dependency issues if compiled against distro provided libraries. **We can only support PrusaSlicer statically linked against the dependencies compiled with the `deps` scripts**, the same way we compile PrusaSlicer for our [binary builds](https://github.com/prusa3d/PrusaSlicer/releases). -Perl is not required anymore. +If you have some reason to link dynamically to your system libraries, you are free to do so, but we can not and will not troubleshoot any issues you possibly run into. -In a typical situation, one would open a command line, go to the PrusaSlicer sources (**the root directory of the repository**), create a directory called `build` or similar, -`cd` into it and call: +Instead of compiling PrusaSlicer from source code, one may also consider to install PrusaSlicer [pre-compiled by contributors](https://github.com/prusa3d/PrusaSlicer/wiki/PrusaSlicer-on-Linux---binary-distributions). +## Step by step guide + +This guide describes building PrusaSlicer statically against dependencies pulled by our `deps` script. Running all the listed commands in order should result in successful build. + +#### 0. Prerequisities + +You must have CMake, GNU build tools and git. If you don't already have them, install them as usual from your distribution packages (e.g. on Ubuntu, you would run `sudo apt-get install cmake build-essential git`, etc.) + +#### 1. Cloning the repository + + +Cloning the repository is simple thanks to git and Github. Simply `cd` into wherever you want to clone PrusaSlicer code base and run +``` +git clone https://www.github.com/prusa3d/PrusaSlicer +cd PrusaSlicer +``` +This will download the source code into a new directory and `cd` into it. You can now optionally select a tag/branch/commit to build using `git checkout`. Otherwise, `master` branch will be built. + + +#### 2. Building dependencies + +PrusaSlicer uses CMake and the build is quite simple, the only tricky part is resolution of dependencies. The supported and recommended way is to build the dependencies first and link to them statically. The source base contains a CMake script that automatically downloads and builds the required dependencies. All that is needed is to run the following (from the top of the cloned repository): + + cd deps + mkdir build + cd build cmake .. - make -jN + make + cd ../.. -where `N` is the number of CPU cores available. -Additional CMake flags may be applicable as explained below. +**Warning**: Once the dependency bundle is installed in a destdir, the destdir cannot be moved elsewhere. This is because wxWidgets hardcode the installation path. -### Dependency resolution -By default PrusaSlicer looks for dependencies the default way CMake looks for them, i.e. in default system locations. -On Linux this will typically make PrusaSlicer depend on dynamically loaded libraries from the system, however, PrusaSlicer can be told -to specifically look for static libraries with the `SLIC3R_STATIC` flag passed to cmake: +#### 3. Building PrusaSlicer - cmake .. -DSLIC3R_STATIC=1 +Now when you have the dependencies compiled, all that is needed is to tell CMake that we are interested in static build and point it to the dependencies. From the top of the repository, run -Additionally, PrusaSlicer can be built in a static manner mostly independent of the system libraries with a dependencies bundle -created using CMake script in the `deps` directory (these are not interconnected with the rest of the CMake scripts). + mkdir build + cd build + cmake .. -DSLIC3R_STATIC=1 -DSLIC3R_PCH=OFF -DCMAKE_PREFIX_PATH=$(pwd)/../deps/build/destdir/usr/local + make -j4 -Note: We say _mostly independent_ because it's still expected the system will provide some transitive dependencies, such as GTK for wxWidgets. +And that's it. You can now run the freshly built PrusaSlicer binary: -To do this, go to the `deps` directory, create a `build` subdirectory (or the like) and use: + cd src + ./prusa-slicer - cmake .. -DDESTDIR= -where the target destdir is a directory of your choosing where the dependencies will be installed. -You can also omit the `DESTDIR` option to use the default, in that case the `destdir` will be created inside the `build` directory where `cmake` is run. -Once the dependencies have been built, in order to pass the destdir path to the **top-level** PrusaSlicer `CMakeLists.txt` script, use the `CMAKE_PREFIX_PATH` option along with turning on `SLIC3R_STATIC`: +#### Troubleshooting - cmake .. -DSLIC3R_STATIC=1 -DCMAKE_PREFIX_PATH=/usr/local +Although most of the dependencies are handled by the build script, we still rely on some system libraries (such as GTK, GL, etc). It is quite likely that you have them already installed, but in case that CMake reports any library missing, install the respective package from your distribution and run CMake again. -Note that `/usr/local` needs to be appended to the destdir path and also the prefix path should be absolute. -**Warning**: Once the dependency bundle is installed in a destdir, the destdir cannot be moved elsewhere. -This is because wxWidgets hardcode the installation path. +## Useful CMake flags when building dependencies -### wxWidgets version +- `-DDESTDIR=` allows to specify a directory where the dependencies will be installed. When not provided, the script creates and uses `destdir` directory where cmake is run. -By default, PrusaSlicer looks for wxWidgets 3.1, this is because the 3.1 version has -a number of bugfixes and improvements not found in 3.0. However, it can also be built with wxWidgets 3.0. -This is done by passing this option to CMake: +- `-DDEP_DOWNLOAD_DIR=` specifies a directory to cache the downloaded source packages for each library. Can be useful for repeated builds, to avoid unnecessary network traffic. - -DSLIC3R_WX_STABLE=1 +- `-DDEP_WX_GTK3=ON` builds wxWidgets (one of the dependencies) against GTK3 (defaults to OFF) -Note that PrusaSlicer is tested with wxWidgets 3.0 somewhat sporadically and so there may be bugs in bleeding edge releases. + +## Useful CMake flags when building PrusaSlicer +- `-DSLIC3R_ASAN=ON` enables gcc/clang address sanitizer (defaults to `OFF`, requires gcc>4.8 or clang>3.1) +- `-DSLIC3R_GTK=3` to use GTK3 (defaults to `2`). Note that wxWidgets must be built against the same GTK version. +- `-DSLIC3R_STATIC=ON` for static build (defaults to `OFF`) +- `-DSLIC3R_WX_STABLE=ON` to look for wxWidgets 3.0 (defaults to `OFF`) +- `-DCMAKE_BUILD_TYPE=Debug` to build in debug mode (defaults to `Release`) + +See the CMake files to get the complete list. + + + +## Building dynamically + +As already mentioned above, dynamic linking of dependencies is possible, but PrusaSlicer team is unable to troubleshoot (Linux world is way too complex). Feel free to do so, but you are on your own. Several remarks though: + +The list of dependencies can be easily obtained by inspecting the CMake scripts in the `deps/` directory. Many don't necessarily need to be as recent +as the versions listed - generally versions available on conservative Linux distros such as Debian stable, Ubuntu LTS releases or Fedora are likely sufficient. If you decide to build this way, it is your responsibility to make sure that CMake finds all required dependencies. It is possible to look at your distribution PrusaSlicer package to see how the package maintainers solved the dependency issues. + +#### wxWidgets +By default, PrusaSlicer looks for wxWidgets 3.1. Our build script in fact downloads specific patched version of wxWidgets. If you want to link against wxWidgets 3.0 (which are still provided by most distributions because wxWidgets 3.1 have not yet been declared stable), you must set `-DSLIC3R_WX_STABLE=ON` when running CMake. Note that while PrusaSlicer can be linked against wWidgets 3.0, the combination is not well tested and there might be bugs in the resulting application. When building on ubuntu 20.04 focal fossa, the package libwxgtk3.0-gtk3-dev needs to be installed instead of libwxgtk3.0-dev and you should use: +``` +-DSLIC3R_WX_STABLE=1 -DSLIC3R_GTK=3 +``` - -DSLIC3R_WX_STABLE=1 -DSLIC3R_GTK=3 - -### Build variant - -By default PrusaSlicer 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 PrusaSlicer 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. +## Miscellaneous ### Installation @@ -87,3 +110,13 @@ If you instead want PrusaSlicer installed in a structure according to the File S This will make PrusaSlicer look for a fixed-location `share/slic3r-prusa3d` directory instead (note that the location becomes hardcoded). You can then use the `make install` target to install PrusaSlicer. + +### Desktop Integration (PrusaSlicer 2.4 and newer) + +If PrusaSlicer is to be distributed as an AppImage or a binary blob (.tar.gz and similar), then a desktop integration support is compiled in by default: PrusaSlicer will offer to integrate with desktop by manually copying the desktop file and application icon into user's desktop configuration. The built-in desktop integration is also handy on Crosstini (Linux on Chrome OS). + +If PrusaSlicer is compiled with `SLIC3R_FHS` enabled, then a desktop integration support will not be integrated. One may want to disable desktop integration by running + + cmake .. -DSLIC3R_DESKTOP_INTEGRATION=0 + +when building PrusaSlicer for flatpack or snap, where the desktop integration is performed by the installer. diff --git a/resources/data/hints.ini b/resources/data/hints.ini index 95060f9ec6..e7b1bb6814 100644 --- a/resources/data/hints.ini +++ b/resources/data/hints.ini @@ -39,6 +39,10 @@ # Open gallery (no aditional var) # hypertext_type = gallery # +#Open top menubar item +#hypertext_menubar_menu_name = (Name in english visible as menu name: File, ) +#hypertext_menubar_item_name = (Name of item in english, if there are three dots at the end of name, put name without three dots) +# # # Each notification can have disabled and enabled modes and techs - divided by ; and space # enabled_tags = ... @@ -77,18 +81,15 @@ hypertext_plater_item = arrange [hint:Negative volume] text = Negative volume\nDid you know that you can subtract one mesh from another using the Negative volume modifier? That way you can, for example, create easily resizable holes directly in PrusaSlicer. Read more in the documentation. (Requires Advanced or Expert mode.) -hypertext_type = link documentation_link = https://help.prusa3d.com/en/article/negative-volume_238503 disabled_tags = SLA; simple [hint:Simplify mesh] text = Simplify mesh\nDid you know that you can reduce the number of triangles in a mesh using the Simplify mesh feature? Right-click the model and select Simplify model. Read more in the documentation. -hypertext_type = link documentation_link = https://help.prusa3d.com/en/article/simplify-mesh_238941 [hint:Reload from disk] text = Reload from disk\nDid you know that if you created a newer version of your model, you can simply reload it in PrusaSlicer? Right-click the model in the 3D view and choose Reload from disk. Read more in the documentation. -hypertext_type = link documentation_link = https://help.prusa3d.com/en/article/reload-from-disk_120427 [hint:Hiding sidebar] @@ -131,7 +132,6 @@ hypertext_plater_item = undo [hint:Different layer height for each model] text = Different layer height for each model\nDid you know that you can print each model on the plater with a different layer height? Right-click the model in the 3D view, choose Layers and Perimeters and adjust the values in the right panel. Read more in the documentation. -hypertext_type = link documentation_link= https://help.prusa3d.com/en/article/per-model-settings_1674 disabled_tags = SLA @@ -168,7 +168,6 @@ text = Load config from G-code\nDid you know that you can use File-Import Config [hint:Ironing] text = Ironing\nDid you know that you can smooth top surfaces of prints using Ironing? The nozzle will run a special second infill phase at the same layer to fill in holes and flatten any lifted plastic. Read more in the documentation. (Requires Advanced or Expert mode.) -hypertext_type = link documentation_link = https://help.prusa3d.com/en/article/ironing_177488 disabled_tags = SLA; simple @@ -186,20 +185,20 @@ disabled_tags = SLA; simple [hint:Insert Pause] text = Insert Pause\nDid you know that you can schedule the print to pause at a specific layer? Right-click the layer slider in the Preview and select Add pause print (M601). This can be used to insert magnets, weights or nuts into your prints. Read more in the documentation. -hypertext_type = link documentation_link = https://help.prusa3d.com/en/article/insert-pause-or-custom-g-code-at-layer_120490#insert-pause-at-layer disabled_tags = SLA [hint:Insert Custom G-code] text = Insert Custom G-code\nDid you know that you can insert a custom G-code at a specific layer? Right-click the layer in the Preview and select Add custom G-code. With this function you can, for example, create a temperature tower. Read more in the documentation. -hypertext_type = link documentation_link = https://help.prusa3d.com/en/article/insert-pause-or-custom-g-code-at-layer_120490#insert-custom-g-code-at-layer disabled_tags = SLA [hint:Configuration snapshots] -text = Configuration snapshots\nDid you know that roll back to a complete backup of all system and user profiles? You can view and move back and forth between snapshots using the Configuration - Configuration snapshots menu. Read more in the documentation. -hypertext_type = link +text = Configuration snapshots\nDid you know that roll back to a complete backup of all system and user profiles? You can view and move back and forth between snapshots using the Configuration - Configuration snapshots menu. documentation_link = https://help.prusa3d.com/en/article/configuration-snapshots_1776 +hypertext_type = menubar +hypertext_menubar_menu_name = Configuration +hypertext_menubar_item_name = Configuration Snapshots [hint:Minimum wall thickness] text = Minimum wall thickness\nDid you know that instead of the number of top and bottom layers, you can define theMinimum shell thicknessin millimeters? This feature is especially useful when using the variable layer height function. @@ -216,7 +215,6 @@ hypertext_preferences_page = 2 [hint:Adaptive infills] text = Adaptive infills\nDid you know that you can use the Adaptive cubic and Support cubic infills to decrease the print time and lower the filament consumption? Read more in the documentation. -hypertext_type = link documentation_link = https://help.prusa3d.com/en/article/infill-patterns_177130 disabled_tags = SLA diff --git a/resources/icons/PrusaSlicer-gcodeviewer-mac_128px.png b/resources/icons/PrusaSlicer-gcodeviewer-mac_128px.png new file mode 100644 index 0000000000..b62f3fddc8 Binary files /dev/null and b/resources/icons/PrusaSlicer-gcodeviewer-mac_128px.png differ diff --git a/resources/icons/PrusaSlicer-mac_128px.png b/resources/icons/PrusaSlicer-mac_128px.png new file mode 100644 index 0000000000..ff0b093beb Binary files /dev/null and b/resources/icons/PrusaSlicer-mac_128px.png differ diff --git a/resources/icons/PrusaSlicer.icns b/resources/icons/PrusaSlicer.icns index 051477dc37..5f6a7feb27 100644 Binary files a/resources/icons/PrusaSlicer.icns and b/resources/icons/PrusaSlicer.icns differ diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 37d144ff0f..8a34a9a113 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,5 +1,5 @@ min_slic3r_version = 2.4.0-alpha0 -1.4.0-alpha7 Updated brim_offset value. Updated Prusa MINI end g-code. Added Filamentworld filament profiles. +1.4.0-alpha7 Updated brim_separation value. Updated Prusa MINI end g-code. Added Filamentworld filament profiles. 1.4.0-alpha6 Added nozzle priming after M600. Added nozzle diameter checks for 0.8 nozzle printer profiles. Updated FW version. Increased number of top solid infill layers (0.2 layer height). 1.4.0-alpha5 Added multiple add:north and Extrudr filament profiles. Updated support head settings (SL1S). 1.4.0-alpha4 Decreased Area Fill (SL1S). diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 0ed5b59a6d..47a3883b4b 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -144,7 +144,7 @@ bridge_angle = 0 bridge_flow_ratio = 1 bridge_speed = 25 brim_width = 0 -brim_offset = 0.1 +brim_separation = 0.1 clip_multipart_objects = 1 compatible_printers = complete_objects = 0 @@ -213,6 +213,7 @@ support_material_angle = 0 support_material_buildplate_only = 0 support_material_enforce_layers = 0 support_material_contact_distance = 0.2 +raft_contact_distance = 0.2 support_material_interface_contact_loops = 0 support_material_interface_layers = 2 support_material_interface_spacing = 0.2 @@ -432,6 +433,7 @@ solid_infill_speed = 30 support_material_extrusion_width = 0.33 support_material_spacing = 1.5 support_material_contact_distance = 0.15 +raft_contact_distance = 0.15 perimeter_acceleration = 300 perimeter_speed = 30 perimeters = 3 @@ -461,6 +463,7 @@ layer_height = 0.1 perimeter_acceleration = 800 top_solid_layers = 9 support_material_contact_distance = 0.17 +raft_contact_distance = 0.17 [print:*0.15mm*] inherits = *common* @@ -613,6 +616,7 @@ solid_infill_extrusion_width = 0.7 top_infill_extrusion_width = 0.45 support_material_extrusion_width = 0.37 support_material_contact_distance = 0.1 +raft_contact_distance = 0.2 top_solid_infill_speed = 40 thick_bridges = 1 @@ -669,6 +673,7 @@ small_perimeter_speed = 15 solid_infill_speed = 20 support_material_speed = 20 support_material_contact_distance = 0.07 +raft_contact_distance = 0.07 [print:0.10mm DETAIL @0.25 nozzle] inherits = *0.10mm*; *0.25nozzle* @@ -683,6 +688,7 @@ small_perimeter_speed = 15 solid_infill_speed = 40 top_solid_infill_speed = 30 support_material_contact_distance = 0.07 +raft_contact_distance = 0.07 [print:0.15mm OPTIMAL @0.25 nozzle] inherits = *0.15mm*; *0.25nozzle* @@ -698,6 +704,7 @@ small_perimeter_speed = 15 solid_infill_speed = 40 top_solid_infill_speed = 30 support_material_contact_distance = 0.08 +raft_contact_distance = 0.08 ## MK2 - 0.6mm nozzle @@ -923,6 +930,7 @@ solid_infill_extrusion_width = 0.5 top_infill_extrusion_width = 0.45 support_material_extrusion_width = 0.38 support_material_contact_distance = 0.2 +raft_contact_distance = 0.2 ## MK3 - MMU2 specific [print:0.15mm SOLUBLE FULL @MK3] @@ -977,6 +985,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and fill_pattern = grid fill_density = 20% support_material_contact_distance = 0.07 +raft_contact_distance = 0.07 [print:0.07mm ULTRADETAIL @0.25 nozzle MK3] inherits = *0.07mm*; *0.25nozzle*; *MK3* @@ -988,6 +997,7 @@ top_solid_infill_speed = 20 fill_pattern = grid fill_density = 20% support_material_contact_distance = 0.07 +raft_contact_distance = 0.07 [print:0.10mm DETAIL @0.25 nozzle MK3] inherits = *0.10mm*; *0.25nozzleMK3*; *MK3* @@ -995,6 +1005,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and fill_pattern = grid fill_density = 20% support_material_contact_distance = 0.07 +raft_contact_distance = 0.07 [print:0.15mm QUALITY @0.25 nozzle MK3] inherits = *0.15mm*; *0.25nozzleMK3*; *MK3* @@ -1002,6 +1013,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and fill_pattern = grid fill_density = 20% support_material_contact_distance = 0.08 +raft_contact_distance = 0.08 ## MK3 - 0.6mm nozzle @@ -1016,6 +1028,7 @@ perimeter_speed = 45 solid_infill_speed = 70 top_solid_infill_speed = 45 support_material_contact_distance = 0.22 +raft_contact_distance = 0.22 bridge_flow_ratio = 1 [print:0.20mm DETAIL @0.6 nozzle MK3] @@ -1029,6 +1042,7 @@ perimeter_speed = 45 solid_infill_speed = 70 top_solid_infill_speed = 45 support_material_contact_distance = 0.22 +raft_contact_distance = 0.22 bridge_flow_ratio = 1 [print:0.30mm QUALITY @0.6 nozzle MK3] @@ -1042,6 +1056,7 @@ perimeter_speed = 45 solid_infill_speed = 70 top_solid_infill_speed = 45 support_material_contact_distance = 0.25 +raft_contact_distance = 0.25 bridge_flow_ratio = 1 [print:0.35mm SPEED @0.6 nozzle MK3] @@ -1059,6 +1074,7 @@ perimeter_extrusion_width = 0.68 infill_extrusion_width = 0.68 solid_infill_extrusion_width = 0.68 support_material_contact_distance = 0.25 +raft_contact_distance = 0.25 bridge_flow_ratio = 0.95 [print:0.40mm DRAFT @0.6 nozzle MK3] @@ -1076,6 +1092,7 @@ perimeter_extrusion_width = 0.68 infill_extrusion_width = 0.68 solid_infill_extrusion_width = 0.68 support_material_contact_distance = 0.25 +raft_contact_distance = 0.25 bridge_flow_ratio = 0.95 ## MK3 - MMU2 specific @@ -1255,6 +1272,7 @@ solid_infill_extrusion_width = 0.45 top_infill_extrusion_width = 0.4 support_material_xy_spacing = 60% support_material_contact_distance = 0.2 +raft_contact_distance = 0.2 # MINI - 0.25mm nozzle @@ -1265,6 +1283,7 @@ fill_pattern = grid fill_density = 20% support_material_speed = 30 support_material_contact_distance = 0.07 +raft_contact_distance = 0.07 [print:0.07mm ULTRADETAIL @0.25 nozzle MINI] inherits = *0.07mm*; *0.25nozzle*; *MINI* @@ -1276,6 +1295,7 @@ top_solid_infill_speed = 20 fill_pattern = grid fill_density = 20% support_material_contact_distance = 0.07 +raft_contact_distance = 0.07 [print:0.10mm DETAIL @0.25 nozzle MINI] inherits = *0.10mm*; *0.25nozzleMINI*; *MINI* @@ -1283,6 +1303,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and fill_pattern = grid fill_density = 20% support_material_contact_distance = 0.07 +raft_contact_distance = 0.07 [print:0.15mm QUALITY @0.25 nozzle MINI] inherits = *0.15mm*; *0.25nozzleMINI*; *MINI* @@ -1290,6 +1311,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and fill_pattern = grid fill_density = 20% support_material_contact_distance = 0.08 +raft_contact_distance = 0.08 # MINI - 0.6mm nozzle @@ -1305,6 +1327,7 @@ top_solid_infill_speed = 45 infill_extrusion_width = 0.65 solid_infill_extrusion_width = 0.65 support_material_contact_distance = 0.22 +raft_contact_distance = 0.22 bridge_flow_ratio = 1 [print:0.20mm DETAIL @0.6 nozzle MINI] @@ -1319,6 +1342,7 @@ top_solid_infill_speed = 45 infill_extrusion_width = 0.65 solid_infill_extrusion_width = 0.65 support_material_contact_distance = 0.22 +raft_contact_distance = 0.22 bridge_flow_ratio = 1 [print:0.30mm QUALITY @0.6 nozzle MINI] @@ -1333,6 +1357,7 @@ top_solid_infill_speed = 45 external_perimeter_extrusion_width = 0.68 perimeter_extrusion_width = 0.68 support_material_contact_distance = 0.25 +raft_contact_distance = 0.25 bridge_flow_ratio = 1 [print:0.35mm SPEED @0.6 nozzle MINI] @@ -1347,6 +1372,7 @@ top_solid_infill_speed = 45 external_perimeter_extrusion_width = 0.68 perimeter_extrusion_width = 0.68 support_material_contact_distance = 0.25 +raft_contact_distance = 0.25 bridge_flow_ratio = 0.95 [print:0.40mm DRAFT @0.6 nozzle MINI] @@ -1363,6 +1389,7 @@ perimeter_extrusion_width = 0.68 infill_extrusion_width = 0.68 solid_infill_extrusion_width = 0.68 support_material_contact_distance = 0.25 +raft_contact_distance = 0.25 bridge_flow_ratio = 0.95 # MINI - 0.8mm nozzle @@ -1398,6 +1425,7 @@ infill_speed = 40 solid_infill_speed = 40 support_material_speed = 35 support_material_contact_distance = 0.25 +raft_contact_distance = 0.2 top_solid_infill_speed = 28 external_perimeter_extrusion_width = 1 perimeter_extrusion_width = 1 @@ -6522,7 +6550,7 @@ start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 end_gcode = G1 E-1 F2100 ; retract\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+2, max_print_height)} F720 ; Move print head up{endif}\nG1 X178 Y178 F4200 ; park print head\n{if max_layer_z < max_print_height}G1 Z{z_offset+min(max_layer_z+30, max_print_height)} F720 ; Move print head further up{endif}\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nM221 S100 ; reset flow\nM900 K0 ; reset LA\nM84 ; disable motors 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_MINI\n extruder_colour = -color_change_gcode = M600\nG1 E0.8 F1500 ; prime after color change +color_change_gcode = M600 [printer:Original Prusa MINI & MINI+ 0.25 nozzle] inherits = Original Prusa MINI & MINI+ @@ -6536,7 +6564,6 @@ retract_length = 3 retract_lift = 0.15 retract_before_travel = 1 start_gcode = G90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S170 ; set extruder temp for bed leveling\nM140 S[first_layer_bed_temperature] ; set bed temp\nM109 R170 ; wait for bed leveling temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM204 T1250 ; set travel acceleration\nG28 ; home all without mesh bed level\nG29 ; mesh bed leveling \nM204 T[machine_max_acceleration_travel] ; restore travel acceleration\nM104 S[first_layer_temperature] ; set extruder temp\nG92 E0\nG1 Y-2 X179 F2400\nG1 Z3 F720\nM109 S[first_layer_temperature] ; wait for extruder temp\n\n; intro line\nG1 X170 F1000\nG1 Z0.2 F720\nG1 X110 E8 F600\nG1 X40 E10 F400\nG92 E0\n\nM221 S95 ; set flow -color_change_gcode = M600\nG1 E0.6 F1500 ; prime after color change [printer:Original Prusa MINI & MINI+ 0.6 nozzle] inherits = Original Prusa MINI & MINI+ @@ -6548,7 +6575,6 @@ min_layer_height = 0.15 default_print_profile = 0.30mm QUALITY @0.6 nozzle MINI retract_length = 3.5 retract_before_travel = 1.5 -color_change_gcode = M600\nG1 E1 F1500 ; prime after color change [printer:Original Prusa MINI & MINI+ 0.8 nozzle] inherits = Original Prusa MINI & MINI+ @@ -6560,7 +6586,6 @@ default_print_profile = 0.40mm QUALITY @0.8 nozzle MINI default_filament_profile = Prusament PLA @0.8 nozzle retract_length = 3.5 retract_before_travel = 1.5 -color_change_gcode = M600\nG1 E1.2 F1500 ; prime after color change [printer:Original Prusa SL1] printer_technology = SLA diff --git a/resources/shaders/gouraud_light_instanced.fs b/resources/shaders/gouraud_light_instanced.fs new file mode 100644 index 0000000000..970185a00e --- /dev/null +++ b/resources/shaders/gouraud_light_instanced.fs @@ -0,0 +1,12 @@ +#version 110 + +uniform vec4 uniform_color; +uniform float emission_factor; + +// x = tainted, y = specular; +varying vec2 intensity; + +void main() +{ + gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * (intensity.x + emission_factor), uniform_color.a); +} diff --git a/resources/shaders/gouraud_light_instanced.vs b/resources/shaders/gouraud_light_instanced.vs new file mode 100644 index 0000000000..997b6a2bf9 --- /dev/null +++ b/resources/shaders/gouraud_light_instanced.vs @@ -0,0 +1,48 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +// vertex attributes +attribute vec3 v_position; +attribute vec3 v_normal; +// instance attributes +attribute vec3 i_offset; +attribute vec2 i_scales; + +// x = tainted, y = specular; +varying vec2 intensity; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 eye_normal = normalize(gl_NormalMatrix * v_normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + float width = 1.5 * i_scales.x; + float height = 1.5 * i_scales.y; + vec4 world_position = vec4(v_position * vec3(vec2(width), height) + i_offset - vec3(0.0, 0.0, 0.5 * i_scales.y), 1.0); + vec3 eye_position = (gl_ModelViewMatrix * world_position).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_Position = gl_ProjectionMatrix * vec4(eye_position, 1.0); +} diff --git a/src/fast_float/fast_float.h b/src/fast_float/fast_float.h new file mode 100644 index 0000000000..8974005319 --- /dev/null +++ b/src/fast_float/fast_float.h @@ -0,0 +1,2449 @@ +// fast_float by Daniel Lemire +// fast_float by João Paulo Magalhaes + + +// with contributions from Eugene Golushkov +// with contributions from Maksim Kita +// with contributions from Marcin Wojdyr +// with contributions from Neal Richardson +// with contributions from Tim Paine +// with contributions from Fabio Pellacini + + +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + + +#ifndef FASTFLOAT_FAST_FLOAT_H +#define FASTFLOAT_FAST_FLOAT_H + +#include + +namespace fast_float { +enum chars_format { + scientific = 1<<0, + fixed = 1<<2, + hex = 1<<3, + general = fixed | scientific +}; + + +struct from_chars_result { + const char *ptr; + std::errc ec; +}; + +struct parse_options { + explicit parse_options(chars_format fmt = chars_format::general, + char dot = '.') + : format(fmt), decimal_point(dot) {} + + /** Which number formats are accepted */ + chars_format format; + /** The character used as decimal point */ + char decimal_point; +}; + +/** + * This function parses the character sequence [first,last) for a number. It parses floating-point numbers expecting + * a locale-indepent format equivalent to what is used by std::strtod in the default ("C") locale. + * The resulting floating-point value is the closest floating-point values (using either float or double), + * using the "round to even" convention for values that would otherwise fall right in-between two values. + * That is, we provide exact parsing according to the IEEE standard. + * + * Given a successful parse, the pointer (`ptr`) in the returned value is set to point right after the + * parsed number, and the `value` referenced is set to the parsed value. In case of error, the returned + * `ec` contains a representative error, otherwise the default (`std::errc()`) value is stored. + * + * The implementation does not throw and does not allocate memory (e.g., with `new` or `malloc`). + * + * Like the C++17 standard, the `fast_float::from_chars` functions take an optional last argument of + * the type `fast_float::chars_format`. It is a bitset value: we check whether + * `fmt & fast_float::chars_format::fixed` and `fmt & fast_float::chars_format::scientific` are set + * to determine whether we allowe the fixed point and scientific notation respectively. + * The default is `fast_float::chars_format::general` which allows both `fixed` and `scientific`. + */ +template +from_chars_result from_chars(const char *first, const char *last, + T &value, chars_format fmt = chars_format::general) noexcept; + +/** + * Like from_chars, but accepts an `options` argument to govern number parsing. + */ +template +from_chars_result from_chars_advanced(const char *first, const char *last, + T &value, parse_options options) noexcept; + +} +#endif // FASTFLOAT_FAST_FLOAT_H + + +#ifndef FASTFLOAT_FLOAT_COMMON_H +#define FASTFLOAT_FLOAT_COMMON_H + +#include +#include +#include + +#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \ + || defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) \ + || defined(__MINGW64__) \ + || defined(__s390x__) \ + || (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || defined(__PPC64LE__)) \ + || defined(__EMSCRIPTEN__)) +#define FASTFLOAT_64BIT +#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) \ + || defined(__arm__) || defined(_M_ARM) \ + || defined(__MINGW32__)) +#define FASTFLOAT_32BIT +#else + // Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow. + // We can never tell the register width, but the SIZE_MAX is a good approximation. + // UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max portability. + #if SIZE_MAX == 0xffff + #error Unknown platform (16-bit, unsupported) + #elif SIZE_MAX == 0xffffffff + #define FASTFLOAT_32BIT + #elif SIZE_MAX == 0xffffffffffffffff + #define FASTFLOAT_64BIT + #else + #error Unknown platform (not 32-bit, not 64-bit?) + #endif +#endif + +#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) +#include +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#define FASTFLOAT_VISUAL_STUDIO 1 +#endif + +#ifdef _WIN32 +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#if defined(__APPLE__) || defined(__FreeBSD__) +#include +#elif defined(sun) || defined(__sun) +#include +#else +#include +#endif +# +#ifndef __BYTE_ORDER__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#ifndef __ORDER_LITTLE_ENDIAN__ +// safe choice +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#endif +# +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define FASTFLOAT_IS_BIG_ENDIAN 0 +#else +#define FASTFLOAT_IS_BIG_ENDIAN 1 +#endif +#endif + +#ifdef FASTFLOAT_VISUAL_STUDIO +#define fastfloat_really_inline __forceinline +#else +#define fastfloat_really_inline inline __attribute__((always_inline)) +#endif + +namespace fast_float { + +// Compares two ASCII strings in a case insensitive manner. +inline bool fastfloat_strncasecmp(const char *input1, const char *input2, + size_t length) { + char running_diff{0}; + for (size_t i = 0; i < length; i++) { + running_diff |= (input1[i] ^ input2[i]); + } + return (running_diff == 0) || (running_diff == 32); +} + +#ifndef FLT_EVAL_METHOD +#error "FLT_EVAL_METHOD should be defined, please include cfloat." +#endif + +namespace { +constexpr uint32_t max_digits = 768; +constexpr uint32_t max_digit_without_overflow = 19; +constexpr int32_t decimal_point_range = 2047; +} // namespace + +struct value128 { + uint64_t low; + uint64_t high; + value128(uint64_t _low, uint64_t _high) : low(_low), high(_high) {} + value128() : low(0), high(0) {} +}; + +/* result might be undefined when input_num is zero */ +fastfloat_really_inline int leading_zeroes(uint64_t input_num) { + assert(input_num > 0); +#ifdef FASTFLOAT_VISUAL_STUDIO + #if defined(_M_X64) || defined(_M_ARM64) + unsigned long leading_zero = 0; + // Search the mask data from most significant bit (MSB) + // to least significant bit (LSB) for a set bit (1). + _BitScanReverse64(&leading_zero, input_num); + return (int)(63 - leading_zero); + #else + int last_bit = 0; + if(input_num & uint64_t(0xffffffff00000000)) input_num >>= 32, last_bit |= 32; + if(input_num & uint64_t( 0xffff0000)) input_num >>= 16, last_bit |= 16; + if(input_num & uint64_t( 0xff00)) input_num >>= 8, last_bit |= 8; + if(input_num & uint64_t( 0xf0)) input_num >>= 4, last_bit |= 4; + if(input_num & uint64_t( 0xc)) input_num >>= 2, last_bit |= 2; + if(input_num & uint64_t( 0x2)) input_num >>= 1, last_bit |= 1; + return 63 - last_bit; + #endif +#else + return __builtin_clzll(input_num); +#endif +} + +#ifdef FASTFLOAT_32BIT + +// slow emulation routine for 32-bit +fastfloat_really_inline uint64_t emulu(uint32_t x, uint32_t y) { + return x * (uint64_t)y; +} + +// slow emulation routine for 32-bit +#if !defined(__MINGW64__) +fastfloat_really_inline uint64_t _umul128(uint64_t ab, uint64_t cd, + uint64_t *hi) { + uint64_t ad = emulu((uint32_t)(ab >> 32), (uint32_t)cd); + uint64_t bd = emulu((uint32_t)ab, (uint32_t)cd); + uint64_t adbc = ad + emulu((uint32_t)ab, (uint32_t)(cd >> 32)); + uint64_t adbc_carry = !!(adbc < ad); + uint64_t lo = bd + (adbc << 32); + *hi = emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) + + (adbc_carry << 32) + !!(lo < bd); + return lo; +} +#endif // !__MINGW64__ + +#endif // FASTFLOAT_32BIT + + +// compute 64-bit a*b +fastfloat_really_inline value128 full_multiplication(uint64_t a, + uint64_t b) { + value128 answer; +#ifdef _M_ARM64 + // ARM64 has native support for 64-bit multiplications, no need to emulate + answer.high = __umulh(a, b); + answer.low = a * b; +#elif defined(FASTFLOAT_32BIT) || (defined(_WIN64) && !defined(__clang__)) + answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64 +#elif defined(FASTFLOAT_64BIT) + __uint128_t r = ((__uint128_t)a) * b; + answer.low = uint64_t(r); + answer.high = uint64_t(r >> 64); +#else + #error Not implemented +#endif + return answer; +} + + +struct adjusted_mantissa { + uint64_t mantissa{0}; + int power2{0}; // a negative value indicates an invalid result + adjusted_mantissa() = default; + bool operator==(const adjusted_mantissa &o) const { + return mantissa == o.mantissa && power2 == o.power2; + } + bool operator!=(const adjusted_mantissa &o) const { + return mantissa != o.mantissa || power2 != o.power2; + } +}; + +struct decimal { + uint32_t num_digits{0}; + int32_t decimal_point{0}; + bool negative{false}; + bool truncated{false}; + uint8_t digits[max_digits]; + decimal() = default; + // Copies are not allowed since this is a fat object. + decimal(const decimal &) = delete; + // Copies are not allowed since this is a fat object. + decimal &operator=(const decimal &) = delete; + // Moves are allowed: + decimal(decimal &&) = default; + decimal &operator=(decimal &&other) = default; +}; + +constexpr static double powers_of_ten_double[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; +constexpr static float powers_of_ten_float[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, + 1e6, 1e7, 1e8, 1e9, 1e10}; + +template struct binary_format { + static inline constexpr int mantissa_explicit_bits(); + static inline constexpr int minimum_exponent(); + static inline constexpr int infinite_power(); + static inline constexpr int sign_index(); + static inline constexpr int min_exponent_fast_path(); + static inline constexpr int max_exponent_fast_path(); + static inline constexpr int max_exponent_round_to_even(); + static inline constexpr int min_exponent_round_to_even(); + static inline constexpr uint64_t max_mantissa_fast_path(); + static inline constexpr int largest_power_of_ten(); + static inline constexpr int smallest_power_of_ten(); + static inline constexpr T exact_power_of_ten(int64_t power); +}; + +template <> inline constexpr int binary_format::mantissa_explicit_bits() { + return 52; +} +template <> inline constexpr int binary_format::mantissa_explicit_bits() { + return 23; +} + +template <> inline constexpr int binary_format::max_exponent_round_to_even() { + return 23; +} + +template <> inline constexpr int binary_format::max_exponent_round_to_even() { + return 10; +} + +template <> inline constexpr int binary_format::min_exponent_round_to_even() { + return -4; +} + +template <> inline constexpr int binary_format::min_exponent_round_to_even() { + return -17; +} + +template <> inline constexpr int binary_format::minimum_exponent() { + return -1023; +} +template <> inline constexpr int binary_format::minimum_exponent() { + return -127; +} + +template <> inline constexpr int binary_format::infinite_power() { + return 0x7FF; +} +template <> inline constexpr int binary_format::infinite_power() { + return 0xFF; +} + +template <> inline constexpr int binary_format::sign_index() { return 63; } +template <> inline constexpr int binary_format::sign_index() { return 31; } + +template <> inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -22; +#endif +} +template <> inline constexpr int binary_format::min_exponent_fast_path() { +#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0) + return 0; +#else + return -10; +#endif +} + +template <> inline constexpr int binary_format::max_exponent_fast_path() { + return 22; +} +template <> inline constexpr int binary_format::max_exponent_fast_path() { + return 10; +} + +template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} +template <> inline constexpr uint64_t binary_format::max_mantissa_fast_path() { + return uint64_t(2) << mantissa_explicit_bits(); +} + +template <> +inline constexpr double binary_format::exact_power_of_ten(int64_t power) { + return powers_of_ten_double[power]; +} +template <> +inline constexpr float binary_format::exact_power_of_ten(int64_t power) { + + return powers_of_ten_float[power]; +} + + +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 308; +} +template <> +inline constexpr int binary_format::largest_power_of_ten() { + return 38; +} + +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -342; +} +template <> +inline constexpr int binary_format::smallest_power_of_ten() { + return -65; +} + +} // namespace fast_float + +// for convenience: +template +inline OStream& operator<<(OStream &out, const fast_float::decimal &d) { + out << "0."; + for (size_t i = 0; i < d.num_digits; i++) { + out << int32_t(d.digits[i]); + } + out << " * 10 ** " << d.decimal_point; + return out; +} + +#endif + + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include +#include + + +namespace fast_float { + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } + +fastfloat_really_inline uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 + | (val & 0x00FF000000000000) >> 40 + | (val & 0x0000FF0000000000) >> 24 + | (val & 0x000000FF00000000) >> 8 + | (val & 0x00000000FF000000) << 8 + | (val & 0x0000000000FF0000) << 24 + | (val & 0x000000000000FF00) << 40 + | (val & 0x00000000000000FF) << 56; +} + +fastfloat_really_inline uint64_t read_u64(const char *chars) { + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + ::memcpy(chars, &val, sizeof(uint64_t)); +} + +// credit @aqrit +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { + return parse_eight_digits_unrolled(read_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { + return is_made_of_eight_digits_fast(read_u64(chars)); +} + +struct parsed_number_string { + int64_t exponent; + uint64_t mantissa; + const char *lastmatch; + bool negative; + bool valid; + bool too_many_digits; +}; + + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +fastfloat_really_inline +parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { + const chars_format fmt = options.format; + const char decimal_point = options.decimal_point; + + parsed_number_string answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + if (p == pend) { + return answer; + } + if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot + return answer; + } + } + const char *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - '0'); // might overflow, we will handle the overflow later + ++p; + } + const char *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + int64_t exponent = 0; + if ((p != pend) && (*p == decimal_point)) { + ++p; + // Fast approach only tested under little endian systems + if ((p + 8 <= pend) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + if ((p + 8 <= pend) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + } + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = end_of_integer_part + 1 - p; + digit_count -= exponent; + } + // we must have encountered at least one integer! + if (digit_count == 0) { + return answer; + } + int64_t exp_number = 0; // explicit exponential part + if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { + const char * location_of_e = p; + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if(!(fmt & chars_format::fixed)) { + // We are in error. + return answer; + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if(neg_exp) { exp_number = - exp_number; } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + const char *start = start_digits; + while ((start != pend) && (*start == '0' || *start == decimal_point)) { + if(*start == '0') { digit_count --; } + start++; + } + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + i = 0; + p = start_digits; + const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; + while((i < minimal_nineteen_digit_integer) && (p != pend) && is_integer(*p)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p++; // skip the dot + const char *first_after_period = p; + while((i < minimal_nineteen_digit_integer) && (p != pend) && is_integer(*p)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + exponent = first_after_period - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + + +// This should always succeed since it follows a call to parse_number_string +// This function could be optimized. In particular, we could stop after 19 digits +// and try to bail out. Furthermore, we should be able to recover the computed +// exponent from the pass in parse_number_string. +fastfloat_really_inline decimal parse_decimal(const char *p, const char *pend, parse_options options) noexcept { + const char decimal_point = options.decimal_point; + + decimal answer; + answer.num_digits = 0; + answer.decimal_point = 0; + answer.truncated = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + } + // skip leading zeroes + while ((p != pend) && (*p == '0')) { + ++p; + } + while ((p != pend) && is_integer(*p)) { + if (answer.num_digits < max_digits) { + answer.digits[answer.num_digits] = uint8_t(*p - '0'); + } + answer.num_digits++; + ++p; + } + if ((p != pend) && (*p == decimal_point)) { + ++p; + const char *first_after_period = p; + // if we have not yet encountered a zero, we have to skip it as well + if(answer.num_digits == 0) { + // skip zeros + while ((p != pend) && (*p == '0')) { + ++p; + } + } + // We expect that this loop will often take the bulk of the running time + // because when a value has lots of digits, these digits often + while ((p + 8 <= pend) && (answer.num_digits + 8 < max_digits)) { + uint64_t val = read_u64(p); + if(! is_made_of_eight_digits_fast(val)) { break; } + // We have eight digits, process them in one go! + val -= 0x3030303030303030; + write_u64(answer.digits + answer.num_digits, val); + answer.num_digits += 8; + p += 8; + } + while ((p != pend) && is_integer(*p)) { + if (answer.num_digits < max_digits) { + answer.digits[answer.num_digits] = uint8_t(*p - '0'); + } + answer.num_digits++; + ++p; + } + answer.decimal_point = int32_t(first_after_period - p); + } + // We want num_digits to be the number of significant digits, excluding + // leading *and* trailing zeros! Otherwise the truncated flag later is + // going to be misleading. + if(answer.num_digits > 0) { + // We potentially need the answer.num_digits > 0 guard because we + // prune leading zeros. So with answer.num_digits > 0, we know that + // we have at least one non-zero digit. + const char *preverse = p - 1; + int32_t trailing_zeros = 0; + while ((*preverse == '0') || (*preverse == decimal_point)) { + if(*preverse == '0') { trailing_zeros++; }; + --preverse; + } + answer.decimal_point += int32_t(answer.num_digits); + answer.num_digits -= uint32_t(trailing_zeros); + } + if(answer.num_digits > max_digits) { + answer.truncated = true; + answer.num_digits = max_digits; + } + if ((p != pend) && (('e' == *p) || ('E' == *p))) { + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + int32_t exp_number = 0; // exponential part + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + answer.decimal_point += (neg_exp ? -exp_number : exp_number); + } + // In very rare cases, we may have fewer than 19 digits, we want to be able to reliably + // assume that all digits up to max_digit_without_overflow have been initialized. + for(uint32_t i = answer.num_digits; i < max_digit_without_overflow; i++) { answer.digits[i] = 0; } + + return answer; +} +} // namespace fast_float + +#endif + + +#ifndef FASTFLOAT_FAST_TABLE_H +#define FASTFLOAT_FAST_TABLE_H +#include + +namespace fast_float { + +/** + * When mapping numbers from decimal to binary, + * we go from w * 10^q to m * 2^p but we have + * 10^q = 5^q * 2^q, so effectively + * we are trying to match + * w * 2^q * 5^q to m * 2^p. Thus the powers of two + * are not a concern since they can be represented + * exactly using the binary notation, only the powers of five + * affect the binary significand. + */ + +/** + * The smallest non-zero float (binary64) is 2^−1074. + * We take as input numbers of the form w x 10^q where w < 2^64. + * We have that w * 10^-343 < 2^(64-344) 5^-343 < 2^-1076. + * However, we have that + * (2^64-1) * 10^-342 = (2^64-1) * 2^-342 * 5^-342 > 2^−1074. + * Thus it is possible for a number of the form w * 10^-342 where + * w is a 64-bit value to be a non-zero floating-point number. + ********* + * Any number of form w * 10^309 where w>= 1 is going to be + * infinite in binary64 so we never need to worry about powers + * of 5 greater than 308. + */ +template +struct powers_template { + +constexpr static int smallest_power_of_five = binary_format::smallest_power_of_ten(); +constexpr static int largest_power_of_five = binary_format::largest_power_of_ten(); +constexpr static int number_of_entries = 2 * (largest_power_of_five - smallest_power_of_five + 1); +// Powers of five from 5^-342 all the way to 5^308 rounded toward one. +static const uint64_t power_of_five_128[number_of_entries]; +}; + +template +const uint64_t powers_template::power_of_five_128[number_of_entries] = { + 0xeef453d6923bd65a,0x113faa2906a13b3f, + 0x9558b4661b6565f8,0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76,0x5d79bcf00d2df649, + 0xe95a99df8ace6f53,0xf4d82c2c107973dc, + 0x91d8a02bb6c10594,0x79071b9b8a4be869, + 0xb64ec836a47146f9,0x9748e2826cdee284, + 0xe3e27a444d8d98b7,0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72,0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f,0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723,0xad2c788035e61382, + 0x8b16fb203055ac76,0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793,0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78,0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b,0x8672648c40e5ad68, + 0xa9c98d8ccb009506,0x680efdaf511f18c2, + 0xd43bf0effdc0ba48,0x212bd1b2566def2, + 0x84a57695fe98746d,0x14bb630f7604b57, + 0xa5ced43b7e3e9188,0x419ea3bd35385e2d, + 0xcf42894a5dce35ea,0x52064cac828675b9, + 0x818995ce7aa0e1b2,0x7343efebd1940993, + 0xa1ebfb4219491a1f,0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6,0xd41a26e077774ef6, + 0xfd00b897478238d0,0x8920b098955522b4, + 0x9e20735e8cb16382,0x55b46e5f5d5535b0, + 0xc5a890362fddbc62,0xeb2189f734aa831d, + 0xf712b443bbd52b7b,0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d,0x47b233c92125366e, + 0xc1069cd4eabe89f8,0x999ec0bb696e840a, + 0xf148440a256e2c76,0xc00670ea43ca250d, + 0x96cd2a865764dbca,0x380406926a5e5728, + 0xbc807527ed3e12bc,0xc605083704f5ecf2, + 0xeba09271e88d976b,0xf7864a44c633682e, + 0x93445b8731587ea3,0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c,0x5960ea05bad82964, + 0xe61acf033d1a45df,0x6fb92487298e33bd, + 0x8fd0c16206306bab,0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696,0x8f48a4899877186c, + 0xe0b62e2929aba83c,0x331acdabfe94de87, + 0x8c71dcd9ba0b4925,0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f,0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a,0xc9e82cd9f69d6150, + 0x892731ac9faf056e,0xbe311c083a225cd2, + 0xab70fe17c79ac6ca,0x6dbd630a48aaf406, + 0xd64d3d9db981787d,0x92cbbccdad5b108, + 0x85f0468293f0eb4e,0x25bbf56008c58ea5, + 0xa76c582338ed2621,0xaf2af2b80af6f24e, + 0xd1476e2c07286faa,0x1af5af660db4aee1, + 0x82cca4db847945ca,0x50d98d9fc890ed4d, + 0xa37fce126597973c,0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c,0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f,0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1,0x77b191618c54e9ac, + 0xc795830d75038c1d,0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25,0x4b0573286b44ad1d, + 0x9becce62836ac577,0x4ee367f9430aec32, + 0xc2e801fb244576d5,0x229c41f793cda73f, + 0xf3a20279ed56d48a,0x6b43527578c1110f, + 0x9845418c345644d6,0x830a13896b78aaa9, + 0xbe5691ef416bd60c,0x23cc986bc656d553, + 0xedec366b11c6cb8f,0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39,0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07,0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9,0xd1b3400f8f9cff68, + 0x91376c36d99995be,0x23100809b9c21fa1, + 0xb58547448ffffb2d,0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9,0x16c90c8f323f516c, + 0x8dd01fad907ffc3b,0xae3da7d97f6792e3, + 0xb1442798f49ffb4a,0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d,0x40405643d711d583, + 0x8a7d3eef7f1cfc52,0x482835ea666b2572, + 0xad1c8eab5ee43b66,0xda3243650005eecf, + 0xd863b256369d4a40,0x90bed43e40076a82, + 0x873e4f75e2224e68,0x5a7744a6e804a291, + 0xa90de3535aaae202,0x711515d0a205cb36, + 0xd3515c2831559a83,0xd5a5b44ca873e03, + 0x8412d9991ed58091,0xe858790afe9486c2, + 0xa5178fff668ae0b6,0x626e974dbe39a872, + 0xce5d73ff402d98e3,0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e,0x7ce66634bc9d0b99, + 0xa139029f6a239f72,0x1c1fffc1ebc44e80, + 0xc987434744ac874e,0xa327ffb266b56220, + 0xfbe9141915d7a922,0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5,0x6f773fc3603db4a9, + 0xc4ce17b399107c22,0xcb550fb4384d21d3, + 0xf6019da07f549b2b,0x7e2a53a146606a48, + 0x99c102844f94e0fb,0x2eda7444cbfc426d, + 0xc0314325637a1939,0xfa911155fefb5308, + 0xf03d93eebc589f88,0x793555ab7eba27ca, + 0x96267c7535b763b5,0x4bc1558b2f3458de, + 0xbbb01b9283253ca2,0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb,0x465e15a979c1cadc, + 0x92a1958a7675175f,0xbfacd89ec191ec9, + 0xb749faed14125d36,0xcef980ec671f667b, + 0xe51c79a85916f484,0x82b7e12780e7401a, + 0x8f31cc0937ae58d2,0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07,0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9,0x67a791e093e1d49a, + 0x8bd6a141006042bd,0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d,0x58fae9f773886e18, + 0xda7f5bf590966848,0xaf39a475506a899e, + 0x888f99797a5e012d,0x6d8406c952429603, + 0xaab37fd7d8f58178,0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6,0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26,0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f,0xf42faa48c0ea481e, + 0xd0601d8efc57b08b,0xf13b94daf124da26, + 0x823c12795db6ce57,0x76c53d08d6b70858, + 0xa2cb1717b52481ed,0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268,0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02,0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1,0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a,0x359ab6419ca1091b, + 0xf867241c8cc6d4c0,0xc30163d203c94b62, + 0x9b407691d7fc44f8,0x79e0de63425dcf1d, + 0xc21094364dfb5636,0x985915fc12f542e4, + 0xf294b943e17a2bc4,0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a,0xa705992ceecf9c42, + 0xbd8430bd08277231,0x50c6ff782a838353, + 0xece53cec4a314ebd,0xa4f8bf5635246428, + 0x940f4613ae5ed136,0x871b7795e136be99, + 0xb913179899f68584,0x28e2557b59846e3f, + 0xe757dd7ec07426e5,0x331aeada2fe589cf, + 0x9096ea6f3848984f,0x3ff0d2c85def7621, + 0xb4bca50b065abe63,0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb,0xd3e8495912c62894, + 0x8d3360f09cf6e4bd,0x64712dd7abbbd95c, + 0xb080392cc4349dec,0xbd8d794d96aacfb3, + 0xdca04777f541c567,0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60,0xf41686c49db57244, + 0xac5d37d5b79b6239,0x311c2875c522ced5, + 0xd77485cb25823ac7,0x7d633293366b828b, + 0x86a8d39ef77164bc,0xae5dff9c02033197, + 0xa8530886b54dbdeb,0xd9f57f830283fdfc, + 0xd267caa862a12d66,0xd072df63c324fd7b, + 0x8380dea93da4bc60,0x4247cb9e59f71e6d, + 0xa46116538d0deb78,0x52d9be85f074e608, + 0xcd795be870516656,0x67902e276c921f8b, + 0x806bd9714632dff6,0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3,0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0,0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c,0x796b805720085f81, + 0x9cc3a6eec6311a63,0xcbe3303674053bb0, + 0xc3f490aa77bd60fc,0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b,0xee92fb5515482d44, + 0x991711052d8bf3c5,0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6,0xd262d45a78a0635d, + 0xef340a98172aace4,0x86fb897116c87c34, + 0x9580869f0e7aac0e,0xd45d35e6ae3d4da0, + 0xbae0a846d2195712,0x8974836059cca109, + 0xe998d258869facd7,0x2bd1a438703fc94b, + 0x91ff83775423cc06,0x7b6306a34627ddcf, + 0xb67f6455292cbf08,0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca,0x20caba5f1d9e4a93, + 0x8e938662882af53e,0x547eb47b7282ee9c, + 0xb23867fb2a35b28d,0xe99e619a4f23aa43, + 0xdec681f9f4c31f31,0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e,0xde83bc408dd3dd04, + 0xae0b158b4738705e,0x9624ab50b148d445, + 0xd98ddaee19068c76,0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9,0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc,0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b,0x7647c3200069671f, + 0x84c8d4dfd2c63f3b,0x29ecd9f40041e073, + 0xa5fb0a17c777cf09,0xf468107100525890, + 0xcf79cc9db955c2cc,0x7182148d4066eeb4, + 0x81ac1fe293d599bf,0xc6f14cd848405530, + 0xa21727db38cb002f,0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b,0xa6d90811f0e4851c, + 0xfd442e4688bd304a,0x908f4a166d1da663, + 0x9e4a9cec15763e2e,0x9a598e4e043287fe, + 0xc5dd44271ad3cdba,0x40eff1e1853f29fd, + 0xf7549530e188c128,0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9,0x82bb74f8301958ce, + 0xc13a148e3032d6e7,0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1,0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5,0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de,0x7415d448f6b6f0e7, + 0xebdf661791d60f56,0x111b495b3464ad21, + 0x936b9fcebb25c995,0xcab10dd900beec34, + 0xb84687c269ef3bfb,0x3d5d514f40eea742, + 0xe65829b3046b0afa,0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc,0x47f0e785eaba72ab, + 0xb3f4e093db73a093,0x59ed216765690f56, + 0xe0f218b8d25088b8,0x306869c13ec3532c, + 0x8c974f7383725573,0x1e414218c73a13fb, + 0xafbd2350644eeacf,0xe5d1929ef90898fa, + 0xdbac6c247d62a583,0xdf45f746b74abf39, + 0x894bc396ce5da772,0x6b8bba8c328eb783, + 0xab9eb47c81f5114f,0x66ea92f3f326564, + 0xd686619ba27255a2,0xc80a537b0efefebd, + 0x8613fd0145877585,0xbd06742ce95f5f36, + 0xa798fc4196e952e7,0x2c48113823b73704, + 0xd17f3b51fca3a7a0,0xf75a15862ca504c5, + 0x82ef85133de648c4,0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5,0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3,0x318df905079926a8, + 0xffbbcfe994e5c61f,0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3,0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8,0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b,0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d,0x6bea10ca65c084e, + 0xc31bfa0fe5698db8,0x486e494fcff30a62, + 0xf3e2f893dec3f126,0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7,0xf89629465a75e01c, + 0xbe89523386091465,0xf6bbb397f1135823, + 0xee2ba6c0678b597f,0x746aa07ded582e2c, + 0x94db483840b717ef,0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb,0x92f34d62616ce413, + 0xe896a0d7e51e1566,0x77b020baf9c81d17, + 0x915e2486ef32cd60,0xace1474dc1d122e, + 0xb5b5ada8aaff80b8,0xd819992132456ba, + 0xe3231912d5bf60e6,0x10e1fff697ed6c69, + 0x8df5efabc5979c8f,0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3,0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0,0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4,0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d,0x86c16c98d2c953c6, + 0xd89d64d57a607744,0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b,0x11471cd764ad4972, + 0xa93af6c6c79b5d2d,0xd598e40d3dd89bcf, + 0xd389b47879823479,0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb,0xcedf722a585139ba, + 0xa54394fe1eedb8fe,0xc2974eb4ee658828, + 0xce947a3da6a9273e,0x733d226229feea32, + 0x811ccc668829b887,0x806357d5a3f525f, + 0xa163ff802a3426a8,0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052,0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67,0xbbac2078d443ace2, + 0x9d9ba7832936edc0,0xd54b944b84aa4c0d, + 0xc5029163f384a931,0xa9e795e65d4df11, + 0xf64335bcf065d37d,0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e,0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39,0xe45ec2862f71e1d6, + 0xf07da27a82c37088,0x5d767327bb4e5a4c, + 0x964e858c91ba2655,0x3a6a07f8d510f86f, + 0xbbe226efb628afea,0x890489f70a55368b, + 0xeadab0aba3b2dbe5,0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f,0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb,0x9ce6ebb40173744, + 0xe55990879ddcaabd,0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6,0x9fa946824a12232d, + 0xb32df8e9f3546564,0x47939822dc96abf9, + 0xdff9772470297ebd,0x59787e2b93bc56f7, + 0x8bfbea76c619ef36,0x57eb4edb3c55b65a, + 0xaefae51477a06b03,0xede622920b6b23f1, + 0xdab99e59958885c4,0xe95fab368e45eced, + 0x88b402f7fd75539b,0x11dbcb0218ebb414, + 0xaae103b5fcd2a881,0xd652bdc29f26a119, + 0xd59944a37c0752a2,0x4be76d3346f0495f, + 0x857fcae62d8493a5,0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e,0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2,0x7e2000a41346a7a7, + 0x825ecc24c873782f,0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b,0x728900802f0f32fa, + 0xcbb41ef979346bca,0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc,0xe2f610c84987bfa8, + 0x9f24b832e6b0f436,0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143,0x91503d1c79720dbb, + 0xf8a95fcf88747d94,0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c,0xc986afbe3ee11aba, + 0xc24452da229b021b,0xfbe85badce996168, + 0xf2d56790ab41c2a2,0xfae27299423fb9c3, + 0x97c560ba6b0919a5,0xdccd879fc967d41a, + 0xbdb6b8e905cb600f,0x5400e987bbc1c920, + 0xed246723473e3813,0x290123e9aab23b68, + 0x9436c0760c86e30b,0xf9a0b6720aaf6521, + 0xb94470938fa89bce,0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2,0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9,0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828,0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232,0x25c6da63c38de1b0, + 0x8d590723948a535f,0x579c487e5a38ad0e, + 0xb0af48ec79ace837,0x2d835a9df0c6d851, + 0xdcdb1b2798182244,0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b,0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5,0xe272467e3d222f3f, + 0xd7adf884aa879177,0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea,0x98e947129fc2b4e9, + 0xa87fea27a539e9a5,0x3f2398d747b36224, + 0xd29fe4b18e88640e,0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89,0x1953cf68300424ac, + 0xa48ceaaab75a8e2b,0x5fa8c3423c052dd7, + 0xcdb02555653131b6,0x3792f412cb06794d, + 0x808e17555f3ebf11,0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6,0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b,0xf245825a5a445275, + 0xfb158592be068d2e,0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d,0x55464dd69685606b, + 0xc428d05aa4751e4c,0xaa97e14c3c26b886, + 0xf53304714d9265df,0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab,0xe546a8038efe4029, + 0xbf8fdb78849a5f96,0xde98520472bdd033, + 0xef73d256a5c0f77c,0x963e66858f6d4440, + 0x95a8637627989aad,0xdde7001379a44aa8, + 0xbb127c53b17ec159,0x5560c018580d5d52, + 0xe9d71b689dde71af,0xaab8f01e6e10b4a6, + 0x9226712162ab070d,0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1,0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05,0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3,0x77f3608e92adb242, + 0xb267ed1940f1c61c,0x55f038b237591ed3, + 0xdf01e85f912e37a3,0x6b6c46dec52f6688, + 0x8b61313bbabce2c6,0x2323ac4b3b3da015, + 0xae397d8aa96c1b77,0xabec975e0a0d081a, + 0xd9c7dced53c72255,0x96e7bd358c904a21, + 0x881cea14545c7575,0x7e50d64177da2e54, + 0xaa242499697392d2,0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787,0x955e4ec64b44e864, + 0x84ec3c97da624ab4,0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61,0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba,0x67de18eda5814af2, + 0x81ceb32c4b43fcf4,0x80eacf948770ced7, + 0xa2425ff75e14fc31,0xa1258379a94d028d, + 0xcad2f7f5359a3b3e,0x96ee45813a04330, + 0xfd87b5f28300ca0d,0x8bca9d6e188853fc, + 0x9e74d1b791e07e48,0x775ea264cf55347e, + 0xc612062576589dda,0x95364afe032a819e, + 0xf79687aed3eec551,0x3a83ddbd83f52205, + 0x9abe14cd44753b52,0xc4926a9672793543, + 0xc16d9a0095928a27,0x75b7053c0f178294, + 0xf1c90080baf72cb1,0x5324c68b12dd6339, + 0x971da05074da7bee,0xd3f6fc16ebca5e04, + 0xbce5086492111aea,0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5,0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07,0x3aff322e62439fd0, + 0xb877aa3236a4b449,0x9befeb9fad487c3, + 0xe69594bec44de15b,0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9,0xf9d37014bf60a11, + 0xb424dc35095cd80f,0x538484c19ef38c95, + 0xe12e13424bb40e13,0x2865a5f206b06fba, + 0x8cbccc096f5088cb,0xf93f87b7442e45d4, + 0xafebff0bcb24aafe,0xf78f69a51539d749, + 0xdbe6fecebdedd5be,0xb573440e5a884d1c, + 0x89705f4136b4a597,0x31680a88f8953031, + 0xabcc77118461cefc,0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc,0x3d32907604691b4d, + 0x8637bd05af6c69b5,0xa63f9a49c2c1b110, + 0xa7c5ac471b478423,0xfcf80dc33721d54, + 0xd1b71758e219652b,0xd3c36113404ea4a9, + 0x83126e978d4fdf3b,0x645a1cac083126ea, + 0xa3d70a3d70a3d70a,0x3d70a3d70a3d70a4, + 0xcccccccccccccccc,0xcccccccccccccccd, + 0x8000000000000000,0x0, + 0xa000000000000000,0x0, + 0xc800000000000000,0x0, + 0xfa00000000000000,0x0, + 0x9c40000000000000,0x0, + 0xc350000000000000,0x0, + 0xf424000000000000,0x0, + 0x9896800000000000,0x0, + 0xbebc200000000000,0x0, + 0xee6b280000000000,0x0, + 0x9502f90000000000,0x0, + 0xba43b74000000000,0x0, + 0xe8d4a51000000000,0x0, + 0x9184e72a00000000,0x0, + 0xb5e620f480000000,0x0, + 0xe35fa931a0000000,0x0, + 0x8e1bc9bf04000000,0x0, + 0xb1a2bc2ec5000000,0x0, + 0xde0b6b3a76400000,0x0, + 0x8ac7230489e80000,0x0, + 0xad78ebc5ac620000,0x0, + 0xd8d726b7177a8000,0x0, + 0x878678326eac9000,0x0, + 0xa968163f0a57b400,0x0, + 0xd3c21bcecceda100,0x0, + 0x84595161401484a0,0x0, + 0xa56fa5b99019a5c8,0x0, + 0xcecb8f27f4200f3a,0x0, + 0x813f3978f8940984,0x4000000000000000, + 0xa18f07d736b90be5,0x5000000000000000, + 0xc9f2c9cd04674ede,0xa400000000000000, + 0xfc6f7c4045812296,0x4d00000000000000, + 0x9dc5ada82b70b59d,0xf020000000000000, + 0xc5371912364ce305,0x6c28000000000000, + 0xf684df56c3e01bc6,0xc732000000000000, + 0x9a130b963a6c115c,0x3c7f400000000000, + 0xc097ce7bc90715b3,0x4b9f100000000000, + 0xf0bdc21abb48db20,0x1e86d40000000000, + 0x96769950b50d88f4,0x1314448000000000, + 0xbc143fa4e250eb31,0x17d955a000000000, + 0xeb194f8e1ae525fd,0x5dcfab0800000000, + 0x92efd1b8d0cf37be,0x5aa1cae500000000, + 0xb7abc627050305ad,0xf14a3d9e40000000, + 0xe596b7b0c643c719,0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f,0xe4820023a2000000, + 0xb35dbf821ae4f38b,0xdda2802c8a800000, + 0xe0352f62a19e306e,0xd50b2037ad200000, + 0x8c213d9da502de45,0x4526f422cc340000, + 0xaf298d050e4395d6,0x9670b12b7f410000, + 0xdaf3f04651d47b4c,0x3c0cdd765f114000, + 0x88d8762bf324cd0f,0xa5880a69fb6ac800, + 0xab0e93b6efee0053,0x8eea0d047a457a00, + 0xd5d238a4abe98068,0x72a4904598d6d880, + 0x85a36366eb71f041,0x47a6da2b7f864750, + 0xa70c3c40a64e6c51,0x999090b65f67d924, + 0xd0cf4b50cfe20765,0xfff4b4e3f741cf6d, + 0x82818f1281ed449f,0xbff8f10e7a8921a4, + 0xa321f2d7226895c7,0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39,0x9bf4f8a69f764490, + 0xfee50b7025c36a08,0x2f236d04753d5b4, + 0x9f4f2726179a2245,0x1d762422c946590, + 0xc722f0ef9d80aad6,0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b,0xd2e0898765a7deb2, + 0x9b934c3b330c8577,0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5,0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a,0x8bef464e3945ef7a, + 0x97edd871cfda3a56,0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec,0x3d52eeed1cbea317, + 0xed63a231d4c4fb27,0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8,0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436,0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44,0x60dbbca87196b616, + 0x90e40fbeea1d3a4a,0xbc8955e946fe31cd, + 0xb51d13aea4a488dd,0x6babab6398bdbe41, + 0xe264589a4dcdab14,0xc696963c7eed2dd1, + 0x8d7eb76070a08aec,0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8,0x3b25a55f43294bcb, + 0xdd15fe86affad912,0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab,0x6e3569326c784337, + 0xacb92ed9397bf996,0x49c2c37f07965404, + 0xd7e77a8f87daf7fb,0xdc33745ec97be906, + 0x86f0ac99b4e8dafd,0x69a028bb3ded71a3, + 0xa8acd7c0222311bc,0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b,0xf50a3fa490c30190, + 0x83c7088e1aab65db,0x792667c6da79e0fa, + 0xa4b8cab1a1563f52,0x577001b891185938, + 0xcde6fd5e09abcf26,0xed4c0226b55e6f86, + 0x80b05e5ac60b6178,0x544f8158315b05b4, + 0xa0dc75f1778e39d6,0x696361ae3db1c721, + 0xc913936dd571c84c,0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f,0x4ab48a04065c723, + 0x9d174b2dcec0e47b,0x62eb0d64283f9c76, + 0xc45d1df942711d9a,0x3ba5d0bd324f8394, + 0xf5746577930d6500,0xca8f44ec7ee36479, + 0x9968bf6abbe85f20,0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8,0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2,0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5,0xbba1f1d158724a12, + 0xbb445da9ca61281f,0x2a8a6e45ae8edc97, + 0xea1575143cf97226,0xf52d09d71a3293bd, + 0x924d692ca61be758,0x593c2626705f9c56, + 0xb6e0c377cfa2e12e,0x6f8b2fb00c77836c, + 0xe498f455c38b997a,0xb6dfb9c0f956447, + 0x8edf98b59a373fec,0x4724bd4189bd5eac, + 0xb2977ee300c50fe7,0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1,0x2f2967b66737e3ed, + 0x8b865b215899f46c,0xbd79e0d20082ee74, + 0xae67f1e9aec07187,0xecd8590680a3aa11, + 0xda01ee641a708de9,0xe80e6f4820cc9495, + 0x884134fe908658b2,0x3109058d147fdcdd, + 0xaa51823e34a7eede,0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96,0x6c9e18ac7007c91a, + 0x850fadc09923329e,0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45,0x84db8346b786151c, + 0xcfe87f7cef46ff16,0xe612641865679a63, + 0x81f14fae158c5f6e,0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749,0xe3be5e330f38f09d, + 0xcb090c8001ab551c,0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63,0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e,0x2867e7fddcdd9afa, + 0xc646d63501a1511d,0xb281e1fd541501b8, + 0xf7d88bc24209a565,0x1f225a7ca91a4226, + 0x9ae757596946075f,0x3375788de9b06958, + 0xc1a12d2fc3978937,0x52d6b1641c83ae, + 0xf209787bb47d6b84,0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332,0xf840b7ba963646e0, + 0xbd176620a501fbff,0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff,0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf,0xc66f336c36b10137, + 0xb8a8d9bbe123f017,0xb80b0047445d4184, + 0xe6d3102ad96cec1d,0xa60dc059157491e5, + 0x9043ea1ac7e41392,0x87c89837ad68db2f, + 0xb454e4a179dd1877,0x29babe4598c311fb, + 0xe16a1dc9d8545e94,0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d,0x1899e4a65f58660c, + 0xb01ae745b101e9e4,0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d,0x76707543f4fa1f73, + 0x899504ae72497eba,0x6a06494a791c53a8, + 0xabfa45da0edbde69,0x487db9d17636892, + 0xd6f8d7509292d603,0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2,0xb8a2392ba45a9b2, + 0xa7f26836f282b732,0x8e6cac7768d7141e, + 0xd1ef0244af2364ff,0x3207d795430cd926, + 0x8335616aed761f1f,0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7,0x5f16206c9c6209a6, + 0xcd036837130890a1,0x36dba887c37a8c0f, + 0x802221226be55a64,0xc2494954da2c9789, + 0xa02aa96b06deb0fd,0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d,0x6f92829494e5acc7, + 0xfa42a8b73abbf48c,0xcb772339ba1f17f9, + 0x9c69a97284b578d7,0xff2a760414536efb, + 0xc38413cf25e2d70d,0xfef5138519684aba, + 0xf46518c2ef5b8cd1,0x7eb258665fc25d69, + 0x98bf2f79d5993802,0xef2f773ffbd97a61, + 0xbeeefb584aff8603,0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784,0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2,0xdd945a747bf26183, + 0xba756174393d88df,0x94f971119aeef9e4, + 0xe912b9d1478ceb17,0x7a37cd5601aab85d, + 0x91abb422ccb812ee,0xac62e055c10ab33a, + 0xb616a12b7fe617aa,0x577b986b314d6009, + 0xe39c49765fdf9d94,0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d,0x14588f13be847307, + 0xb1d219647ae6b31c,0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3,0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee,0x25de7bb9480d5854, + 0xada72ccc20054ae9,0xaf561aa79a10ae6a, + 0xd910f7ff28069da4,0x1b2ba1518094da04, + 0x87aa9aff79042286,0x90fb44d2f05d0842, + 0xa99541bf57452b28,0x353a1607ac744a53, + 0xd3fa922f2d1675f2,0x42889b8997915ce8, + 0x847c9b5d7c2e09b7,0x69956135febada11, + 0xa59bc234db398c25,0x43fab9837e699095, + 0xcf02b2c21207ef2e,0x94f967e45e03f4bb, + 0x8161afb94b44f57d,0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc,0x6462d92a69731732, + 0xca28a291859bbf93,0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78,0x5cda735244c3d43e, + 0x9defbf01b061adab,0x3a0888136afa64a7, + 0xc56baec21c7a1916,0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b,0x8aad549e57273d45, + 0x9a3c2087a63f6399,0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f,0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f,0x656d44a2a11c51d5, + 0x969eb7c47859e743,0x9f644ae5a4b1b325, + 0xbc4665b596706114,0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959,0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8,0x9a7f12442d588f2, + 0xb7dcbf5354e9bece,0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81,0x8f1668c8a86da5fa, + 0x8fa475791a569d10,0xf96e017d694487bc, + 0xb38d92d760ec4455,0x37c981dcc395a9ac, + 0xe070f78d3927556a,0x85bbe253f47b1417, + 0x8c469ab843b89562,0x93956d7478ccec8e, + 0xaf58416654a6babb,0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a,0x6997b05fcc0319e, + 0x88fcf317f22241e2,0x441fece3bdf81f03, + 0xab3c2fddeeaad25a,0xd527e81cad7626c3, + 0xd60b3bd56a5586f1,0x8a71e223d8d3b074, + 0x85c7056562757456,0xf6872d5667844e49, + 0xa738c6bebb12d16c,0xb428f8ac016561db, + 0xd106f86e69d785c7,0xe13336d701beba52, + 0x82a45b450226b39c,0xecc0024661173473, + 0xa34d721642b06084,0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5,0x31ec038df7b441f4, + 0xff290242c83396ce,0x7e67047175a15271, + 0x9f79a169bd203e41,0xf0062c6e984d386, + 0xc75809c42c684dd1,0x52c07b78a3e60868, + 0xf92e0c3537826145,0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb,0x88a66076400bb691, + 0xc2abf989935ddbfe,0x6acff893d00ea435, + 0xf356f7ebf83552fe,0x583f6b8c4124d43, + 0x98165af37b2153de,0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6,0x744f18c0592e4c5c, + 0xeda2ee1c7064130c,0x1162def06f79df73, + 0x9485d4d1c63e8be7,0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1,0x6d953e2bd7173692, + 0xe8111c87c5c1ba99,0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0,0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8,0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a,0x2e44ae64840fd61d, + 0x8da471a9de737e24,0x5ceaecfed289e5d2, + 0xb10d8e1456105dad,0x7425a83e872c5f47, + 0xdd50f1996b947518,0xd12f124e28f77719, + 0x8a5296ffe33cc92f,0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b,0x636cc64d1001550b, + 0xd8210befd30efa5a,0x3c47f7e05401aa4e, + 0x8714a775e3e95c78,0x65acfaec34810a71, + 0xa8d9d1535ce3b396,0x7f1839a741a14d0d, + 0xd31045a8341ca07c,0x1ede48111209a050, + 0x83ea2b892091e44d,0x934aed0aab460432, + 0xa4e4b66b68b65d60,0xf81da84d5617853f, + 0xce1de40642e3f4b9,0x36251260ab9d668e, + 0x80d2ae83e9ce78f3,0xc1d72b7c6b426019, + 0xa1075a24e4421730,0xb24cf65b8612f81f, + 0xc94930ae1d529cfc,0xdee033f26797b627, + 0xfb9b7cd9a4a7443c,0x169840ef017da3b1, + 0x9d412e0806e88aa5,0x8e1f289560ee864e, + 0xc491798a08a2ad4e,0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2,0xae10af696774b1db, + 0x9991a6f3d6bf1765,0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f,0x17fd090a58d32af3, + 0xeff394dcff8a948e,0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9,0x4abdaf101564f98e, + 0xbb764c4ca7a4440f,0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513,0x84c86189216dc5ed, + 0x92746b9be2f8552c,0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77,0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515,0xfabaf3feaa5334a, + 0x8f05b1163ba6832d,0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8,0x743e20e9ef511012, + 0xdf78e4b2bd342cf6,0x914da9246b255416, + 0x8bab8eefb6409c1a,0x1ad089b6c2f7548e, + 0xae9672aba3d0c320,0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8,0xc9e5d72d90a2741e, + 0x8865899617fb1871,0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d,0xddbb901b98feeab7, + 0xd51ea6fa85785631,0x552a74227f3ea565, + 0x8533285c936b35de,0xd53a88958f87275f, + 0xa67ff273b8460356,0x8a892abaf368f137, + 0xd01fef10a657842c,0x2d2b7569b0432d85, + 0x8213f56a67f6b29b,0x9c3b29620e29fc73, + 0xa298f2c501f45f42,0x8349f3ba91b47b8f, + 0xcb3f2f7642717713,0x241c70a936219a73, + 0xfe0efb53d30dd4d7,0xed238cd383aa0110, + 0x9ec95d1463e8a506,0xf4363804324a40aa, + 0xc67bb4597ce2ce48,0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da,0xdd94b7868e94050a, + 0x9b10a4e5e9913128,0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72,0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf,0xbc633b39673c8cec, + 0x976e41088617ca01,0xd5be0503e085d813, + 0xbd49d14aa79dbc82,0x4b2d8644d8a74e18, + 0xec9c459d51852ba2,0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45,0xcabb90e5c942b503, + 0xb8da1662e7b00a17,0x3d6a751f3b936243, + 0xe7109bfba19c0c9d,0xcc512670a783ad4, + 0x906a617d450187e2,0x27fb2b80668b24c5, + 0xb484f9dc9641e9da,0xb1f9f660802dedf6, + 0xe1a63853bbd26451,0x5e7873f8a0396973, + 0x8d07e33455637eb2,0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f,0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7,0x7641a140cc7810fb, + 0x89b9b3e11b6329ba,0xa9e904c87fcb0a9d, + 0xac2820d9623bf429,0x546345fa9fbdcd44, + 0xd732290fbacaf133,0xa97c177947ad4095, + 0x867f59a9d4bed6c0,0x49ed8eabcccc485d, + 0xa81f301449ee8c70,0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c,0x73832eec6fff3111, + 0x83585d8fd9c25db7,0xc831fd53c5ff7eab, + 0xa42e74f3d032f525,0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f,0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85,0x7980d163cf5b81b3, + 0xa0555e361951c366,0xd7e105bcc332621f, + 0xc86ab5c39fa63440,0x8dd9472bf3fefaa7, + 0xfa856334878fc150,0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2,0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07,0xa862f80ec4700c8, + 0xf4a642e14c6262c8,0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd,0x8038d51cb897789c, + 0xbf21e44003acdd2c,0xe0470a63e6bd56c3, + 0xeeea5d5004981478,0x1858ccfce06cac74, + 0x95527a5202df0ccb,0xf37801e0c43ebc8, + 0xbaa718e68396cffd,0xd30560258f54e6ba, + 0xe950df20247c83fd,0x47c6b82ef32a2069, + 0x91d28b7416cdd27e,0x4cdc331d57fa5441, + 0xb6472e511c81471d,0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5,0x58180fddd97723a6, + 0x8e679c2f5e44ff8f,0x570f09eaa7ea7648,}; +using powers = powers_template<>; + +} + +#endif + +#ifndef FASTFLOAT_DECIMAL_TO_BINARY_H +#define FASTFLOAT_DECIMAL_TO_BINARY_H + +#include +#include +#include +#include +#include +#include +#include + +namespace fast_float { + +// This will compute or rather approximate w * 5**q and return a pair of 64-bit words approximating +// the result, with the "high" part corresponding to the most significant bits and the +// low part corresponding to the least significant bits. +// +template +fastfloat_really_inline +value128 compute_product_approximation(int64_t q, uint64_t w) { + const int index = 2 * int(q - powers::smallest_power_of_five); + // For small values of q, e.g., q in [0,27], the answer is always exact because + // The line value128 firstproduct = full_multiplication(w, power_of_five_128[index]); + // gives the exact answer. + value128 firstproduct = full_multiplication(w, powers::power_of_five_128[index]); + static_assert((bit_precision >= 0) && (bit_precision <= 64), " precision should be in (0,64]"); + constexpr uint64_t precision_mask = (bit_precision < 64) ? + (uint64_t(0xFFFFFFFFFFFFFFFF) >> bit_precision) + : uint64_t(0xFFFFFFFFFFFFFFFF); + if((firstproduct.high & precision_mask) == precision_mask) { // could further guard with (lower + w < lower) + // regarding the second product, we only need secondproduct.high, but our expectation is that the compiler will optimize this extra work away if needed. + value128 secondproduct = full_multiplication(w, powers::power_of_five_128[index + 1]); + firstproduct.low += secondproduct.high; + if(secondproduct.high > firstproduct.low) { + firstproduct.high++; + } + } + return firstproduct; +} + +namespace detail { +/** + * For q in (0,350), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * floor(p) + q + * where + * p = log(5**q)/log(2) = q * log(5)/log(2) + * + * For negative values of q in (-400,0), we have that + * f = (((152170 + 65536) * q ) >> 16); + * is equal to + * -ceil(p) + q + * where + * p = log(5**-q)/log(2) = -q * log(5)/log(2) + */ + fastfloat_really_inline int power(int q) noexcept { + return (((152170 + 65536) * q) >> 16) + 63; + } +} // namespace detail + + +// w * 10 ** q +// The returned value should be a valid ieee64 number that simply need to be packed. +// However, in some very rare cases, the computation will fail. In such cases, we +// return an adjusted_mantissa with a negative power of 2: the caller should recompute +// in such cases. +template +fastfloat_really_inline +adjusted_mantissa compute_float(int64_t q, uint64_t w) noexcept { + adjusted_mantissa answer; + if ((w == 0) || (q < binary::smallest_power_of_ten())) { + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + if (q > binary::largest_power_of_ten()) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + // At this point in time q is in [powers::smallest_power_of_five, powers::largest_power_of_five]. + + // We want the most significant bit of i to be 1. Shift if needed. + int lz = leading_zeroes(w); + w <<= lz; + + // The required precision is binary::mantissa_explicit_bits() + 3 because + // 1. We need the implicit bit + // 2. We need an extra bit for rounding purposes + // 3. We might lose a bit due to the "upperbit" routine (result too small, requiring a shift) + + value128 product = compute_product_approximation(q, w); + if(product.low == 0xFFFFFFFFFFFFFFFF) { // could guard it further + // In some very rare cases, this could happen, in which case we might need a more accurate + // computation that what we can provide cheaply. This is very, very unlikely. + // + const bool inside_safe_exponent = (q >= -27) && (q <= 55); // always good because 5**q <2**128 when q>=0, + // and otherwise, for q<0, we have 5**-q<2**64 and the 128-bit reciprocal allows for exact computation. + if(!inside_safe_exponent) { + answer.power2 = -1; // This (a negative value) indicates an error condition. + return answer; + } + } + // The "compute_product_approximation" function can be slightly slower than a branchless approach: + // value128 product = compute_product(q, w); + // but in practice, we can win big with the compute_product_approximation if its additional branch + // is easily predicted. Which is best is data specific. + int upperbit = int(product.high >> 63); + + answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); + + answer.power2 = int(detail::power(int(q)) + upperbit - lz - binary::minimum_exponent()); + if (answer.power2 <= 0) { // we have a subnormal? + // Here have that answer.power2 <= 0 so -answer.power2 >= 0 + if(-answer.power2 + 1 >= 64) { // if we have more than 64 bits below the minimum exponent, you have a zero for sure. + answer.power2 = 0; + answer.mantissa = 0; + // result should be zero + return answer; + } + // next line is safe because -answer.power2 + 1 < 64 + answer.mantissa >>= -answer.power2 + 1; + // Thankfully, we can't have both "round-to-even" and subnormals because + // "round-to-even" only occurs for powers close to 0. + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + // There is a weird scenario where we don't have a subnormal but just. + // Suppose we start with 2.2250738585072013e-308, we end up + // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal + // whereas 0x40000000000000 x 2^-1023-53 is normal. Now, we need to round + // up 0x3fffffffffffff x 2^-1023-53 and once we do, we are no longer + // subnormal, but we can only know this after rounding. + // So we only declare a subnormal if we are smaller than the threshold. + answer.power2 = (answer.mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) ? 0 : 1; + return answer; + } + + // usually, we round *up*, but if we fall right in between and and we have an + // even basis, we need to round down + // We are only concerned with the cases where 5**q fits in single 64-bit word. + if ((product.low <= 1) && (q >= binary::min_exponent_round_to_even()) && (q <= binary::max_exponent_round_to_even()) && + ((answer.mantissa & 3) == 1) ) { // we may fall between two floats! + // To be in-between two floats we need that in doing + // answer.mantissa = product.high >> (upperbit + 64 - binary::mantissa_explicit_bits() - 3); + // ... we dropped out only zeroes. But if this happened, then we can go back!!! + if((answer.mantissa << (upperbit + 64 - binary::mantissa_explicit_bits() - 3)) == product.high) { + answer.mantissa &= ~uint64_t(1); // flip it so that we do not round up + } + } + + answer.mantissa += (answer.mantissa & 1); // round up + answer.mantissa >>= 1; + if (answer.mantissa >= (uint64_t(2) << binary::mantissa_explicit_bits())) { + answer.mantissa = (uint64_t(1) << binary::mantissa_explicit_bits()); + answer.power2++; // undo previous addition + } + + answer.mantissa &= ~(uint64_t(1) << binary::mantissa_explicit_bits()); + if (answer.power2 >= binary::infinite_power()) { // infinity + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + } + return answer; +} + + +} // namespace fast_float + +#endif + + +#ifndef FASTFLOAT_ASCII_NUMBER_H +#define FASTFLOAT_ASCII_NUMBER_H + +#include +#include +#include +#include + + +namespace fast_float { + +// Next function can be micro-optimized, but compilers are entirely +// able to optimize it well. +fastfloat_really_inline bool is_integer(char c) noexcept { return c >= '0' && c <= '9'; } + +fastfloat_really_inline uint64_t byteswap(uint64_t val) { + return (val & 0xFF00000000000000) >> 56 + | (val & 0x00FF000000000000) >> 40 + | (val & 0x0000FF0000000000) >> 24 + | (val & 0x000000FF00000000) >> 8 + | (val & 0x00000000FF000000) << 8 + | (val & 0x0000000000FF0000) << 24 + | (val & 0x000000000000FF00) << 40 + | (val & 0x00000000000000FF) << 56; +} + +fastfloat_really_inline uint64_t read_u64(const char *chars) { + uint64_t val; + ::memcpy(&val, chars, sizeof(uint64_t)); +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + return val; +} + +fastfloat_really_inline void write_u64(uint8_t *chars, uint64_t val) { +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + // Need to read as-if the number was in little-endian order. + val = byteswap(val); +#endif + ::memcpy(chars, &val, sizeof(uint64_t)); +} + +// credit @aqrit +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return uint32_t(val); +} + +fastfloat_really_inline uint32_t parse_eight_digits_unrolled(const char *chars) noexcept { + return parse_eight_digits_unrolled(read_u64(chars)); +} + +// credit @aqrit +fastfloat_really_inline bool is_made_of_eight_digits_fast(uint64_t val) noexcept { + return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) & + 0x8080808080808080)); +} + +fastfloat_really_inline bool is_made_of_eight_digits_fast(const char *chars) noexcept { + return is_made_of_eight_digits_fast(read_u64(chars)); +} + +struct parsed_number_string { + int64_t exponent; + uint64_t mantissa; + const char *lastmatch; + bool negative; + bool valid; + bool too_many_digits; +}; + + +// Assuming that you use no more than 19 digits, this will +// parse an ASCII string. +fastfloat_really_inline +parsed_number_string parse_number_string(const char *p, const char *pend, parse_options options) noexcept { + const chars_format fmt = options.format; + const char decimal_point = options.decimal_point; + + parsed_number_string answer; + answer.valid = false; + answer.too_many_digits = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + if (p == pend) { + return answer; + } + if (!is_integer(*p) && (*p != decimal_point)) { // a sign must be followed by an integer or the dot + return answer; + } + } + const char *const start_digits = p; + + uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad) + + while ((p != pend) && is_integer(*p)) { + // a multiplication by 10 is cheaper than an arbitrary integer + // multiplication + i = 10 * i + + uint64_t(*p - '0'); // might overflow, we will handle the overflow later + ++p; + } + const char *const end_of_integer_part = p; + int64_t digit_count = int64_t(end_of_integer_part - start_digits); + int64_t exponent = 0; + if ((p != pend) && (*p == decimal_point)) { + ++p; + // Fast approach only tested under little endian systems + if ((p + 8 <= pend) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + if ((p + 8 <= pend) && is_made_of_eight_digits_fast(p)) { + i = i * 100000000 + parse_eight_digits_unrolled(p); // in rare cases, this will overflow, but that's ok + p += 8; + } + } + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + ++p; + i = i * 10 + digit; // in rare cases, this will overflow, but that's ok + } + exponent = end_of_integer_part + 1 - p; + digit_count -= exponent; + } + // we must have encountered at least one integer! + if (digit_count == 0) { + return answer; + } + int64_t exp_number = 0; // explicit exponential part + if ((fmt & chars_format::scientific) && (p != pend) && (('e' == *p) || ('E' == *p))) { + const char * location_of_e = p; + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + if ((p == pend) || !is_integer(*p)) { + if(!(fmt & chars_format::fixed)) { + // We are in error. + return answer; + } + // Otherwise, we will be ignoring the 'e'. + p = location_of_e; + } else { + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + if(neg_exp) { exp_number = - exp_number; } + exponent += exp_number; + } + } else { + // If it scientific and not fixed, we have to bail out. + if((fmt & chars_format::scientific) && !(fmt & chars_format::fixed)) { return answer; } + } + answer.lastmatch = p; + answer.valid = true; + + // If we frequently had to deal with long strings of digits, + // we could extend our code by using a 128-bit integer instead + // of a 64-bit integer. However, this is uncommon. + // + // We can deal with up to 19 digits. + if (digit_count > 19) { // this is uncommon + // It is possible that the integer had an overflow. + // We have to handle the case where we have 0.0000somenumber. + // We need to be mindful of the case where we only have zeroes... + // E.g., 0.000000000...000. + const char *start = start_digits; + while ((start != pend) && (*start == '0' || *start == decimal_point)) { + if(*start == '0') { digit_count --; } + start++; + } + if (digit_count > 19) { + answer.too_many_digits = true; + // Let us start again, this time, avoiding overflows. + i = 0; + p = start_digits; + const uint64_t minimal_nineteen_digit_integer{1000000000000000000}; + while((i < minimal_nineteen_digit_integer) && (p != pend) && is_integer(*p)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + if (i >= minimal_nineteen_digit_integer) { // We have a big integers + exponent = end_of_integer_part - p + exp_number; + } else { // We have a value with a fractional component. + p++; // skip the dot + const char *first_after_period = p; + while((i < minimal_nineteen_digit_integer) && (p != pend) && is_integer(*p)) { + i = i * 10 + uint64_t(*p - '0'); + ++p; + } + exponent = first_after_period - p + exp_number; + } + // We have now corrected both exponent and i, to a truncated value + } + } + answer.exponent = exponent; + answer.mantissa = i; + return answer; +} + + +// This should always succeed since it follows a call to parse_number_string +// This function could be optimized. In particular, we could stop after 19 digits +// and try to bail out. Furthermore, we should be able to recover the computed +// exponent from the pass in parse_number_string. +fastfloat_really_inline decimal parse_decimal(const char *p, const char *pend, parse_options options) noexcept { + const char decimal_point = options.decimal_point; + + decimal answer; + answer.num_digits = 0; + answer.decimal_point = 0; + answer.truncated = false; + answer.negative = (*p == '-'); + if (*p == '-') { // C++17 20.19.3.(7.1) explicitly forbids '+' sign here + ++p; + } + // skip leading zeroes + while ((p != pend) && (*p == '0')) { + ++p; + } + while ((p != pend) && is_integer(*p)) { + if (answer.num_digits < max_digits) { + answer.digits[answer.num_digits] = uint8_t(*p - '0'); + } + answer.num_digits++; + ++p; + } + if ((p != pend) && (*p == decimal_point)) { + ++p; + const char *first_after_period = p; + // if we have not yet encountered a zero, we have to skip it as well + if(answer.num_digits == 0) { + // skip zeros + while ((p != pend) && (*p == '0')) { + ++p; + } + } + // We expect that this loop will often take the bulk of the running time + // because when a value has lots of digits, these digits often + while ((p + 8 <= pend) && (answer.num_digits + 8 < max_digits)) { + uint64_t val = read_u64(p); + if(! is_made_of_eight_digits_fast(val)) { break; } + // We have eight digits, process them in one go! + val -= 0x3030303030303030; + write_u64(answer.digits + answer.num_digits, val); + answer.num_digits += 8; + p += 8; + } + while ((p != pend) && is_integer(*p)) { + if (answer.num_digits < max_digits) { + answer.digits[answer.num_digits] = uint8_t(*p - '0'); + } + answer.num_digits++; + ++p; + } + answer.decimal_point = int32_t(first_after_period - p); + } + // We want num_digits to be the number of significant digits, excluding + // leading *and* trailing zeros! Otherwise the truncated flag later is + // going to be misleading. + if(answer.num_digits > 0) { + // We potentially need the answer.num_digits > 0 guard because we + // prune leading zeros. So with answer.num_digits > 0, we know that + // we have at least one non-zero digit. + const char *preverse = p - 1; + int32_t trailing_zeros = 0; + while ((*preverse == '0') || (*preverse == decimal_point)) { + if(*preverse == '0') { trailing_zeros++; }; + --preverse; + } + answer.decimal_point += int32_t(answer.num_digits); + answer.num_digits -= uint32_t(trailing_zeros); + } + if(answer.num_digits > max_digits) { + answer.truncated = true; + answer.num_digits = max_digits; + } + if ((p != pend) && (('e' == *p) || ('E' == *p))) { + ++p; + bool neg_exp = false; + if ((p != pend) && ('-' == *p)) { + neg_exp = true; + ++p; + } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1) + ++p; + } + int32_t exp_number = 0; // exponential part + while ((p != pend) && is_integer(*p)) { + uint8_t digit = uint8_t(*p - '0'); + if (exp_number < 0x10000) { + exp_number = 10 * exp_number + digit; + } + ++p; + } + answer.decimal_point += (neg_exp ? -exp_number : exp_number); + } + // In very rare cases, we may have fewer than 19 digits, we want to be able to reliably + // assume that all digits up to max_digit_without_overflow have been initialized. + for(uint32_t i = answer.num_digits; i < max_digit_without_overflow; i++) { answer.digits[i] = 0; } + + return answer; +} +} // namespace fast_float + +#endif + + +#ifndef FASTFLOAT_GENERIC_DECIMAL_TO_BINARY_H +#define FASTFLOAT_GENERIC_DECIMAL_TO_BINARY_H + +/** + * This code is meant to handle the case where we have more than 19 digits. + * + * It is based on work by Nigel Tao (at https://github.com/google/wuffs/) + * who credits Ken Thompson for the design (via a reference to the Go source + * code). + * + * Rob Pike suggested that this algorithm be called "Simple Decimal Conversion". + * + * It is probably not very fast but it is a fallback that should almost never + * be used in real life. Though it is not fast, it is "easily" understood and debugged. + **/ +#include + +namespace fast_float { + +namespace detail { + +// remove all final zeroes +inline void trim(decimal &h) { + while ((h.num_digits > 0) && (h.digits[h.num_digits - 1] == 0)) { + h.num_digits--; + } +} + + + +inline uint32_t number_of_digits_decimal_left_shift(const decimal &h, uint32_t shift) { + shift &= 63; + const static uint16_t number_of_digits_decimal_left_shift_table[65] = { + 0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, + 0x181D, 0x2024, 0x202B, 0x2033, 0x203C, 0x2846, 0x2850, 0x285B, 0x3067, + 0x3073, 0x3080, 0x388E, 0x389C, 0x38AB, 0x38BB, 0x40CC, 0x40DD, 0x40EF, + 0x4902, 0x4915, 0x4929, 0x513E, 0x5153, 0x5169, 0x5180, 0x5998, 0x59B0, + 0x59C9, 0x61E3, 0x61FD, 0x6218, 0x6A34, 0x6A50, 0x6A6D, 0x6A8B, 0x72AA, + 0x72C9, 0x72E9, 0x7B0A, 0x7B2B, 0x7B4D, 0x8370, 0x8393, 0x83B7, 0x83DC, + 0x8C02, 0x8C28, 0x8C4F, 0x9477, 0x949F, 0x94C8, 0x9CF2, 0x051C, 0x051C, + 0x051C, 0x051C, + }; + uint32_t x_a = number_of_digits_decimal_left_shift_table[shift]; + uint32_t x_b = number_of_digits_decimal_left_shift_table[shift + 1]; + uint32_t num_new_digits = x_a >> 11; + uint32_t pow5_a = 0x7FF & x_a; + uint32_t pow5_b = 0x7FF & x_b; + const static uint8_t + number_of_digits_decimal_left_shift_table_powers_of_5[0x051C] = { + 5, 2, 5, 1, 2, 5, 6, 2, 5, 3, 1, 2, 5, 1, 5, 6, 2, 5, 7, 8, 1, 2, 5, 3, + 9, 0, 6, 2, 5, 1, 9, 5, 3, 1, 2, 5, 9, 7, 6, 5, 6, 2, 5, 4, 8, 8, 2, 8, + 1, 2, 5, 2, 4, 4, 1, 4, 0, 6, 2, 5, 1, 2, 2, 0, 7, 0, 3, 1, 2, 5, 6, 1, + 0, 3, 5, 1, 5, 6, 2, 5, 3, 0, 5, 1, 7, 5, 7, 8, 1, 2, 5, 1, 5, 2, 5, 8, + 7, 8, 9, 0, 6, 2, 5, 7, 6, 2, 9, 3, 9, 4, 5, 3, 1, 2, 5, 3, 8, 1, 4, 6, + 9, 7, 2, 6, 5, 6, 2, 5, 1, 9, 0, 7, 3, 4, 8, 6, 3, 2, 8, 1, 2, 5, 9, 5, + 3, 6, 7, 4, 3, 1, 6, 4, 0, 6, 2, 5, 4, 7, 6, 8, 3, 7, 1, 5, 8, 2, 0, 3, + 1, 2, 5, 2, 3, 8, 4, 1, 8, 5, 7, 9, 1, 0, 1, 5, 6, 2, 5, 1, 1, 9, 2, 0, + 9, 2, 8, 9, 5, 5, 0, 7, 8, 1, 2, 5, 5, 9, 6, 0, 4, 6, 4, 4, 7, 7, 5, 3, + 9, 0, 6, 2, 5, 2, 9, 8, 0, 2, 3, 2, 2, 3, 8, 7, 6, 9, 5, 3, 1, 2, 5, 1, + 4, 9, 0, 1, 1, 6, 1, 1, 9, 3, 8, 4, 7, 6, 5, 6, 2, 5, 7, 4, 5, 0, 5, 8, + 0, 5, 9, 6, 9, 2, 3, 8, 2, 8, 1, 2, 5, 3, 7, 2, 5, 2, 9, 0, 2, 9, 8, 4, + 6, 1, 9, 1, 4, 0, 6, 2, 5, 1, 8, 6, 2, 6, 4, 5, 1, 4, 9, 2, 3, 0, 9, 5, + 7, 0, 3, 1, 2, 5, 9, 3, 1, 3, 2, 2, 5, 7, 4, 6, 1, 5, 4, 7, 8, 5, 1, 5, + 6, 2, 5, 4, 6, 5, 6, 6, 1, 2, 8, 7, 3, 0, 7, 7, 3, 9, 2, 5, 7, 8, 1, 2, + 5, 2, 3, 2, 8, 3, 0, 6, 4, 3, 6, 5, 3, 8, 6, 9, 6, 2, 8, 9, 0, 6, 2, 5, + 1, 1, 6, 4, 1, 5, 3, 2, 1, 8, 2, 6, 9, 3, 4, 8, 1, 4, 4, 5, 3, 1, 2, 5, + 5, 8, 2, 0, 7, 6, 6, 0, 9, 1, 3, 4, 6, 7, 4, 0, 7, 2, 2, 6, 5, 6, 2, 5, + 2, 9, 1, 0, 3, 8, 3, 0, 4, 5, 6, 7, 3, 3, 7, 0, 3, 6, 1, 3, 2, 8, 1, 2, + 5, 1, 4, 5, 5, 1, 9, 1, 5, 2, 2, 8, 3, 6, 6, 8, 5, 1, 8, 0, 6, 6, 4, 0, + 6, 2, 5, 7, 2, 7, 5, 9, 5, 7, 6, 1, 4, 1, 8, 3, 4, 2, 5, 9, 0, 3, 3, 2, + 0, 3, 1, 2, 5, 3, 6, 3, 7, 9, 7, 8, 8, 0, 7, 0, 9, 1, 7, 1, 2, 9, 5, 1, + 6, 6, 0, 1, 5, 6, 2, 5, 1, 8, 1, 8, 9, 8, 9, 4, 0, 3, 5, 4, 5, 8, 5, 6, + 4, 7, 5, 8, 3, 0, 0, 7, 8, 1, 2, 5, 9, 0, 9, 4, 9, 4, 7, 0, 1, 7, 7, 2, + 9, 2, 8, 2, 3, 7, 9, 1, 5, 0, 3, 9, 0, 6, 2, 5, 4, 5, 4, 7, 4, 7, 3, 5, + 0, 8, 8, 6, 4, 6, 4, 1, 1, 8, 9, 5, 7, 5, 1, 9, 5, 3, 1, 2, 5, 2, 2, 7, + 3, 7, 3, 6, 7, 5, 4, 4, 3, 2, 3, 2, 0, 5, 9, 4, 7, 8, 7, 5, 9, 7, 6, 5, + 6, 2, 5, 1, 1, 3, 6, 8, 6, 8, 3, 7, 7, 2, 1, 6, 1, 6, 0, 2, 9, 7, 3, 9, + 3, 7, 9, 8, 8, 2, 8, 1, 2, 5, 5, 6, 8, 4, 3, 4, 1, 8, 8, 6, 0, 8, 0, 8, + 0, 1, 4, 8, 6, 9, 6, 8, 9, 9, 4, 1, 4, 0, 6, 2, 5, 2, 8, 4, 2, 1, 7, 0, + 9, 4, 3, 0, 4, 0, 4, 0, 0, 7, 4, 3, 4, 8, 4, 4, 9, 7, 0, 7, 0, 3, 1, 2, + 5, 1, 4, 2, 1, 0, 8, 5, 4, 7, 1, 5, 2, 0, 2, 0, 0, 3, 7, 1, 7, 4, 2, 2, + 4, 8, 5, 3, 5, 1, 5, 6, 2, 5, 7, 1, 0, 5, 4, 2, 7, 3, 5, 7, 6, 0, 1, 0, + 0, 1, 8, 5, 8, 7, 1, 1, 2, 4, 2, 6, 7, 5, 7, 8, 1, 2, 5, 3, 5, 5, 2, 7, + 1, 3, 6, 7, 8, 8, 0, 0, 5, 0, 0, 9, 2, 9, 3, 5, 5, 6, 2, 1, 3, 3, 7, 8, + 9, 0, 6, 2, 5, 1, 7, 7, 6, 3, 5, 6, 8, 3, 9, 4, 0, 0, 2, 5, 0, 4, 6, 4, + 6, 7, 7, 8, 1, 0, 6, 6, 8, 9, 4, 5, 3, 1, 2, 5, 8, 8, 8, 1, 7, 8, 4, 1, + 9, 7, 0, 0, 1, 2, 5, 2, 3, 2, 3, 3, 8, 9, 0, 5, 3, 3, 4, 4, 7, 2, 6, 5, + 6, 2, 5, 4, 4, 4, 0, 8, 9, 2, 0, 9, 8, 5, 0, 0, 6, 2, 6, 1, 6, 1, 6, 9, + 4, 5, 2, 6, 6, 7, 2, 3, 6, 3, 2, 8, 1, 2, 5, 2, 2, 2, 0, 4, 4, 6, 0, 4, + 9, 2, 5, 0, 3, 1, 3, 0, 8, 0, 8, 4, 7, 2, 6, 3, 3, 3, 6, 1, 8, 1, 6, 4, + 0, 6, 2, 5, 1, 1, 1, 0, 2, 2, 3, 0, 2, 4, 6, 2, 5, 1, 5, 6, 5, 4, 0, 4, + 2, 3, 6, 3, 1, 6, 6, 8, 0, 9, 0, 8, 2, 0, 3, 1, 2, 5, 5, 5, 5, 1, 1, 1, + 5, 1, 2, 3, 1, 2, 5, 7, 8, 2, 7, 0, 2, 1, 1, 8, 1, 5, 8, 3, 4, 0, 4, 5, + 4, 1, 0, 1, 5, 6, 2, 5, 2, 7, 7, 5, 5, 5, 7, 5, 6, 1, 5, 6, 2, 8, 9, 1, + 3, 5, 1, 0, 5, 9, 0, 7, 9, 1, 7, 0, 2, 2, 7, 0, 5, 0, 7, 8, 1, 2, 5, 1, + 3, 8, 7, 7, 7, 8, 7, 8, 0, 7, 8, 1, 4, 4, 5, 6, 7, 5, 5, 2, 9, 5, 3, 9, + 5, 8, 5, 1, 1, 3, 5, 2, 5, 3, 9, 0, 6, 2, 5, 6, 9, 3, 8, 8, 9, 3, 9, 0, + 3, 9, 0, 7, 2, 2, 8, 3, 7, 7, 6, 4, 7, 6, 9, 7, 9, 2, 5, 5, 6, 7, 6, 2, + 6, 9, 5, 3, 1, 2, 5, 3, 4, 6, 9, 4, 4, 6, 9, 5, 1, 9, 5, 3, 6, 1, 4, 1, + 8, 8, 8, 2, 3, 8, 4, 8, 9, 6, 2, 7, 8, 3, 8, 1, 3, 4, 7, 6, 5, 6, 2, 5, + 1, 7, 3, 4, 7, 2, 3, 4, 7, 5, 9, 7, 6, 8, 0, 7, 0, 9, 4, 4, 1, 1, 9, 2, + 4, 4, 8, 1, 3, 9, 1, 9, 0, 6, 7, 3, 8, 2, 8, 1, 2, 5, 8, 6, 7, 3, 6, 1, + 7, 3, 7, 9, 8, 8, 4, 0, 3, 5, 4, 7, 2, 0, 5, 9, 6, 2, 2, 4, 0, 6, 9, 5, + 9, 5, 3, 3, 6, 9, 1, 4, 0, 6, 2, 5, + }; + const uint8_t *pow5 = + &number_of_digits_decimal_left_shift_table_powers_of_5[pow5_a]; + uint32_t i = 0; + uint32_t n = pow5_b - pow5_a; + for (; i < n; i++) { + if (i >= h.num_digits) { + return num_new_digits - 1; + } else if (h.digits[i] == pow5[i]) { + continue; + } else if (h.digits[i] < pow5[i]) { + return num_new_digits - 1; + } else { + return num_new_digits; + } + } + return num_new_digits; +} + +inline uint64_t round(decimal &h) { + if ((h.num_digits == 0) || (h.decimal_point < 0)) { + return 0; + } else if (h.decimal_point > 18) { + return UINT64_MAX; + } + // at this point, we know that h.decimal_point >= 0 + uint32_t dp = uint32_t(h.decimal_point); + uint64_t n = 0; + for (uint32_t i = 0; i < dp; i++) { + n = (10 * n) + ((i < h.num_digits) ? h.digits[i] : 0); + } + bool round_up = false; + if (dp < h.num_digits) { + round_up = h.digits[dp] >= 5; // normally, we round up + // but we may need to round to even! + if ((h.digits[dp] == 5) && (dp + 1 == h.num_digits)) { + round_up = h.truncated || ((dp > 0) && (1 & h.digits[dp - 1])); + } + } + if (round_up) { + n++; + } + return n; +} + +// computes h * 2^-shift +inline void decimal_left_shift(decimal &h, uint32_t shift) { + if (h.num_digits == 0) { + return; + } + uint32_t num_new_digits = number_of_digits_decimal_left_shift(h, shift); + int32_t read_index = int32_t(h.num_digits - 1); + uint32_t write_index = h.num_digits - 1 + num_new_digits; + uint64_t n = 0; + + while (read_index >= 0) { + n += uint64_t(h.digits[read_index]) << shift; + uint64_t quotient = n / 10; + uint64_t remainder = n - (10 * quotient); + if (write_index < max_digits) { + h.digits[write_index] = uint8_t(remainder); + } else if (remainder > 0) { + h.truncated = true; + } + n = quotient; + write_index--; + read_index--; + } + while (n > 0) { + uint64_t quotient = n / 10; + uint64_t remainder = n - (10 * quotient); + if (write_index < max_digits) { + h.digits[write_index] = uint8_t(remainder); + } else if (remainder > 0) { + h.truncated = true; + } + n = quotient; + write_index--; + } + h.num_digits += num_new_digits; + if (h.num_digits > max_digits) { + h.num_digits = max_digits; + } + h.decimal_point += int32_t(num_new_digits); + trim(h); +} + +// computes h * 2^shift +inline void decimal_right_shift(decimal &h, uint32_t shift) { + uint32_t read_index = 0; + uint32_t write_index = 0; + + uint64_t n = 0; + + while ((n >> shift) == 0) { + if (read_index < h.num_digits) { + n = (10 * n) + h.digits[read_index++]; + } else if (n == 0) { + return; + } else { + while ((n >> shift) == 0) { + n = 10 * n; + read_index++; + } + break; + } + } + h.decimal_point -= int32_t(read_index - 1); + if (h.decimal_point < -decimal_point_range) { // it is zero + h.num_digits = 0; + h.decimal_point = 0; + h.negative = false; + h.truncated = false; + return; + } + uint64_t mask = (uint64_t(1) << shift) - 1; + while (read_index < h.num_digits) { + uint8_t new_digit = uint8_t(n >> shift); + n = (10 * (n & mask)) + h.digits[read_index++]; + h.digits[write_index++] = new_digit; + } + while (n > 0) { + uint8_t new_digit = uint8_t(n >> shift); + n = 10 * (n & mask); + if (write_index < max_digits) { + h.digits[write_index++] = new_digit; + } else if (new_digit > 0) { + h.truncated = true; + } + } + h.num_digits = write_index; + trim(h); +} + +} // namespace detail + +template +adjusted_mantissa compute_float(decimal &d) { + adjusted_mantissa answer; + if (d.num_digits == 0) { + // should be zero + answer.power2 = 0; + answer.mantissa = 0; + return answer; + } + // At this point, going further, we can assume that d.num_digits > 0. + // + // We want to guard against excessive decimal point values because + // they can result in long running times. Indeed, we do + // shifts by at most 60 bits. We have that log(10**400)/log(2**60) ~= 22 + // which is fine, but log(10**299995)/log(2**60) ~= 16609 which is not + // fine (runs for a long time). + // + if(d.decimal_point < -324) { + // We have something smaller than 1e-324 which is always zero + // in binary64 and binary32. + // It should be zero. + answer.power2 = 0; + answer.mantissa = 0; + return answer; + } else if(d.decimal_point >= 310) { + // We have something at least as large as 0.1e310 which is + // always infinite. + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + static const uint32_t max_shift = 60; + static const uint32_t num_powers = 19; + static const uint8_t decimal_powers[19] = { + 0, 3, 6, 9, 13, 16, 19, 23, 26, 29, // + 33, 36, 39, 43, 46, 49, 53, 56, 59, // + }; + int32_t exp2 = 0; + while (d.decimal_point > 0) { + uint32_t n = uint32_t(d.decimal_point); + uint32_t shift = (n < num_powers) ? decimal_powers[n] : max_shift; + detail::decimal_right_shift(d, shift); + if (d.decimal_point < -decimal_point_range) { + // should be zero + answer.power2 = 0; + answer.mantissa = 0; + return answer; + } + exp2 += int32_t(shift); + } + // We shift left toward [1/2 ... 1]. + while (d.decimal_point <= 0) { + uint32_t shift; + if (d.decimal_point == 0) { + if (d.digits[0] >= 5) { + break; + } + shift = (d.digits[0] < 2) ? 2 : 1; + } else { + uint32_t n = uint32_t(-d.decimal_point); + shift = (n < num_powers) ? decimal_powers[n] : max_shift; + } + detail::decimal_left_shift(d, shift); + if (d.decimal_point > decimal_point_range) { + // we want to get infinity: + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + exp2 -= int32_t(shift); + } + // We are now in the range [1/2 ... 1] but the binary format uses [1 ... 2]. + exp2--; + constexpr int32_t minimum_exponent = binary::minimum_exponent(); + while ((minimum_exponent + 1) > exp2) { + uint32_t n = uint32_t((minimum_exponent + 1) - exp2); + if (n > max_shift) { + n = max_shift; + } + detail::decimal_right_shift(d, n); + exp2 += int32_t(n); + } + if ((exp2 - minimum_exponent) >= binary::infinite_power()) { + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + + const int mantissa_size_in_bits = binary::mantissa_explicit_bits() + 1; + detail::decimal_left_shift(d, mantissa_size_in_bits); + + uint64_t mantissa = detail::round(d); + // It is possible that we have an overflow, in which case we need + // to shift back. + if(mantissa >= (uint64_t(1) << mantissa_size_in_bits)) { + detail::decimal_right_shift(d, 1); + exp2 += 1; + mantissa = detail::round(d); + if ((exp2 - minimum_exponent) >= binary::infinite_power()) { + answer.power2 = binary::infinite_power(); + answer.mantissa = 0; + return answer; + } + } + answer.power2 = exp2 - binary::minimum_exponent(); + if(mantissa < (uint64_t(1) << binary::mantissa_explicit_bits())) { answer.power2--; } + answer.mantissa = mantissa & ((uint64_t(1) << binary::mantissa_explicit_bits()) - 1); + return answer; +} + +template +adjusted_mantissa parse_long_mantissa(const char *first, const char* last, parse_options options) { + decimal d = parse_decimal(first, last, options); + return compute_float(d); +} + +} // namespace fast_float +#endif + + +#ifndef FASTFLOAT_PARSE_NUMBER_H +#define FASTFLOAT_PARSE_NUMBER_H + +#include +#include +#include +#include +#include + +namespace fast_float { + + +namespace detail { +/** + * Special case +inf, -inf, nan, infinity, -infinity. + * The case comparisons could be made much faster given that we know that the + * strings a null-free and fixed. + **/ +template +from_chars_result parse_infnan(const char *first, const char *last, T &value) noexcept { + from_chars_result answer; + answer.ptr = first; + answer.ec = std::errc(); // be optimistic + bool minusSign = false; + if (*first == '-') { // assume first < last, so dereference without checks; C++17 20.19.3.(7.1) explicitly forbids '+' here + minusSign = true; + ++first; + } + if (last - first >= 3) { + if (fastfloat_strncasecmp(first, "nan", 3)) { + answer.ptr = (first += 3); + value = minusSign ? -std::numeric_limits::quiet_NaN() : std::numeric_limits::quiet_NaN(); + // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7, C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan). + if(first != last && *first == '(') { + for(const char* ptr = first + 1; ptr != last; ++ptr) { + if (*ptr == ')') { + answer.ptr = ptr + 1; // valid nan(n-char-seq-opt) + break; + } + else if(!(('a' <= *ptr && *ptr <= 'z') || ('A' <= *ptr && *ptr <= 'Z') || ('0' <= *ptr && *ptr <= '9') || *ptr == '_')) + break; // forbidden char, not nan(n-char-seq-opt) + } + } + return answer; + } + if (fastfloat_strncasecmp(first, "inf", 3)) { + if ((last - first >= 8) && fastfloat_strncasecmp(first + 3, "inity", 5)) { + answer.ptr = first + 8; + } else { + answer.ptr = first + 3; + } + value = minusSign ? -std::numeric_limits::infinity() : std::numeric_limits::infinity(); + return answer; + } + } + answer.ec = std::errc::invalid_argument; + return answer; +} + +template +fastfloat_really_inline void to_float(bool negative, adjusted_mantissa am, T &value) { + uint64_t word = am.mantissa; + word |= uint64_t(am.power2) << binary_format::mantissa_explicit_bits(); + word = negative + ? word | (uint64_t(1) << binary_format::sign_index()) : word; +#if FASTFLOAT_IS_BIG_ENDIAN == 1 + if (std::is_same::value) { + ::memcpy(&value, (char *)&word + 4, sizeof(T)); // extract value at offset 4-7 if float on big-endian + } else { + ::memcpy(&value, &word, sizeof(T)); + } +#else + // For little-endian systems: + ::memcpy(&value, &word, sizeof(T)); +#endif +} + +} // namespace detail + + + +template +from_chars_result from_chars(const char *first, const char *last, + T &value, chars_format fmt /*= chars_format::general*/) noexcept { + return from_chars_advanced(first, last, value, parse_options{fmt}); +} + +template +from_chars_result from_chars_advanced(const char *first, const char *last, + T &value, parse_options options) noexcept { + + static_assert (std::is_same::value || std::is_same::value, "only float and double are supported"); + + + from_chars_result answer; + if (first == last) { + answer.ec = std::errc::invalid_argument; + answer.ptr = first; + return answer; + } + parsed_number_string pns = parse_number_string(first, last, options); + if (!pns.valid) { + return detail::parse_infnan(first, last, value); + } + answer.ec = std::errc(); // be optimistic + answer.ptr = pns.lastmatch; + // Next is Clinger's fast path. + if (binary_format::min_exponent_fast_path() <= pns.exponent && pns.exponent <= binary_format::max_exponent_fast_path() && pns.mantissa <=binary_format::max_mantissa_fast_path() && !pns.too_many_digits) { + value = T(pns.mantissa); + if (pns.exponent < 0) { value = value / binary_format::exact_power_of_ten(-pns.exponent); } + else { value = value * binary_format::exact_power_of_ten(pns.exponent); } + if (pns.negative) { value = -value; } + return answer; + } + adjusted_mantissa am = compute_float>(pns.exponent, pns.mantissa); + if(pns.too_many_digits) { + if(am != compute_float>(pns.exponent, pns.mantissa + 1)) { + am.power2 = -1; // value is invalid. + } + } + // If we called compute_float>(pns.exponent, pns.mantissa) and we have an invalid power (am.power2 < 0), + // then we need to go the long way around again. This is very uncommon. + if(am.power2 < 0) { am = parse_long_mantissa>(first, last, options); } + detail::to_float(pns.negative, am, value); + return answer; +} + +} // namespace fast_float + +#endif + diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index 76aa36194e..217166f8c7 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -15,11 +15,6 @@ #include "Utils.hpp" // for next_highest_power_of_2() -extern "C" -{ -// Ray-Triangle Intersection Test Routines by Tomas Moller, May 2000 -#include -} // Definition of the ray intersection hit structure. #include @@ -231,6 +226,9 @@ namespace detail { const VectorType origin; const VectorType dir; const VectorType invdir; + + // epsilon for ray-triangle intersection, see intersect_triangle1() + const double eps; }; template @@ -283,44 +281,91 @@ namespace detail { return tmin < t1 && tmax > t0; } + // The following intersect_triangle() is derived from raytri.c routine intersect_triangle1() + // Ray-Triangle Intersection Test Routines + // Different optimizations of my and Ben Trumbore's + // code from journals of graphics tools (JGT) + // http://www.acm.org/jgt/ + // by Tomas Moller, May 2000 template - std::enable_if_t::value && std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { - return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), - const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), - &t, &u, &v); + std::enable_if_t::value&& std::is_same::value, bool> + intersect_triangle(const V &orig, const V &dir, const W &vert0, const W &vert1, const W &vert2, double &t, double &u, double &v, double eps) + { + // find vectors for two edges sharing vert0 + const V edge1 = vert1 - vert0; + const V edge2 = vert2 - vert0; + // begin calculating determinant - also used to calculate U parameter + const V pvec = dir.cross(edge2); + // if determinant is near zero, ray lies in plane of triangle + const double det = edge1.dot(pvec); + V qvec; + + if (det > eps) { + // calculate distance from vert0 to ray origin + V tvec = orig - vert0; + // calculate U parameter and test bounds + u = tvec.dot(pvec); + if (u < 0.0 || u > det) + return false; + // prepare to test V parameter + qvec = tvec.cross(edge1); + // calculate V parameter and test bounds + v = dir.dot(qvec); + if (v < 0.0 || u + v > det) + return false; + } else if (det < -eps) { + // calculate distance from vert0 to ray origin + V tvec = orig - vert0; + // calculate U parameter and test bounds + u = tvec.dot(pvec); + if (u > 0.0 || u < det) + return false; + // prepare to test V parameter + qvec = tvec.cross(edge1); + // calculate V parameter and test bounds + v = dir.dot(qvec); + if (v > 0.0 || u + v < det) + return false; + } else + // ray is parallel to the plane of the triangle + return false; + + double inv_det = 1.0 / det; + // calculate t, ray intersects triangle + t = edge2.dot(qvec) * inv_det; + u *= inv_det; + v *= inv_det; + return true; } template std::enable_if_t::value && !std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { - using Vector = Eigen::Matrix; - Vector w0 = v0.template cast(); - Vector w1 = v1.template cast(); - Vector w2 = v2.template cast(); - return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), - w0.data(), w1.data(), w2.data(), &t, &u, &v); + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) { + return intersect_triangle(origin, dir, v0.template cast(), v1.template cast(), v2.template cast(), t, u, v, eps); } template std::enable_if_t::value && std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { - using Vector = Eigen::Matrix; - Vector o = origin.template cast(); - Vector d = dir.template cast(); - return intersect_triangle1(o.data(), d.data(), const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), &t, &u, &v); + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) { + return intersect_triangle(origin.template cast(), dir.template cast(), v0, v1, v2, t, u, v, eps); } template std::enable_if_t::value && ! std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { - using Vector = Eigen::Matrix; - Vector o = origin.template cast(); - Vector d = dir.template cast(); - Vector w0 = v0.template cast(); - Vector w1 = v1.template cast(); - Vector w2 = v2.template cast(); - return intersect_triangle1(o.data(), d.data(), w0.data(), w1.data(), w2.data(), &t, &u, &v); + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) { + return intersect_triangle(origin.template cast(), dir.template cast(), v0.template cast(), v1.template cast(), v2.template cast(), t, u, v, eps); + } + + template + double intersect_triangle_epsilon(const Tree &tree) { + double eps = 0.000001; + if (! tree.empty()) { + const typename Tree::BoundingBox &bbox = tree.nodes().front().bbox; + double l = (bbox.max() - bbox.min()).cwiseMax(); + if (l > 0) + eps /= (l * l); + } + return eps; } template @@ -343,7 +388,7 @@ namespace detail { if (intersect_triangle( ray_intersector.origin, ray_intersector.dir, ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)], - t, u, v) + t, u, v, ray_intersector.eps) && t > 0.) { hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) }; return true; @@ -388,7 +433,7 @@ namespace detail { if (intersect_triangle( ray_intersector.origin, ray_intersector.dir, ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)], - t, u, v) + t, u, v, ray_intersector.eps) && t > 0.) { ray_intersector.hits.emplace_back(igl::Hit{ int(node.idx), -1, float(u), float(v), float(t) }); } @@ -623,12 +668,15 @@ inline bool intersect_ray_first_hit( // Direction of the ray. const VectorType &dir, // First intersection of the ray with the indexed triangle set. - igl::Hit &hit) + igl::Hit &hit, + // Epsilon for the ray-triangle intersection, it should be proportional to an average triangle edge length. + const double eps = 0.000001) { using Scalar = typename VectorType::Scalar; - auto ray_intersector = detail::RayIntersector { + auto ray_intersector = detail::RayIntersector { vertices, faces, tree, - origin, dir, VectorType(dir.cwiseInverse()) + origin, dir, VectorType(dir.cwiseInverse()), + eps }; return ! tree.empty() && detail::intersect_ray_recursive_first_hit( ray_intersector, size_t(0), std::numeric_limits::infinity(), hit); @@ -652,11 +700,14 @@ inline bool intersect_ray_all_hits( // Direction of the ray. const VectorType &dir, // All intersections of the ray with the indexed triangle set, sorted by parameter t. - std::vector &hits) + std::vector &hits, + // Epsilon for the ray-triangle intersection, it should be proportional to an average triangle edge length. + const double eps = 0.000001) { auto ray_intersector = detail::RayIntersectorHits { { vertices, faces, {tree}, - origin, dir, VectorType(dir.cwiseInverse()) } + origin, dir, VectorType(dir.cwiseInverse()), + eps } }; if (! tree.empty()) { ray_intersector.hits.reserve(8); diff --git a/src/libslic3r/BlacklistedLibraryCheck.cpp b/src/libslic3r/BlacklistedLibraryCheck.cpp index 3d2ee34827..76f675c707 100644 --- a/src/libslic3r/BlacklistedLibraryCheck.cpp +++ b/src/libslic3r/BlacklistedLibraryCheck.cpp @@ -12,7 +12,7 @@ namespace Slic3r { #ifdef WIN32 //only dll name with .dll suffix - currently case sensitive -const std::vector BlacklistedLibraryCheck::blacklist({ L"NahimicOSD.dll" }); +const std::vector BlacklistedLibraryCheck::blacklist({ L"NahimicOSD.dll", L"SS2OSD.dll" }); bool BlacklistedLibraryCheck::get_blacklisted(std::vector& names) { @@ -33,22 +33,20 @@ std::wstring BlacklistedLibraryCheck::get_blacklisted_string() bool BlacklistedLibraryCheck::perform_check() { - // Get a handle to the process. - HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, GetCurrentProcessId()); - if (NULL == hProcess) - return false; + // Get the pseudo-handle for the current process. + HANDLE hCurrentProcess = GetCurrentProcess(); // Get a list of all the modules in this process. HMODULE hMods[1024]; DWORD cbNeeded; - if (EnumProcessModulesEx(hProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL)) + if (EnumProcessModulesEx(hCurrentProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL)) { //printf("Total Dlls: %d\n", cbNeeded / sizeof(HMODULE)); for (unsigned int i = 0; i < cbNeeded / sizeof(HMODULE); ++ i) { wchar_t szModName[MAX_PATH]; // Get the full path to the module's file. - if (GetModuleFileNameExW(hProcess, hMods[i], szModName, MAX_PATH)) + if (GetModuleFileNameExW(hCurrentProcess, hMods[i], szModName, MAX_PATH)) { // Add to list if blacklisted if (BlacklistedLibraryCheck::is_blacklisted(szModName)) { @@ -61,7 +59,6 @@ bool BlacklistedLibraryCheck::perform_check() } } - CloseHandle(hProcess); //printf("\n"); return !m_found.empty(); } diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index f4455fdd55..db31975e35 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -134,10 +134,10 @@ static Polygons top_level_outer_brim_islands(const ConstPrintObjectPtrs &top_lev Polygons islands; for (const PrintObject *object : top_level_objects_with_brim) { //FIXME how about the brim type? - auto brim_offset = float(scale_(object->config().brim_offset.value)); + auto brim_separation = float(scale_(object->config().brim_separation.value)); Polygons islands_object; for (const ExPolygon &ex_poly : get_print_object_bottom_layer_expolygons(*object)) { - Polygons contour_offset = offset(ex_poly.contour, brim_offset); + Polygons contour_offset = offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare); for (Polygon &poly : contour_offset) poly.douglas_peucker(SCALED_RESOLUTION); @@ -166,7 +166,7 @@ static ExPolygons top_level_outer_brim_area(const Print &print for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) { const PrintObject *object = print.objects()[print_object_idx]; const BrimType brim_type = object->config().brim_type.value; - const float brim_offset = scale_(object->config().brim_offset.value); + const float brim_separation = scale_(object->config().brim_separation.value); const float brim_width = scale_(object->config().brim_width.value); const bool is_top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end(); @@ -174,16 +174,16 @@ static ExPolygons top_level_outer_brim_area(const Print &print ExPolygons no_brim_area_object; for (const ExPolygon &ex_poly : bottom_layers_expolygons[print_object_idx]) { if ((brim_type == BrimType::btOuterOnly || brim_type == BrimType::btOuterAndInner) && is_top_outer_brim) - append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_offset), offset(ex_poly.contour, brim_offset))); + append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare))); if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset)); + append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare)); if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly.holes)); + append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes)); if (brim_type != BrimType::btNoBrim) - append(no_brim_area_object, offset_ex(ExPolygon(ex_poly.contour), brim_offset)); + append(no_brim_area_object, offset_ex(ExPolygon(ex_poly.contour), brim_separation, ClipperLib::jtSquare)); no_brim_area_object.emplace_back(ex_poly.contour); } @@ -212,11 +212,11 @@ static ExPolygons inner_brim_area(const Print &print, ExPolygons no_brim_area; Polygons holes; for(size_t print_object_idx = 0; print_object_idx < print.objects().size(); ++print_object_idx) { - const PrintObject *object = print.objects()[print_object_idx]; - const BrimType brim_type = object->config().brim_type.value; - const float brim_offset = scale_(object->config().brim_offset.value); - const float brim_width = scale_(object->config().brim_width.value); - const bool top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end(); + const PrintObject *object = print.objects()[print_object_idx]; + const BrimType brim_type = object->config().brim_type.value; + const float brim_separation = scale_(object->config().brim_separation.value); + const float brim_width = scale_(object->config().brim_width.value); + const bool top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end(); ExPolygons brim_area_object; ExPolygons no_brim_area_object; @@ -226,21 +226,21 @@ static ExPolygons inner_brim_area(const Print &print, if (top_outer_brim) no_brim_area_object.emplace_back(ex_poly); else - append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_offset), offset(ex_poly.contour, brim_offset))); + append(brim_area_object, diff_ex(offset(ex_poly.contour, brim_width + brim_separation, ClipperLib::jtSquare), offset(ex_poly.contour, brim_separation, ClipperLib::jtSquare))); } if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btOuterAndInner) - append(brim_area_object, diff_ex(offset_ex(ex_poly.holes, -brim_offset), offset_ex(ex_poly.holes, -brim_width - brim_offset))); + append(brim_area_object, diff_ex(offset_ex(ex_poly.holes, -brim_separation, ClipperLib::jtSquare), offset_ex(ex_poly.holes, -brim_width - brim_separation, ClipperLib::jtSquare))); if (brim_type == BrimType::btInnerOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly.holes)); + append(no_brim_area_object, diff_ex(offset(ex_poly.contour, no_brim_offset, ClipperLib::jtSquare), ex_poly.holes)); if (brim_type == BrimType::btOuterOnly || brim_type == BrimType::btNoBrim) - append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset)); + append(no_brim_area_object, offset_ex(ex_poly.holes, -no_brim_offset, ClipperLib::jtSquare)); append(holes_object, ex_poly.holes); } - append(no_brim_area_object, offset_ex(bottom_layers_expolygons[print_object_idx], brim_offset)); + append(no_brim_area_object, offset_ex(bottom_layers_expolygons[print_object_idx], brim_separation, ClipperLib::jtSquare)); for (const PrintInstance &instance : object->instances()) { append_and_translate(brim_area, brim_area_object, instance); @@ -356,12 +356,12 @@ static void make_inner_brim(const Print &print, Flow flow = print.brim_flow(); ExPolygons islands_ex = inner_brim_area(print, top_level_objects_with_brim, bottom_layers_expolygons, float(flow.scaled_spacing())); Polygons loops; - islands_ex = offset_ex(islands_ex, -0.5f * float(flow.scaled_spacing()), jtSquare); + islands_ex = offset_ex(islands_ex, -0.5f * float(flow.scaled_spacing()), ClipperLib::jtSquare); for (size_t i = 0; !islands_ex.empty(); ++i) { for (ExPolygon &poly_ex : islands_ex) poly_ex.douglas_peucker(SCALED_RESOLUTION); polygons_append(loops, to_polygons(islands_ex)); - islands_ex = offset_ex(islands_ex, -float(flow.scaled_spacing()), jtSquare); + islands_ex = offset_ex(islands_ex, -float(flow.scaled_spacing()), ClipperLib::jtSquare); } loops = union_pt_chained_outside_in(loops); @@ -385,7 +385,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance size_t num_loops = size_t(floor(max_brim_width(print.objects()) / flow.spacing())); for (size_t i = 0; i < num_loops; ++i) { try_cancel(); - islands = offset(islands, float(flow.scaled_spacing()), jtSquare); + islands = offset(islands, float(flow.scaled_spacing()), ClipperLib::jtSquare); for (Polygon &poly : islands) poly.douglas_peucker(SCALED_RESOLUTION); polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index c8a3835dde..ab58a43aa0 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -696,10 +696,8 @@ ConfigSubstitutions ConfigBase::load_from_ini_string_commented(std::string &&dat for (size_t i = 0; i < data.size();) if (i == 0 || data[i] == '\n') { // Start of a line. - if (i != 0) { - // Consume LF. - assert(data[i] == '\n'); - // Don't keep empty lines. + if (data[i] == '\n') { + // Consume LF, don't keep empty lines. if (j > 0 && data[j - 1] != '\n') data[j ++] = data[i]; ++ i; diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index ed2d5d9c36..295c1413d4 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -49,6 +49,17 @@ const unsigned int VERSION_3MF = 1; const unsigned int VERSION_3MF_COMPATIBLE = 2; const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file +// Painting gizmos data version numbers +// 0 : 3MF files saved by older PrusaSlicer or the painting gizmo wasn't used. No version definition in them. +// 1 : Introduction of painting gizmos data versioning. No other changes in painting gizmos data. +const unsigned int FDM_SUPPORTS_PAINTING_VERSION = 1; +const unsigned int SEAM_PAINTING_VERSION = 1; +const unsigned int MM_PAINTING_VERSION = 1; + +const std::string SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION = "slic3rpe:FdmSupportsPaintingVersion"; +const std::string SLIC3RPE_SEAM_PAINTING_VERSION = "slic3rpe:SeamPaintingVersion"; +const std::string SLIC3RPE_MM_PAINTING_VERSION = "slic3rpe:MmPaintingVersion"; + const std::string MODEL_FOLDER = "3D/"; const std::string MODEL_EXTENSION = ".model"; const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA @@ -393,6 +404,10 @@ namespace Slic3r { unsigned int m_version; bool m_check_version; + unsigned int m_fdm_supports_painting_version = 0; + unsigned int m_seam_painting_version = 0; + unsigned int m_mm_painting_version = 0; + XML_Parser m_xml_parser; // Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state // after returning from XML_Parse() function, thus we keep the error state here. @@ -420,6 +435,7 @@ namespace Slic3r { ~_3MF_Importer(); bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version); + unsigned int version() const { return m_version; } private: void _destroy_xml_parser(); @@ -542,6 +558,9 @@ namespace Slic3r { bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version) { m_version = 0; + m_fdm_supports_painting_version = 0; + m_seam_painting_version = 0; + m_mm_painting_version = 0; m_check_version = check_version; m_model = &model; m_unit_factor = 1.0f; @@ -1668,6 +1687,12 @@ namespace Slic3r { return true; } + inline static void check_painting_version(unsigned int loaded_version, unsigned int highest_supported_version, const std::string &error_msg) + { + if (loaded_version > highest_supported_version) + throw version_error(error_msg); + } + bool _3MF_Importer::_handle_end_metadata() { if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION) { @@ -1680,6 +1705,24 @@ namespace Slic3r { } } + if (m_curr_metadata_name == SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION) { + m_fdm_supports_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); + check_painting_version(m_fdm_supports_painting_version, FDM_SUPPORTS_PAINTING_VERSION, + _(L("The selected 3MF contains FDM supports painted object using a newer version of PrusaSlicer and is not compatible."))); + } + + if (m_curr_metadata_name == SLIC3RPE_SEAM_PAINTING_VERSION) { + m_seam_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); + check_painting_version(m_seam_painting_version, SEAM_PAINTING_VERSION, + _(L("The selected 3MF contains seam painted object using a newer version of PrusaSlicer and is not compatible."))); + } + + if (m_curr_metadata_name == SLIC3RPE_MM_PAINTING_VERSION) { + m_mm_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); + check_painting_version(m_mm_painting_version, MM_PAINTING_VERSION, + _(L("The selected 3MF contains multi-material painted object using a newer version of PrusaSlicer and is not compatible."))); + } + return true; } @@ -1837,6 +1880,7 @@ namespace Slic3r { } unsigned int geo_tri_count = (unsigned int)geometry.triangles.size() / 3; + unsigned int renamed_volumes_count = 0; for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) { if (geo_tri_count <= volume_data.first_triangle_id || geo_tri_count <= volume_data.last_triangle_id || volume_data.last_triangle_id < volume_data.first_triangle_id) { @@ -1846,11 +1890,17 @@ namespace Slic3r { Transform3d volume_matrix_to_object = Transform3d::Identity(); bool has_transform = false; +#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT + bool is_left_handed = false; +#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT // extract the volume transformation from the volume's metadata, if present for (const Metadata& metadata : volume_data.metadata) { if (metadata.key == MATRIX_KEY) { volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value); has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10); +#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT + is_left_handed = Slic3r::Geometry::Transformation(volume_matrix_to_object).is_left_handed(); +#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT break; } } @@ -1882,6 +1932,13 @@ namespace Slic3r { stl_get_size(&stl); triangle_mesh.repair(); +#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT + // PrusaSlicer older than 2.4.0 saved mirrored volumes with reversed winding of the triangles + // This caused the call to TriangleMesh::repair() to reverse all the facets because the calculated volume was negative + if (is_left_handed && stl.stats.facets_reversed > 0 && stl.stats.facets_reversed == stl.stats.original_num_facets) + stl.stats.facets_reversed = 0; +#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT + if (m_version == 0) { // if the 3mf was not produced by PrusaSlicer and there is only one instance, // bake the transformation into the geometry to allow the reload from disk command @@ -1945,6 +2002,14 @@ namespace Slic3r { else volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); } + + // this may happen for 3mf saved by 3rd part softwares + if (volume->name.empty()) { + volume->name = object.name; + if (renamed_volumes_count > 0) + volume->name += "_" + std::to_string(renamed_volumes_count + 1); + ++renamed_volumes_count; + } } return true; @@ -2271,6 +2336,16 @@ namespace Slic3r { stream << "\n"; stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n"; stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "\n"; + + if (model.is_fdm_support_painted()) + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION << "\">" << FDM_SUPPORTS_PAINTING_VERSION << "\n"; + + if (model.is_seam_painted()) + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_SEAM_PAINTING_VERSION << "\">" << SEAM_PAINTING_VERSION << "\n"; + + if (model.is_mm_painted()) + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_MM_PAINTING_VERSION << "\">" << MM_PAINTING_VERSION << "\n"; + std::string name = xml_escape(boost::filesystem::path(filename).stem().string()); stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "\n"; stream << " <" << METADATA_TAG << " name=\"Designer\">" << "\n"; @@ -2506,6 +2581,10 @@ namespace Slic3r { if (volume == nullptr) continue; +#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT + bool is_left_handed = volume->is_left_handed(); +#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT + VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume); assert(volume_it != volumes_offsets.end()); @@ -2520,6 +2599,15 @@ namespace Slic3r { { const Vec3i &idx = its.indices[i]; char *ptr = buf; +#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT + boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG << + " v1=\"" << boost::spirit::int_ << + "\" v2=\"" << boost::spirit::int_ << + "\" v3=\"" << boost::spirit::int_ << "\"", + idx[is_left_handed ? 2 : 0] + volume_it->second.first_vertex_id, + idx[1] + volume_it->second.first_vertex_id, + idx[is_left_handed ? 0 : 2] + volume_it->second.first_vertex_id); +#else boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG << " v1=\"" << boost::spirit::int_ << "\" v2=\"" << boost::spirit::int_ << @@ -2527,6 +2615,7 @@ namespace Slic3r { idx[0] + volume_it->second.first_vertex_id, idx[1] + volume_it->second.first_vertex_id, idx[2] + volume_it->second.first_vertex_id); +#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT *ptr = '\0'; output_buffer += buf; } @@ -2577,9 +2666,16 @@ namespace Slic3r { bool _3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) { +#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED + // This happens for empty projects +#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED if (build_items.size() == 0) { add_error("No build item found"); +#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED + return true; +#else return false; +#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED } stream << " <" << BUILD_TAG << ">\n"; @@ -2954,6 +3050,19 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv return true; } +// Perform conversions based on the config values available. +//FIXME provide a version of PrusaSlicer that stored the project file (3MF). +static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config) +{ + if (! config.has("brim_separation")) { + if (auto *opt_elephant_foot = config.option("elefant_foot_compensation", false); opt_elephant_foot) { + // Conversion from older PrusaSlicer which applied brim separation equal to elephant foot compensation. + auto *opt_brim_separation = config.option("brim_separation", true); + opt_brim_separation->value = opt_elephant_foot->value; + } + } +} + bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) { if (path == nullptr || model == nullptr) @@ -2964,6 +3073,7 @@ bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionCo _3MF_Importer importer; bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version); importer.log_errors(); + handle_legacy_project_loaded(importer.version(), config); return res; } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 254f1d4fd9..cebc9136f7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -13,6 +13,7 @@ #include "ClipperUtils.hpp" #include "libslic3r.h" #include "LocalesUtils.hpp" +#include "libslic3r/format.hpp" #include #include @@ -34,6 +35,7 @@ #include "SVG.hpp" #include +#include #include @@ -512,7 +514,8 @@ std::vector GCode::collect_layers_to_print(const PrintObjec bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); - // Check that there are extrusions on the very first layer. + // Check that there are extrusions on the very first layer. The case with empty + // first layer may result in skirt/brim in the air and maybe other issues. if (layers_to_print.size() == 1u) { if (!has_extrusions) throw Slic3r::SlicingError(_(L("There is an object with no extrusions in the first layer.")) + "\n" + @@ -534,11 +537,12 @@ std::vector GCode::collect_layers_to_print(const PrintObjec if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) { const_cast(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, - _(L("Empty layers detected. Make sure the object is printable.")) + "\n" + - _(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " + - std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is " - "usually caused by negligibly small extrusions or by a faulty model. Try to repair " - "the model or change its orientation on the bed."))); + Slic3r::format(_(L("Empty layer detected between heights %1% and %2%. Make sure the object is printable.")), + (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.), + layers_to_print.back().print_z()) + + "\n" + Slic3r::format(_(L("Object name: %1%")), object.model_object()->name) + "\n\n" + + _(L("This is usually caused by negligibly small extrusions or by a faulty model. " + "Try to repair the model or change its orientation on the bed."))); } // Remember last layer with extrusions. @@ -741,27 +745,28 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re std::string path_tmp(path); path_tmp += ".tmp"; - FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb"); - if (file == nullptr) + m_processor.initialize(path_tmp); + GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor); + if (! file.is_open()) throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); try { m_placeholder_parser_failed_templates.clear(); this->_do_export(*print, file, thumbnail_cb); - fflush(file); - if (ferror(file)) { - fclose(file); + file.flush(); + if (file.is_error()) { + file.close(); boost::nowide::remove(path_tmp.c_str()); throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); } } catch (std::exception & /* ex */) { // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown. // Close and remove the file. - fclose(file); + file.close(); boost::nowide::remove(path_tmp.c_str()); throw; } - fclose(file); + file.close(); if (! m_placeholder_parser_failed_templates.empty()) { // G-code export proceeded, but some of the PlaceholderParser substitutions failed. @@ -779,7 +784,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re } BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); - m_processor.process_file(path_tmp, true, [print]() { print->throw_if_canceled(); }); + m_processor.finalize(); // DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); DoExport::update_print_estimated_stats(m_processor, m_writer.extruders(), print->m_print_statistics); if (result != nullptr) { @@ -1043,7 +1048,7 @@ std::vector sort_object_instances_by_model_order(const Pri return instances; } -void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb) +void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb) { PROFILE_FUNC(); @@ -1095,7 +1100,6 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu m_volumetric_speed = DoExport::autospeed_volumetric_limit(print); print.throw_if_canceled(); - m_cooling_buffer = make_unique(*this); if (print.config().spiral_vase.value) m_spiral_vase = make_unique(print.config()); #ifdef HAS_PRESSURE_EQUALIZER @@ -1108,10 +1112,10 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu #endif /* HAS_PRESSURE_EQUALIZER */ // Write information on the generator. - _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str()); + file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str()); DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option("thumbnails")->values, - [this, file](const char* sz) { this->_write(file, sz); }, + [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); }); // Write notes (content of the Print Settings tab -> Notes) @@ -1122,10 +1126,10 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Remove the trailing '\r' from the '\r\n' sequence. if (! line.empty() && line.back() == '\r') line.pop_back(); - _write_format(file, "; %s\n", line.c_str()); + file.write_format("; %s\n", line.c_str()); } if (! lines.empty()) - _write(file, "\n"); + file.write("\n"); } print.throw_if_canceled(); @@ -1136,22 +1140,22 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu const double first_layer_height = print.config().first_layer_height.value; for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) { const PrintRegion ®ion = print.get_print_region(region_id); - _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width()); - _write_format(file, "; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width()); - _write_format(file, "; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width()); - _write_format(file, "; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width()); - _write_format(file, "; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width()); + file.write_format("; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width()); + file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width()); + file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width()); + file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width()); + file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width()); if (print.has_support_material()) - _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width()); + file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width()); if (print.config().first_layer_extrusion_width.value > 0) - _write_format(file, "; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, first_layer_height, true).width()); - _write_format(file, "\n"); + file.write_format("; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, first_layer_height, true).width()); + file.write_format("\n"); } print.throw_if_canceled(); // adds tags for time estimators if (print.config().remaining_times.value) - _write_format(file, ";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str()); + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str()); // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser(); @@ -1208,6 +1212,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu } print.throw_if_canceled(); + m_cooling_buffer = make_unique(*this); m_cooling_buffer->set_current_extruder(initial_extruder_id); // Emit machine envelope limits for the Marlin firmware. @@ -1215,7 +1220,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Disable fan. if (! print.config().cooling.get_at(initial_extruder_id) || print.config().disable_fan_first_layers.get_at(initial_extruder_id)) - _write(file, m_writer.set_fan(0, true)); + file.write(m_writer.set_fan(0)); // Let the start-up script prime the 1st printing tool. m_placeholder_parser.set("initial_tool", initial_extruder_id); @@ -1258,10 +1263,10 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false); // adds tag for processor - _write_format(file, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); + file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); // Write the custom start G-code - _writeln(file, start_gcode); + file.writeln(start_gcode); // Process filament-specific gcode. /* if (has_wipe_tower) { @@ -1269,14 +1274,14 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu } else { DynamicConfig config; config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); - _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); + file.writeln(this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); } */ this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true); print.throw_if_canceled(); // Set other general things. - _write(file, this->preamble()); + file.write(this->preamble()); // Calculate wiping points if needed DoExport::init_ooze_prevention(print, m_ooze_prevention); @@ -1288,7 +1293,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) { // Set initial extruder only after custom start G-code. // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. - _write(file, this->set_extruder(initial_extruder_id, 0.)); + file.write(this->set_extruder(initial_extruder_id, 0.)); } // Do all objects for each layer. @@ -1314,8 +1319,8 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // This happens before Z goes down to layer 0 again, so that no collision happens hopefully. m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer m_avoid_crossing_perimeters.use_external_mp_once(); - _write(file, this->retract()); - _write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); + file.write(this->retract()); + file.write(this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. m_avoid_crossing_perimeters.disable_once(); @@ -1327,23 +1332,18 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature. this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false); this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false); - _writeln(file, between_objects_gcode); + file.writeln(between_objects_gcode); } // Reset the cooling buffer internal state (the current position, feed rate, accelerations). - m_cooling_buffer->reset(); + m_cooling_buffer->reset(this->writer().get_position()); m_cooling_buffer->set_current_extruder(initial_extruder_id); - // Pair the object layers with the support layers by z, extrude them. - std::vector layers_to_print = collect_layers_to_print(object); - for (const LayerToPrint <p : layers_to_print) { - std::vector lrs; - lrs.emplace_back(std::move(ltp)); - this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), <p == &layers_to_print.back(), - nullptr, *print_object_instance_sequential_active - object.instances().data()); - print.throw_if_canceled(); - } + // Process all layers of a single object instance (sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file); #ifdef HAS_PRESSURE_EQUALIZER if (m_pressure_equalizer) - _write(file, m_pressure_equalizer->process("", true)); + file.write(m_pressure_equalizer->process("", true)); #endif /* HAS_PRESSURE_EQUALIZER */ ++ finished_objects; // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. @@ -1358,9 +1358,9 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Prusa Multi-Material wipe tower. if (has_wipe_tower && ! layers_to_print.empty()) { m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get())); - _write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height")); + file.write(m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height")); if (print.config().single_extruder_multi_material_priming) { - _write(file, m_wipe_tower->prime(*this)); + file.write(m_wipe_tower->prime(*this)); // Verify, whether the print overaps the priming extrusions. BoundingBoxf bbox_print(get_print_extrusions_extents(print)); coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; @@ -1372,15 +1372,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu bool overlap = bbox_prime.overlap(bbox_print); if (print.config().gcode_flavor == gcfMarlinLegacy || print.config().gcode_flavor == gcfMarlinFirmware) { - _write(file, this->retract()); - _write(file, "M300 S800 P500\n"); // Beep for 500ms, tone 800Hz. + file.write(this->retract()); + file.write("M300 S800 P500\n"); // Beep for 500ms, tone 800Hz. if (overlap) { // Wait for the user to remove the priming extrusions. - _write(file, "M1 Remove priming towers and click button.\n"); + file.write("M1 Remove priming towers and click button.\n"); } else { // Just wait for a bit to let the user check, that the priming succeeded. //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. - _write(file, "M1 S10\n"); + file.write("M1 S10\n"); } } else { // This is not Marlin, M1 command is probably not supported. @@ -1397,29 +1397,25 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu } print.throw_if_canceled(); } - // Extrude the layers. - for (auto &layer : layers_to_print) { - const LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first); - if (m_wipe_tower && layer_tools.has_wipe_tower) - m_wipe_tower->next_layer(); - this->process_layer(file, print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); - print.throw_if_canceled(); - } + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file); #ifdef HAS_PRESSURE_EQUALIZER if (m_pressure_equalizer) - _write(file, m_pressure_equalizer->process("", true)); + file.write(m_pressure_equalizer->process("", true)); #endif /* HAS_PRESSURE_EQUALIZER */ if (m_wipe_tower) // Purge the extruder, pull out the active filament. - _write(file, m_wipe_tower->finalize(*this)); + file.write(m_wipe_tower->finalize(*this)); } // Write end commands to file. - _write(file, this->retract()); - _write(file, m_writer.set_fan(false)); + file.write(this->retract()); + file.write(m_writer.set_fan(0)); // adds tag for processor - _write_format(file, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); + file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); // Process filament-specific gcode in extruder order. { @@ -1431,52 +1427,134 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Process the end_filament_gcode for the active filament only. int extruder_id = m_writer.extruder()->id(); config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); - _writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config().end_filament_gcode.get_at(extruder_id), extruder_id, &config)); + file.writeln(this->placeholder_parser_process("end_filament_gcode", print.config().end_filament_gcode.get_at(extruder_id), extruder_id, &config)); } else { for (const std::string &end_gcode : print.config().end_filament_gcode.values) { int extruder_id = (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front()); config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); - _writeln(file, this->placeholder_parser_process("end_filament_gcode", end_gcode, extruder_id, &config)); + file.writeln(this->placeholder_parser_process("end_filament_gcode", end_gcode, extruder_id, &config)); } } - _writeln(file, this->placeholder_parser_process("end_gcode", print.config().end_gcode, m_writer.extruder()->id(), &config)); + file.writeln(this->placeholder_parser_process("end_gcode", print.config().end_gcode, m_writer.extruder()->id(), &config)); } - _write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% - _write(file, m_writer.postamble()); + file.write(m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% + file.write(m_writer.postamble()); // adds tags for time estimators if (print.config().remaining_times.value) - _write_format(file, ";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Last_Line_M73_Placeholder).c_str()); + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Last_Line_M73_Placeholder).c_str()); print.throw_if_canceled(); // Get filament stats. - _write(file, DoExport::update_print_stats_and_format_filament_stats( + file.write(DoExport::update_print_stats_and_format_filament_stats( // Const inputs has_wipe_tower, print.wipe_tower_data(), m_writer.extruders(), // Modifies print.m_print_statistics)); - _write(file, "\n"); - _write_format(file, "; total filament used [g] = %.2lf\n", print.m_print_statistics.total_weight); - _write_format(file, "; total filament cost = %.2lf\n", print.m_print_statistics.total_cost); + file.write("\n"); + file.write_format("; total filament used [g] = %.2lf\n", print.m_print_statistics.total_weight); + file.write_format("; total filament cost = %.2lf\n", print.m_print_statistics.total_cost); if (print.m_print_statistics.total_toolchanges > 0) - _write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges); - _write_format(file, ";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); + file.write_format("; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges); + file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str()); // Append full config, delimited by two 'phony' configuration keys prusaslicer_config = begin and prusaslicer_config = end. // The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer. { - _write(file, "\n; prusaslicer_config = begin\n"); + file.write("\n; prusaslicer_config = begin\n"); std::string full_config; append_full_config(print, full_config); if (!full_config.empty()) - _write(file, full_config); - _write(file, "; prusaslicer_config = end\n"); + file.write(full_config); + file.write("; prusaslicer_config = end\n"); } print.throw_if_canceled(); } +// Process all layers of all objects (non-sequential mode) with a parallel pipeline: +// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser +// and export G-code into file. +void GCode::process_layers( + const Print &print, + const ToolOrdering &tool_ordering, + const std::vector &print_object_instances_ordering, + const std::vector>> &layers_to_print, + GCodeOutputStream &output_stream) +{ + // The pipeline is variable: The vase mode filter is optional. + size_t layer_to_print_idx = 0; + const auto generator = tbb::make_filter(tbb::filter::serial_in_order, + [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> GCode::LayerResult { + if (layer_to_print_idx == layers_to_print.size()) { + fc.stop(); + return {}; + } else { + const std::pair>& layer = layers_to_print[layer_to_print_idx++]; + const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first); + if (m_wipe_tower && layer_tools.has_wipe_tower) + m_wipe_tower->next_layer(); + print.throw_if_canceled(); + return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); + } + }); + const auto spiral_vase = tbb::make_filter(tbb::filter::serial_in_order, + [&spiral_vase = *this->m_spiral_vase.get()](GCode::LayerResult in) -> GCode::LayerResult { + spiral_vase.enable(in.spiral_vase_enable); + return { spiral_vase.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; + }); + const auto cooling = tbb::make_filter(tbb::filter::serial_in_order, + [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string { + return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); + }); + const auto output = tbb::make_filter(tbb::filter::serial_in_order, + [&output_stream](std::string s) { output_stream.write(s); } + ); + + // The pipeline elements are joined using const references, thus no copying is performed. + if (m_spiral_vase) + tbb::parallel_pipeline(12, generator & spiral_vase & cooling & output); + else + tbb::parallel_pipeline(12, generator & cooling & output); +} + +// Process all layers of a single object instance (sequential mode) with a parallel pipeline: +// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser +// and export G-code into file. +void GCode::process_layers( + const Print &print, + const ToolOrdering &tool_ordering, + std::vector layers_to_print, + const size_t single_object_idx, + GCodeOutputStream &output_stream) +{ + // The pipeline is fixed: Neither wipe tower nor vase mode are implemented for sequential print. + size_t layer_to_print_idx = 0; + tbb::parallel_pipeline(12, + tbb::make_filter( + tbb::filter::serial_in_order, + [this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx](tbb::flow_control& fc) -> GCode::LayerResult { + if (layer_to_print_idx == layers_to_print.size()) { + fc.stop(); + return {}; + } else { + LayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; + print.throw_if_canceled(); + return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx); + } + }) & + tbb::make_filter( + tbb::filter::serial_in_order, + [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string { + return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); + }) & + tbb::make_filter( + tbb::filter::serial_in_order, + [&output_stream](std::string s) { output_stream.write(s); } + )); +} + std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) { try { @@ -1562,16 +1640,16 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters. // Do not process this piece of G-code by the time estimator, it already knows the values through another sources. -void GCode::print_machine_envelope(FILE *file, Print &print) +void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print) { if ((print.config().gcode_flavor.value == gcfMarlinLegacy || print.config().gcode_flavor.value == gcfMarlinFirmware) && print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) { - fprintf(file, "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n", + file.write_format("M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n", int(print.config().machine_max_acceleration_x.values.front() + 0.5), int(print.config().machine_max_acceleration_y.values.front() + 0.5), int(print.config().machine_max_acceleration_z.values.front() + 0.5), int(print.config().machine_max_acceleration_e.values.front() + 0.5)); - fprintf(file, "M203 X%d Y%d Z%d E%d ; sets maximum feedrates, mm/sec\n", + file.write_format("M203 X%d Y%d Z%d E%d ; sets maximum feedrates, mm/sec\n", int(print.config().machine_max_feedrate_x.values.front() + 0.5), int(print.config().machine_max_feedrate_y.values.front() + 0.5), int(print.config().machine_max_feedrate_z.values.front() + 0.5), @@ -1584,18 +1662,18 @@ void GCode::print_machine_envelope(FILE *file, Print &print) int travel_acc = print.config().gcode_flavor == gcfMarlinLegacy ? int(print.config().machine_max_acceleration_extruding.values.front() + 0.5) : int(print.config().machine_max_acceleration_travel.values.front() + 0.5); - fprintf(file, "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n", + file.write_format("M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n", int(print.config().machine_max_acceleration_extruding.values.front() + 0.5), int(print.config().machine_max_acceleration_retracting.values.front() + 0.5), travel_acc); assert(is_decimal_separator_point()); - fprintf(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", + file.write_format("M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n", print.config().machine_max_jerk_x.values.front(), print.config().machine_max_jerk_y.values.front(), print.config().machine_max_jerk_z.values.front(), print.config().machine_max_jerk_e.values.front()); - fprintf(file, "M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec\n", + file.write_format("M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec\n", int(print.config().machine_min_extruding_rate.values.front() + 0.5), int(print.config().machine_min_travel_rate.values.front() + 0.5)); } @@ -1605,7 +1683,7 @@ void GCode::print_machine_envelope(FILE *file, Print &print) // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. // M140 - Set Extruder Temperature // M190 - Set Extruder Temperature and Wait -void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +void GCode::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) { // Initial bed temperature based on the first extruder. int temp = print.config().first_layer_bed_temperature.get_at(first_printing_extruder_id); @@ -1618,7 +1696,7 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s // the custom start G-code emited these. std::string set_temp_gcode = m_writer.set_bed_temperature(temp, wait); if (! temp_set_by_gcode) - _write(file, set_temp_gcode); + file.write(set_temp_gcode); } // Write 1st layer extruder temperatures into the G-code. @@ -1626,7 +1704,7 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s // M104 - Set Extruder Temperature // M109 - Set Extruder Temperature and Wait // RepRapFirmware: G10 Sxx -void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) { // Is the bed temperature set by the provided custom G-code? int temp_by_gcode = -1; @@ -1643,7 +1721,7 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c // Set temperature of the first printing extruder only. int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id); if (temp > 0) - _write(file, m_writer.set_temperature(temp, wait, first_printing_extruder_id)); + file.write(m_writer.set_temperature(temp, wait, first_printing_extruder_id)); } else { // Set temperatures of all the printing extruders. for (unsigned int tool_id : print.extruders()) { @@ -1651,7 +1729,7 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c if (print.config().ooze_prevention.value) temp += print.config().standby_temperature_delta.value; if (temp > 0) - _write(file, m_writer.set_temperature(temp, wait, tool_id)); + file.write(m_writer.set_temperature(temp, wait, tool_id)); } } } @@ -1886,9 +1964,7 @@ namespace Skirt { // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths // and performing the extruder specific extrusions together. -void GCode::process_layer( - // Write into the output file. - FILE *file, +GCode::LayerResult GCode::process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, @@ -1904,11 +1980,6 @@ void GCode::process_layer( // Either printing all copies of all objects, or just a single copy of a single object. assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); - if (layer_tools.extruders.empty()) - // Nothing to extrude. - return; - - // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. const Layer *object_layer = nullptr; const SupportLayer *support_layer = nullptr; for (const LayerToPrint &l : layers) { @@ -1918,6 +1989,12 @@ void GCode::process_layer( support_layer = l.support_layer; } const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer; + GCode::LayerResult result { {}, layer.id(), false, last_layer }; + if (layer_tools.extruders.empty()) + // Nothing to extrude. + return result; + + // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. coordf_t print_z = layer.print_z; bool first_layer = layer.id() == 0; unsigned int first_extruder_id = layer_tools.extruders.front(); @@ -1939,7 +2016,7 @@ void GCode::process_layer( break; } } - m_spiral_vase->enable(enable); + result.spiral_vase_enable = enable; // If we're going to apply spiralvase to this layer, disable loop clipping. m_enable_loop_clipping = !enable; } @@ -1974,6 +2051,7 @@ void GCode::process_layer( } gcode += this->change_layer(print_z); // this will increase m_layer_index m_layer = &layer; + m_object_layer_over_raft = false; if (! print.config().layer_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); @@ -2232,8 +2310,13 @@ void GCode::process_layer( gcode+="; PURGING FINISHED\n"; for (InstanceToPrint &instance_to_print : instances_to_print) { + const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; + // To control print speed of the 1st object layer printed over raft interface. + bool object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && + instance_to_print.print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); m_config.apply(instance_to_print.print_object.config(), true); - m_layer = layers[instance_to_print.layer_id].layer(); + m_layer = layer_to_print.layer(); + m_object_layer_over_raft = object_layer_over_raft; if (m_config.avoid_crossing_perimeters) m_avoid_crossing_perimeters.init_layer(*m_layer); if (this->config().gcode_label_objects) @@ -2246,11 +2329,13 @@ void GCode::process_layer( m_last_obj_copy = this_object_copy; this->set_origin(unscale(offset)); if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) { - m_layer = layers[instance_to_print.layer_id].support_layer; + m_layer = layer_to_print.support_layer; + m_object_layer_over_raft = false; gcode += this->extrude_support( // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role)); - m_layer = layers[instance_to_print.layer_id].layer(); + m_layer = layer_to_print.layer(); + m_object_layer_over_raft = object_layer_over_raft; } //FIXME order islands? // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) @@ -2273,6 +2358,7 @@ void GCode::process_layer( } } +#if 0 // Apply spiral vase post-processing if this layer contains suitable geometry // (we must feed all the G-code into the post-processor, including the first // bottom non-spiral layers otherwise it will mess with positions) @@ -2295,9 +2381,15 @@ void GCode::process_layer( // printf("G-code after filter:\n%s\n", out.c_str()); #endif /* HAS_PRESSURE_EQUALIZER */ - _write(file, gcode); + file.write(gcode); +#endif + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); + + result.gcode = std::move(gcode); + result.cooling_buffer_flush = object_layer || last_layer; + return result; } void GCode::apply_print_config(const PrintConfig &print_config) @@ -2631,22 +2723,42 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill return gcode; } -void GCode::_write(FILE* file, const char *what) +bool GCode::GCodeOutputStream::is_error() const +{ + return ::ferror(this->f); +} + +void GCode::GCodeOutputStream::flush() +{ + ::fflush(this->f); +} + +void GCode::GCodeOutputStream::close() +{ + if (this->f) { + ::fclose(this->f); + this->f = nullptr; + } +} + +void GCode::GCodeOutputStream::write(const char *what) { if (what != nullptr) { const char* gcode = what; // writes string to file - fwrite(gcode, 1, ::strlen(gcode), file); + fwrite(gcode, 1, ::strlen(gcode), this->f); + //FIXME don't allocate a string, maybe process a batch of lines? + m_processor.process_buffer(std::string(gcode)); } } -void GCode::_writeln(FILE* file, const std::string &what) +void GCode::GCodeOutputStream::writeln(const std::string &what) { if (! what.empty()) - _write(file, (what.back() == '\n') ? what : (what + '\n')); + this->write(what.back() == '\n' ? what : what + '\n'); } -void GCode::_write_format(FILE* file, const char* format, ...) +void GCode::GCodeOutputStream::write_format(const char* format, ...) { va_list args; va_start(args, format); @@ -2670,7 +2782,7 @@ void GCode::_write_format(FILE* file, const char* format, ...) char *bufptr = buffer_dynamic ? (char*)malloc(buflen) : buffer; int res = ::vsnprintf(bufptr, buflen, format, args); if (res > 0) - _write(file, bufptr); + this->write(bufptr); if (buffer_dynamic) free(bufptr); @@ -2702,6 +2814,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double acceleration; if (this->on_first_layer() && m_config.first_layer_acceleration.value > 0) { acceleration = m_config.first_layer_acceleration.value; + } else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) { + acceleration = m_config.first_layer_acceleration_over_raft.value; } else if (m_config.perimeter_acceleration.value > 0 && is_perimeter(path.role())) { acceleration = m_config.perimeter_acceleration.value; } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())) { @@ -2746,6 +2860,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, speed = m_volumetric_speed / path.mm3_per_mm; if (this->on_first_layer()) speed = m_config.get_abs_value("first_layer_speed", speed); + else if (this->object_layer_over_raft()) + speed = m_config.get_abs_value("first_layer_speed_over_raft", speed); if (m_config.max_volumetric_speed.value > 0) { // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) speed = std::min( diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 08ab830024..93a11821e4 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -125,7 +125,8 @@ public: m_last_processor_extrusion_role(erNone), m_layer_count(0), m_layer_index(-1), - m_layer(nullptr), + m_layer(nullptr), + m_object_layer_over_raft(false), m_volumetric_speed(0), m_last_pos_defined(false), m_last_extrusion_role(erNone), @@ -138,7 +139,7 @@ public: m_silent_time_estimator_enabled(false), m_last_obj_copy(nullptr, Point(std::numeric_limits::max(), std::numeric_limits::max())) {} - ~GCode() {} + ~GCode() = default; // throws std::runtime_exception on error, // throws CanceledException through print->throw_if_canceled(). @@ -183,13 +184,47 @@ public: }; private: - void _do_export(Print &print, FILE *file, ThumbnailsGeneratorCallback thumbnail_cb); + class GCodeOutputStream { + public: + GCodeOutputStream(FILE *f, GCodeProcessor &processor) : f(f), m_processor(processor) {} + ~GCodeOutputStream() { this->close(); } + + bool is_open() const { return f; } + bool is_error() const; + + void flush(); + void close(); + + // Write a string into a file. + void write(const std::string& what) { this->write(what.c_str()); } + void write(const char* what); + + // Write a string into a file. + // Add a newline, if the string does not end with a newline already. + // Used to export a custom G-code section processed by the PlaceholderParser. + void writeln(const std::string& what); + + // Formats and write into a file the given data. + void write_format(const char* format, ...); + + private: + FILE *f = nullptr; + GCodeProcessor &m_processor; + }; + void _do_export(Print &print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb); static std::vector collect_layers_to_print(const PrintObject &object); static std::vector>> collect_layers_to_print(const Print &print); - void process_layer( - // Write into the output file. - FILE *file, + + struct LayerResult { + std::string gcode; + size_t layer_id; + // Is spiral vase post processing enabled for this layer? + bool spiral_vase_enable { false }; + // Should the cooling buffer content be flushed at the end of this layer? + bool cooling_buffer_flush { false }; + }; + LayerResult process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, @@ -200,6 +235,24 @@ private: // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_idx = size_t(-1)); + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + void process_layers( + const Print &print, + const ToolOrdering &tool_ordering, + const std::vector &print_object_instances_ordering, + const std::vector>> &layers_to_print, + GCodeOutputStream &output_stream); + // Process all layers of a single object instance (sequential mode) with a parallel pipeline: + // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser + // and export G-code into file. + void process_layers( + const Print &print, + const ToolOrdering &tool_ordering, + std::vector layers_to_print, + const size_t single_object_idx, + GCodeOutputStream &output_stream); void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } bool last_pos_defined() const { return m_last_pos_defined; } @@ -316,9 +369,11 @@ private: unsigned int m_layer_count; // Progress bar indicator. Increments from -1 up to layer_count. int m_layer_index; - // Current layer processed. Insequential printing mode, only a single copy will be printed. + // Current layer processed. In sequential printing mode, only a single copy will be printed. // In non-sequential mode, all its copies will be printed. const Layer* m_layer; + // m_layer is an object layer and it is being printed over raft surface. + bool m_object_layer_over_raft; double m_volumetric_speed; // Support for the extrusion role markers. Which marker is active? ExtrusionRole m_last_extrusion_role; @@ -355,24 +410,14 @@ private: // Processor GCodeProcessor m_processor; - // Write a string into a file. - void _write(FILE* file, const std::string& what) { this->_write(file, what.c_str()); } - void _write(FILE* file, const char *what); - - // Write a string into a file. - // Add a newline, if the string does not end with a newline already. - // Used to export a custom G-code section processed by the PlaceholderParser. - void _writeln(FILE* file, const std::string& what); - - // Formats and write into a file the given data. - void _write_format(FILE* file, const char* format, ...); - std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1); - void print_machine_envelope(FILE *file, Print &print); - void _print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); - void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); + void print_machine_envelope(GCodeOutputStream &file, Print &print); + void _print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); + void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); // On the first printing layer. This flag triggers first layer speeds. bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; } + // To control print speed of 1st object layer over raft interface. + bool object_layer_over_raft() const { return m_object_layer_over_raft; } friend ObjectByExtruder& object_by_extruder( std::map> &by_extruder, diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 17f3e0b737..9ca85c7281 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -16,19 +16,25 @@ namespace Slic3r { -CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_gcodegen(gcodegen), m_current_extruder(0) +CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0) { - this->reset(); + this->reset(gcodegen.writer().get_position()); + + const std::vector &extruders = gcodegen.writer().extruders(); + m_extruder_ids.reserve(extruders.size()); + for (const Extruder &ex : extruders) { + m_num_extruders = std::max(ex.id() + 1, m_num_extruders); + m_extruder_ids.emplace_back(ex.id()); + } } -void CoolingBuffer::reset() +void CoolingBuffer::reset(const Vec3d &position) { m_current_pos.assign(5, 0.f); - Vec3d pos = m_gcodegen.writer().get_position(); - m_current_pos[0] = float(pos(0)); - m_current_pos[1] = float(pos(1)); - m_current_pos[2] = float(pos(2)); - m_current_pos[4] = float(m_gcodegen.config().travel_speed.value); + m_current_pos[0] = float(position.x()); + m_current_pos[1] = float(position.y()); + m_current_pos[2] = float(position.z()); + m_current_pos[4] = float(m_config.travel_speed.value); } struct CoolingLine @@ -303,30 +309,23 @@ std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, b // Return the list of parsed lines, bucketed by an extruder. std::vector CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector ¤t_pos) const { - const FullPrintConfig &config = m_gcodegen.config(); - const std::vector &extruders = m_gcodegen.writer().extruders(); - unsigned int num_extruders = 0; - for (const Extruder &ex : extruders) - num_extruders = std::max(ex.id() + 1, num_extruders); - - std::vector per_extruder_adjustments(extruders.size()); - std::vector map_extruder_to_per_extruder_adjustment(num_extruders, 0); - for (size_t i = 0; i < extruders.size(); ++ i) { + std::vector per_extruder_adjustments(m_extruder_ids.size()); + std::vector map_extruder_to_per_extruder_adjustment(m_num_extruders, 0); + for (size_t i = 0; i < m_extruder_ids.size(); ++ i) { PerExtruderAdjustments &adj = per_extruder_adjustments[i]; - unsigned int extruder_id = extruders[i].id(); + unsigned int extruder_id = m_extruder_ids[i]; adj.extruder_id = extruder_id; - adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id); - adj.slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(extruder_id)); - adj.min_print_speed = float(config.min_print_speed.get_at(extruder_id)); + adj.cooling_slow_down_enabled = m_config.cooling.get_at(extruder_id); + adj.slowdown_below_layer_time = float(m_config.slowdown_below_layer_time.get_at(extruder_id)); + adj.min_print_speed = float(m_config.min_print_speed.get_at(extruder_id)); map_extruder_to_per_extruder_adjustment[extruder_id] = i; } - const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); unsigned int current_extruder = m_current_extruder; PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; const char *line_start = gcode.c_str(); const char *line_end = line_start; - const char extrusion_axis = get_extrusion_axis(config)[0]; + const char extrusion_axis = get_extrusion_axis(m_config)[0]; // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command // for a sequence of extrusion moves. size_t active_speed_modifier = size_t(-1); @@ -387,7 +386,7 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: } if ((line.type & CoolingLine::TYPE_G92) == 0) { // G0 or G1. Calculate the duration. - if (config.use_relative_e_distances.value) + if (m_config.use_relative_e_distances.value) // Reset extruder accumulator. current_pos[3] = 0.f; float dif[4]; @@ -430,8 +429,8 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { line.type = CoolingLine::TYPE_EXTRUDE_END; active_speed_modifier = size_t(-1); - } else if (boost::starts_with(sline, toolchange_prefix)) { - unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size()); + } else if (boost::starts_with(sline, m_toolchange_prefix)) { + unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size()); // Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored. if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) { if (new_extruder != current_extruder) { @@ -641,7 +640,7 @@ float CoolingBuffer::calculate_layer_slowdown(std::vector slowdown_below_layer_time) { // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything. } else { - // Adjust this and all the following (higher config.slowdown_below_layer_time) extruders. + // Adjust this and all the following (higher m_config.slowdown_below_layer_time) extruders. // Sum maximum slow down time as if everything was slowed down including the external perimeters. float max_time = elapsed_time_total0; for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it) @@ -694,8 +693,7 @@ std::string CoolingBuffer::apply_layer_cooldown( bool bridge_fan_control = false; int bridge_fan_speed = 0; auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() { - const FullPrintConfig &config = m_gcodegen.config(); -#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) +#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; int disable_fan_first_layers = EXTRUDER_CONFIG(disable_fan_first_layers); @@ -737,13 +735,12 @@ std::string CoolingBuffer::apply_layer_cooldown( } if (fan_speed_new != fan_speed) { fan_speed = fan_speed_new; - new_gcode += m_gcodegen.writer().set_fan(fan_speed); + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); } }; const char *pos = gcode.c_str(); int current_feedrate = 0; - const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); change_extruder_set_fan(); for (const CoolingLine *line : lines) { const char *line_start = gcode.c_str() + line->line_start; @@ -751,7 +748,7 @@ std::string CoolingBuffer::apply_layer_cooldown( if (line_start > pos) new_gcode.append(pos, line_start - pos); if (line->type & CoolingLine::TYPE_SET_TOOL) { - unsigned int new_extruder = (unsigned int)atoi(line_start + toolchange_prefix.size()); + unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size()); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; change_extruder_set_fan(); @@ -759,10 +756,10 @@ std::string CoolingBuffer::apply_layer_cooldown( new_gcode.append(line_start, line_end - line_start); } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) { if (bridge_fan_control) - new_gcode += m_gcodegen.writer().set_fan(bridge_fan_speed, true); + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed); } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) { if (bridge_fan_control) - new_gcode += m_gcodegen.writer().set_fan(fan_speed, true); + new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); } else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { // Just remove this comment. } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index 0932d62d37..5f49ef4557 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -23,10 +23,9 @@ struct PerExtruderAdjustments; class CoolingBuffer { public: CoolingBuffer(GCode &gcodegen); - void reset(); + void reset(const Vec3d &position); void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; } std::string process_layer(std::string &&gcode, size_t layer_id, bool flush); - GCode* gcodegen() { return &m_gcodegen; } private: CoolingBuffer& operator=(const CoolingBuffer&) = delete; @@ -36,17 +35,25 @@ private: // Returns the adjusted G-code. std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector &per_extruder_adjustments); - GCode& m_gcodegen; // G-code snippet cached for the support layers preceding an object layer. - std::string m_gcode; + std::string m_gcode; // Internal data. // X,Y,Z,E,F - std::vector m_axis; - std::vector m_current_pos; - unsigned int m_current_extruder; + std::vector m_axis; + std::vector m_current_pos; + // Cached from GCodeWriter. + // Printing extruder IDs, zero based. + std::vector m_extruder_ids; + // Highest of m_extruder_ids plus 1. + unsigned int m_num_extruders { 0 }; + const std::string m_toolchange_prefix; + // Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified, + // the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required. + const PrintConfig &m_config; + unsigned int m_current_extruder; // Old logic: proportional. - bool m_cooling_logic_proportional = false; + bool m_cooling_logic_proportional = false; }; } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 0d71a19f53..d16cfb9cf3 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -21,6 +21,9 @@ #include +static const float DEFAULT_TOOLPATH_WIDTH = 0.4f; +static const float DEFAULT_TOOLPATH_HEIGHT = 0.2f; + static const float INCHES_TO_MM = 25.4f; static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2 @@ -344,20 +347,34 @@ void GCodeProcessor::TimeProcessor::reset() machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true; } -void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector& moves) +struct FilePtr { + FilePtr(FILE *f) : f(f) {} + ~FilePtr() { this->close(); } + void close() { + if (this->f) { + ::fclose(this->f); + this->f = nullptr; + } + } + FILE* f = nullptr; +}; + +void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends) { - boost::nowide::ifstream in(filename); - if (!in.good()) + FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") }; + if (in.f == nullptr) throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); // temporary file to contain modified gcode std::string out_path = filename + ".postprocess"; - FILE* out = boost::nowide::fopen(out_path.c_str(), "wb"); - if (out == nullptr) + FilePtr out{ boost::nowide::fopen(out_path.c_str(), "wb") }; + if (out.f == nullptr) { throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); + } auto time_in_minutes = [](float time_in_seconds) { - return int(::roundf(time_in_seconds / 60.0f)); + assert(time_in_seconds >= 0.f); + return int((time_in_seconds + 0.5f) / 60.0f); }; auto time_in_last_minute = [](float time_in_seconds) { @@ -389,7 +406,6 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st return std::string(line_M73); }; - GCodeReader parser; std::string gcode_line; size_t g1_lines_counter = 0; // keeps track of last exported pair @@ -408,11 +424,12 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st std::string export_line; // replace placeholder lines with the proper final value - auto process_placeholders = [&](const std::string& gcode_line) { + // gcode_line is in/out parameter, to reduce expensive memory allocation + auto process_placeholders = [&](std::string& gcode_line) { unsigned int extra_lines_count = 0; // remove trailing '\n' - std::string line = gcode_line.substr(0, gcode_line.length() - 1); + auto line = std::string_view(gcode_line).substr(0, gcode_line.length() - 1); std::string ret; if (line.length() > 1) { @@ -453,7 +470,10 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st } } - return std::tuple(!ret.empty(), ret.empty() ? gcode_line : ret, (extra_lines_count == 0) ? extra_lines_count : extra_lines_count - 1); + if (! ret.empty()) + // Not moving the move operator on purpose, so that the gcode_line allocation will grow and it will not be reallocated after handful of lines are processed. + gcode_line = ret; + return std::tuple(!ret.empty(), (extra_lines_count == 0) ? extra_lines_count : extra_lines_count - 1); }; // check for temporary lines @@ -476,11 +496,19 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st g1_times_cache_it.emplace_back(machine.g1_times_cache.begin()); // add lines M73 to exported gcode - auto process_line_G1 = [&]() { + auto process_line_G1 = [ + // Lambdas, mostly for string formatting, all with an empty capture block. + time_in_minutes, format_time_float, format_line_M73_main, format_line_M73_stop_int, format_line_M73_stop_float, time_in_last_minute, + &self = std::as_const(*this), + // Caches, to be modified + &g1_times_cache_it, &last_exported_main, &last_exported_stop, + // String output + &export_line] + (const size_t g1_lines_counter) { unsigned int exported_lines_count = 0; - if (export_remaining_time_enabled) { + if (self.export_remaining_time_enabled) { for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - const TimeMachine& machine = machines[i]; + const TimeMachine& machine = self.machines[i]; if (machine.enabled) { // export pair // Skip all machine.g1_times_cache below g1_lines_counter. @@ -544,60 +572,81 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st }; // helper function to write to disk - auto write_string = [&](const std::string& str) { - fwrite((const void*)export_line.c_str(), 1, export_line.length(), out); - if (ferror(out)) { - in.close(); - fclose(out); + size_t out_file_pos = 0; + lines_ends.clear(); + auto write_string = [&export_line, &out, &out_path, &out_file_pos, &lines_ends](const std::string& str) { + fwrite((const void*)export_line.c_str(), 1, export_line.length(), out.f); + if (ferror(out.f)) { + out.close(); boost::nowide::remove(out_path.c_str()); throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n")); } + for (size_t i = 0; i < export_line.size(); ++ i) + if (export_line[i] == '\n') + lines_ends.emplace_back(out_file_pos + i + 1); + out_file_pos += export_line.size(); export_line.clear(); }; unsigned int line_id = 0; std::vector> offsets; - while (std::getline(in, gcode_line)) { - if (!in.good()) { - fclose(out); - throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); - } + { + // Read the input stream 64kB at a time, extract lines and process them. + std::vector buffer(65536 * 10, 0); + // Line buffer. + assert(gcode_line.empty()); + for (;;) { + size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f); + if (::ferror(in.f)) + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); + bool eof = cnt_read == 0; + auto it = buffer.begin(); + auto it_bufend = buffer.begin() + cnt_read; + while (it != it_bufend || (eof && ! gcode_line.empty())) { + // Find end of line. + bool eol = false; + auto it_end = it; + for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ; + // End of line is indicated also if end of file was reached. + eol |= eof && it_end == it_bufend; + gcode_line.insert(gcode_line.end(), it, it_end); + if (eol) { + ++line_id; - ++line_id; - - gcode_line += "\n"; - // replace placeholder lines - auto [processed, result, lines_added_count] = process_placeholders(gcode_line); - if (processed && lines_added_count > 0) - offsets.push_back({ line_id, lines_added_count }); - gcode_line = result; - if (!processed) { - // remove temporary lines - if (is_temporary_decoration(gcode_line)) - continue; - - // add lines M73 where needed - parser.parse_line(gcode_line, - [&](GCodeReader& reader, const GCodeReader::GCodeLine& line) { - if (line.cmd_is("G1")) { - unsigned int extra_lines_count = process_line_G1(); - ++g1_lines_counter; + gcode_line += "\n"; + // replace placeholder lines + auto [processed, lines_added_count] = process_placeholders(gcode_line); + if (processed && lines_added_count > 0) + offsets.push_back({ line_id, lines_added_count }); + if (! processed && ! is_temporary_decoration(gcode_line) && GCodeReader::GCodeLine::cmd_is(gcode_line, "G1")) { + // remove temporary lines, add lines M73 where needed + unsigned int extra_lines_count = process_line_G1(g1_lines_counter ++); if (extra_lines_count > 0) offsets.push_back({ line_id, extra_lines_count }); } - }); - } - export_line += gcode_line; - if (export_line.length() > 65535) - write_string(export_line); + export_line += gcode_line; + if (export_line.length() > 65535) + write_string(export_line); + gcode_line.clear(); + } + // Skip EOL. + it = it_end; + if (it != it_bufend && *it == '\r') + ++ it; + if (it != it_bufend && *it == '\n') + ++ it; + } + if (eof) + break; + } } if (!export_line.empty()) write_string(export_line); - fclose(out); + out.close(); in.close(); // updates moves' gcode ids which have been modified by the insertion of the M73 lines @@ -698,7 +747,9 @@ void GCodeProcessor::Result::reset() { } #else void GCodeProcessor::Result::reset() { - moves = std::vector(); + + moves.clear(); + lines_ends.clear(); bed_shape = Pointfs(); settings_ids.reset(); extruders_count = 0; @@ -777,6 +828,9 @@ bool GCodeProcessor::contains_reserved_tags(const std::string& gcode, unsigned i } GCodeProcessor::GCodeProcessor() +#if ENABLE_FIX_PREVIEW_OPTIONS_Z +: m_options_z_corrector(m_result) +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z { reset(); m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n"; @@ -951,10 +1005,9 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } // replace missing values with default - std::string default_color = "#FF8000"; for (size_t i = 0; i < m_result.extruder_colors.size(); ++i) { if (m_result.extruder_colors[i].empty()) - m_result.extruder_colors[i] = default_color; + m_result.extruder_colors[i] = "#FF8000"; } m_extruder_colors.resize(m_result.extruder_colors.size()); @@ -1139,7 +1192,6 @@ void GCodeProcessor::reset() m_cp_color.reset(); m_producer = EProducer::Unknown; - m_producers_enabled = false; m_time_processor.reset(); m_used_filaments.reset(); @@ -1152,6 +1204,10 @@ void GCodeProcessor::reset() m_last_default_color_id = 0; #endif // ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + m_options_z_corrector.reset(); +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z + #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_mm3_per_mm_compare.reset(); m_height_compare.reset(); @@ -1159,27 +1215,26 @@ void GCodeProcessor::reset() #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } -void GCodeProcessor::process_file(const std::string& filename, bool apply_postprocess, std::function cancel_callback) +void GCodeProcessor::process_file(const std::string& filename, std::function cancel_callback) { - auto last_cancel_callback_time = std::chrono::high_resolution_clock::now(); - CNumericLocalesSetter locales_setter; #if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); + m_start_time = std::chrono::high_resolution_clock::now(); #endif // ENABLE_GCODE_VIEWER_STATISTICS // pre-processing // parse the gcode file to detect its producer - if (m_producers_enabled) { + { m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { const std::string_view cmd = line.cmd(); - if (cmd.length() == 0) { + if (cmd.empty()) { const std::string_view comment = line.comment(); if (comment.length() > 1 && detect_producer(comment)) m_parser.quit_parsing(); } }); + m_parser.reset(); // if the gcode was produced by PrusaSlicer, // extract the config from it @@ -1201,18 +1256,45 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr m_result.id = ++s_result_id; // 1st move must be a dummy move m_result.moves.emplace_back(MoveVertex()); - m_parser.parse_file(filename, [this, cancel_callback, &last_cancel_callback_time](GCodeReader& reader, const GCodeReader::GCodeLine& line) { - if (cancel_callback != nullptr) { - // call the cancel callback every 100 ms - auto curr_time = std::chrono::high_resolution_clock::now(); - if (std::chrono::duration_cast(curr_time - last_cancel_callback_time).count() > 100) { + size_t parse_line_callback_cntr = 10000; + m_parser.parse_file(filename, [this, cancel_callback, &parse_line_callback_cntr](GCodeReader& reader, const GCodeReader::GCodeLine& line) { + if (-- parse_line_callback_cntr == 0) { + // Don't call the cancel_callback() too often, do it every at every 10000'th line. + parse_line_callback_cntr = 10000; + if (cancel_callback) cancel_callback(); - last_cancel_callback_time = curr_time; - } } - process_gcode_line(line); - }); + this->process_gcode_line(line, true); + }); + this->finalize(); +} + +void GCodeProcessor::initialize(const std::string& filename) +{ + assert(is_decimal_separator_point()); + +#if ENABLE_GCODE_VIEWER_STATISTICS + m_start_time = std::chrono::high_resolution_clock::now(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + // process gcode + m_result.filename = filename; + m_result.id = ++s_result_id; + // 1st move must be a dummy move + m_result.moves.emplace_back(MoveVertex()); +} + +void GCodeProcessor::process_buffer(const std::string &buffer) +{ + //FIXME maybe cache GCodeLine gline to be over multiple parse_buffer() invocations. + m_parser.parse_buffer(buffer, [this](GCodeReader&, const GCodeReader::GCodeLine& line) { + this->process_gcode_line(line, false); + }); +} + +void GCodeProcessor::finalize() +{ // update width/height of wipe moves for (MoveVertex& move : m_result.moves) { if (move.type == EMoveType::Wipe) { @@ -1234,10 +1316,6 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr update_estimated_times_stats(); - // post-process to add M73 lines into the gcode - if (apply_postprocess) - m_time_processor.post_process(filename, m_result.moves); - #if ENABLE_GCODE_VIEWER_DATA_CHECKING std::cout << "\n"; m_mm3_per_mm_compare.output(); @@ -1245,8 +1323,9 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr m_width_compare.output(); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + m_time_processor.post_process(m_result.filename, m_result.moves, m_result.lines_ends); #if ENABLE_GCODE_VIEWER_STATISTICS - m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); + m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS } @@ -1340,7 +1419,7 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) if (pos != cmt.npos) { std::string data_str = cmt.substr(pos + 1); std::vector values_str; - boost::split(values_str, data_str, boost::is_any_of("|"), boost::token_compress_on); + boost::split(values_str, data_str, boost::is_any_of("|,"), boost::token_compress_on); for (const std::string& s : values_str) { out.emplace_back(static_cast(string_to_double_decimal_point(s))); } @@ -1364,10 +1443,16 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) m_result.filament_densities.clear(); extract_floats(comment, "filamentDensities", m_result.filament_densities); } + else if (comment.find("extruderDiameter") != comment.npos) { + std::vector extruder_diameters; + extract_floats(comment, "extruderDiameter", extruder_diameters); + m_result.extruders_count = extruder_diameters.size(); + } } }); - m_result.extruders_count = std::max(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size())); + if (m_result.extruders_count == 0) + m_result.extruders_count = std::max(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size())); if (bed_size.is_defined()) { m_result.bed_shape = { @@ -1379,7 +1464,7 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) } } -void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) +void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled) { /* std::cout << line.raw() << std::endl; */ @@ -1391,61 +1476,170 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) const std::string_view cmd = line.cmd(); if (cmd.length() > 1) { // process command lines - switch (::toupper(cmd[0])) + switch (cmd[0]) { + case 'g': case 'G': - { - switch (::atoi(&cmd[1])) - { - case 0: { process_G0(line); break; } // Move - case 1: { process_G1(line); break; } // Move - case 10: { process_G10(line); break; } // Retract - case 11: { process_G11(line); break; } // Unretract - case 20: { process_G20(line); break; } // Set Units to Inches - case 21: { process_G21(line); break; } // Set Units to Millimeters - case 22: { process_G22(line); break; } // Firmware controlled retract - case 23: { process_G23(line); break; } // Firmware controlled unretract - case 28: { process_G28(line); break; } // Move to origin - case 90: { process_G90(line); break; } // Set to Absolute Positioning - case 91: { process_G91(line); break; } // Set to Relative Positioning - case 92: { process_G92(line); break; } // Set Position - default: { break; } + switch (cmd.size()) { + case 2: + switch (cmd[1]) { + case '0': { process_G0(line); break; } // Move + case '1': { process_G1(line); break; } // Move + default: break; } break; + case 3: + switch (cmd[1]) { + case '1': + switch (cmd[2]) { + case '0': { process_G10(line); break; } // Retract + case '1': { process_G11(line); break; } // Unretract + default: break; + } + break; + case '2': + switch (cmd[2]) { + case '0': { process_G20(line); break; } // Set Units to Inches + case '1': { process_G21(line); break; } // Set Units to Millimeters + case '2': { process_G22(line); break; } // Firmware controlled retract + case '3': { process_G23(line); break; } // Firmware controlled unretract + case '8': { process_G28(line); break; } // Move to origin + default: break; + } + break; + case '9': + switch (cmd[2]) { + case '0': { process_G90(line); break; } // Set to Absolute Positioning + case '1': { process_G91(line); break; } // Set to Relative Positioning + case '2': { process_G92(line); break; } // Set Position + default: break; + } + break; + } + break; + default: + break; } + break; + case 'm': case 'M': - { - switch (::atoi(&cmd[1])) - { - case 1: { process_M1(line); break; } // Sleep or Conditional stop - case 82: { process_M82(line); break; } // Set extruder to absolute mode - case 83: { process_M83(line); break; } // Set extruder to relative mode - case 104: { process_M104(line); break; } // Set extruder temperature - case 106: { process_M106(line); break; } // Set fan speed - case 107: { process_M107(line); break; } // Disable fan - case 108: { process_M108(line); break; } // Set tool (Sailfish) - case 109: { process_M109(line); break; } // Set extruder temperature and wait - case 132: { process_M132(line); break; } // Recall stored home offsets - case 135: { process_M135(line); break; } // Set tool (MakerWare) - case 201: { process_M201(line); break; } // Set max printing acceleration - case 203: { process_M203(line); break; } // Set maximum feedrate - case 204: { process_M204(line); break; } // Set default acceleration - case 205: { process_M205(line); break; } // Advanced settings - case 221: { process_M221(line); break; } // Set extrude factor override percentage - case 401: { process_M401(line); break; } // Repetier: Store x, y and z position - case 402: { process_M402(line); break; } // Repetier: Go to stored position - case 566: { process_M566(line); break; } // Set allowable instantaneous speed change - case 702: { process_M702(line); break; } // Unload the current filament into the MK3 MMU2 unit at the end of print. - default: { break; } + switch (cmd.size()) { + case 2: + switch (cmd[1]) { + case '1': { process_M1(line); break; } // Sleep or Conditional stop + default: break; } break; - } - case 'T': - { - process_T(line); // Select Tool + case 3: + switch (cmd[1]) { + case '8': + switch (cmd[2]) { + case '2': { process_M82(line); break; } // Set extruder to absolute mode + case '3': { process_M83(line); break; } // Set extruder to relative mode + default: break; + } + break; + default: + break; + } + break; + case 4: + switch (cmd[1]) { + case '1': + switch (cmd[2]) { + case '0': + switch (cmd[3]) { + case '4': { process_M104(line); break; } // Set extruder temperature + case '6': { process_M106(line); break; } // Set fan speed + case '7': { process_M107(line); break; } // Disable fan + case '8': { process_M108(line); break; } // Set tool (Sailfish) + case '9': { process_M109(line); break; } // Set extruder temperature and wait + default: break; + } + break; + case '3': + switch (cmd[3]) { + case '2': { process_M132(line); break; } // Recall stored home offsets + case '5': { process_M135(line); break; } // Set tool (MakerWare) + default: break; + } + break; + default: + break; + } + break; + case '2': + switch (cmd[2]) { + case '0': + switch (cmd[3]) { + case '1': { process_M201(line); break; } // Set max printing acceleration + case '3': { process_M203(line); break; } // Set maximum feedrate + case '4': { process_M204(line); break; } // Set default acceleration + case '5': { process_M205(line); break; } // Advanced settings + default: break; + } + break; + case '2': + switch (cmd[3]) { + case '1': { process_M221(line); break; } // Set extrude factor override percentage + default: break; + } + break; + default: + break; + } + break; + case '4': + switch (cmd[2]) { + case '0': + switch (cmd[3]) { + case '1': { process_M401(line); break; } // Repetier: Store x, y and z position + case '2': { process_M402(line); break; } // Repetier: Go to stored position + default: break; + } + break; + default: + break; + } + break; + case '5': + switch (cmd[2]) { + case '6': + switch (cmd[3]) { + case '6': { process_M566(line); break; } // Set allowable instantaneous speed change + default: break; + } + break; + default: + break; + } + break; + case '7': + switch (cmd[2]) { + case '0': + switch (cmd[3]) { + case '2': { process_M702(line); break; } // Unload the current filament into the MK3 MMU2 unit at the end of print. + default: break; + } + break; + default: + break; + } + break; + default: + break; + } + break; + default: break; } - default: { break; } + break; + case 't': + case 'T': + process_T(line); // Select Tool + break; + default: + break; } } else { @@ -1453,7 +1647,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line) if (comment.length() > 2 && comment.front() == ';') // Process tags embedded into comments. Tag comments always start at the start of a line // with a comment and continue with a tag without any whitespace separator. - process_tags(comment.substr(1)); + process_tags(comment.substr(1), producers_enabled); } } @@ -1502,10 +1696,10 @@ template } } -void GCodeProcessor::process_tags(const std::string_view comment) +void GCodeProcessor::process_tags(const std::string_view comment, bool producers_enabled) { // producers tags - if (m_producers_enabled && process_producers_tags(comment)) + if (producers_enabled && process_producers_tags(comment)) return; // extrusion role tag @@ -1529,7 +1723,7 @@ void GCodeProcessor::process_tags(const std::string_view comment) return; } - if (!m_producers_enabled || m_producer == EProducer::PrusaSlicer) { + if (!producers_enabled || m_producer == EProducer::PrusaSlicer) { // height tag if (boost::starts_with(comment, reserved_tag(ETags::Height))) { if (!parse_number(comment.substr(reserved_tag(ETags::Height).size()), m_forced_height)) @@ -1619,6 +1813,9 @@ void GCodeProcessor::process_tags(const std::string_view comment) #if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::ColorChange, extruder_id + 1, color, "" }; m_result.custom_gcode_per_print_z.emplace_back(item); +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + m_options_z_corrector.set(); +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z process_custom_gcode_time(CustomGCode::ColorChange); process_filaments(CustomGCode::ColorChange); #endif // ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER @@ -1638,6 +1835,9 @@ void GCodeProcessor::process_tags(const std::string_view comment) #if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::PausePrint, m_extruder_id + 1, "", "" }; m_result.custom_gcode_per_print_z.emplace_back(item); +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + m_options_z_corrector.set(); +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z #endif // ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER process_custom_gcode_time(CustomGCode::PausePrint); return; @@ -1649,6 +1849,9 @@ void GCodeProcessor::process_tags(const std::string_view comment) #if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER CustomGCode::Item item = { static_cast(m_end_position[2]), CustomGCode::Custom, m_extruder_id + 1, "", "" }; m_result.custom_gcode_per_print_z.emplace_back(item); +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + m_options_z_corrector.set(); +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z #endif // ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER return; } @@ -2210,9 +2413,6 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) return; EMoveType type = move_type(delta_pos); - if (type == EMoveType::Extrude && m_end_position[Z] == 0.0f) - type = EMoveType::Travel; - if (type == EMoveType::Extrude) { float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; @@ -2232,10 +2432,23 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) else { if (m_end_position[Z] > m_extruded_last_z + EPSILON) { m_height = m_end_position[Z] - m_extruded_last_z; +#if !ENABLE_FIX_PREVIEW_OPTIONS_Z m_extruded_last_z = m_end_position[Z]; +#endif // !ENABLE_FIX_PREVIEW_OPTIONS_Z } } + if (m_height == 0.0f) + m_height = DEFAULT_TOOLPATH_HEIGHT; + + if (m_end_position[Z] == 0.0f) + m_end_position[Z] = m_height; + +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + m_extruded_last_z = m_end_position[Z]; + m_options_z_corrector.update(m_height); +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z + #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_height_compare.update(m_height, m_extrusion_role); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -2252,17 +2465,17 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // cross section: rectangle + 2 semicircles m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; + if (m_width == 0.0f) + m_width = DEFAULT_TOOLPATH_WIDTH; + // clamp width to avoid artifacts which may arise from wrong values of m_height - m_width = std::min(m_width, std::max(1.0f, 4.0f * m_height)); + m_width = std::min(m_width, std::max(2.0f, 4.0f * m_height)); #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_width_compare.update(m_width, m_extrusion_role); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } - if (type == EMoveType::Extrude && (m_width == 0.0f || m_height == 0.0f)) - type = EMoveType::Travel; - // time estimate section auto move_length = [](const AxisCoords& delta_pos) { float sq_xyz_length = sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]); @@ -2916,7 +3129,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) m_line_id + 1 : ((type == EMoveType::Seam) ? m_last_line_id : m_line_id); - MoveVertex vertex = { + m_result.moves.push_back({ m_last_line_id, type, m_extrusion_role, @@ -2931,8 +3144,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) m_fan_speed, m_extruder_temps[m_extruder_id], static_cast(m_result.moves.size()) - }; - m_result.moves.emplace_back(vertex); + }); // stores stop time placeholders for later use if (type == EMoveType::Color_change || type == EMoveType::Pause_Print) { diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 4fcdd8df39..040f7432ae 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -306,7 +306,7 @@ namespace Slic3r { // post process the file with the given filename to add remaining time lines M73 // and updates moves' gcode ids accordingly - void post_process(const std::string& filename, std::vector& moves); + void post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends); }; struct UsedFilaments // filaments per ColorChange @@ -350,6 +350,8 @@ namespace Slic3r { std::string filename; unsigned int id; std::vector moves; + // Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code. + std::vector lines_ends; Pointfs bed_shape; SettingsIds settings_ids; size_t extruders_count; @@ -388,6 +390,45 @@ namespace Slic3r { bool has_first_vertex() const { return m_first_vertex.has_value(); } }; +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + // Helper class used to fix the z for color change, pause print and + // custom gcode markes + class OptionsZCorrector + { + Result& m_result; + std::optional m_move_id; + std::optional m_custom_gcode_per_print_z_id; + + public: + explicit OptionsZCorrector(Result& result) : m_result(result) { + } + + void set() { + m_move_id = m_result.moves.size() - 1; + m_custom_gcode_per_print_z_id = m_result.custom_gcode_per_print_z.size() - 1; + } + + void update(float height) { + if (!m_move_id.has_value() || !m_custom_gcode_per_print_z_id.has_value()) + return; + + const Vec3f position = m_result.moves.back().position; + + MoveVertex& move = m_result.moves.emplace_back(m_result.moves[*m_move_id]); + move.position = position; + move.height = height; + m_result.moves.erase(m_result.moves.begin() + *m_move_id); + m_result.custom_gcode_per_print_z[*m_custom_gcode_per_print_z_id].print_z = position.z(); + reset(); + } + + void reset() { + m_move_id.reset(); + m_custom_gcode_per_print_z_id.reset(); + } + }; +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z + #if ENABLE_GCODE_VIEWER_DATA_CHECKING struct DataChecker { @@ -492,9 +533,15 @@ namespace Slic3r { CpColor m_cp_color; bool m_use_volumetric_e; SeamsDetector m_seams_detector; +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + OptionsZCorrector m_options_z_corrector; +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z #if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER size_t m_last_default_color_id; #endif // ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER +#if ENABLE_GCODE_VIEWER_STATISTICS + std::chrono::time_point m_start_time; +#endif // ENABLE_GCODE_VIEWER_STATISTICS enum class EProducer { @@ -511,7 +558,6 @@ namespace Slic3r { static const std::vector> Producers; EProducer m_producer; - bool m_producers_enabled; TimeProcessor m_time_processor; UsedFilaments m_used_filaments; @@ -534,7 +580,6 @@ namespace Slic3r { return m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled; } void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; } - void enable_producers(bool enabled) { m_producers_enabled = enabled; } void reset(); const Result& get_result() const { return m_result; } @@ -542,7 +587,12 @@ namespace Slic3r { // Process the gcode contained in the file with the given filename // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). - void process_file(const std::string& filename, bool apply_postprocess, std::function cancel_callback = nullptr); + void process_file(const std::string& filename, std::function cancel_callback = nullptr); + + // Streaming interface, for processing G-codes just generated by PrusaSlicer in a pipelined fashion. + void initialize(const std::string& filename); + void process_buffer(const std::string& buffer); + void finalize(); float get_time(PrintEstimatedStatistics::ETimeMode mode) const; std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; @@ -555,10 +605,10 @@ namespace Slic3r { private: void apply_config(const DynamicPrintConfig& config); void apply_config_simplify3d(const std::string& filename); - void process_gcode_line(const GCodeReader::GCodeLine& line); + void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled); // Process tags embedded into comments - void process_tags(const std::string_view comment); + void process_tags(const std::string_view comment, bool producers_enabled); bool process_producers_tags(const std::string_view comment); bool process_prusaslicer_tags(const std::string_view comment); bool process_cura_tags(const std::string_view comment); diff --git a/src/libslic3r/GCode/SpiralVase.cpp b/src/libslic3r/GCode/SpiralVase.cpp index acb6ad0348..c3caee2dc4 100644 --- a/src/libslic3r/GCode/SpiralVase.cpp +++ b/src/libslic3r/GCode/SpiralVase.cpp @@ -54,7 +54,7 @@ std::string SpiralVase::process_layer(const std::string &gcode) // For absolute extruder distances it will be switched off. // Tapering the absolute extruder distances requires to process every extrusion value after the first transition // layer. - bool transition = m_transition_layer && m_config->use_relative_e_distances.value; + bool transition = m_transition_layer && m_config.use_relative_e_distances.value; float layer_height_factor = layer_height / total_layer_length; float len = 0.f; m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len] diff --git a/src/libslic3r/GCode/SpiralVase.hpp b/src/libslic3r/GCode/SpiralVase.hpp index 5353901fe6..fb461c2015 100644 --- a/src/libslic3r/GCode/SpiralVase.hpp +++ b/src/libslic3r/GCode/SpiralVase.hpp @@ -8,10 +8,10 @@ namespace Slic3r { class SpiralVase { public: - SpiralVase(const PrintConfig &config) : m_config(&config) + SpiralVase(const PrintConfig &config) : m_config(config) { - m_reader.z() = (float)m_config->z_offset; - m_reader.apply_config(*m_config); + m_reader.z() = (float)m_config.z_offset; + m_reader.apply_config(m_config); }; void enable(bool en) { @@ -22,7 +22,7 @@ public: std::string process_layer(const std::string &gcode); private: - const PrintConfig *m_config; + const PrintConfig &m_config; GCodeReader m_reader; bool m_enabled = false; @@ -32,4 +32,4 @@ private: } -#endif +#endif // slic3r_SpiralVase_hpp_ diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index 7dc3c9a8ac..61ab10f223 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -9,6 +9,7 @@ #include "LocalesUtils.hpp" #include +#include namespace Slic3r { @@ -32,7 +33,7 @@ void GCodeReader::apply_config(const DynamicPrintConfig &config) m_extrusion_axis = get_extrusion_axis_char(m_config); } -const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline, std::pair &command) +const char* GCodeReader::parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair &command) { PROFILE_FUNC(); @@ -70,9 +71,9 @@ const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline, } if (axis != NUM_AXES_WITH_UNKNOWN) { // Try to parse the numeric value. - char *pend = nullptr; - double v = strtod(++ c, &pend); - if (pend != nullptr && is_end_of_word(*pend)) { + double v; + auto [pend, ec] = fast_float::from_chars(++ c, end, v); + if (pend != c && is_end_of_word(*pend)) { // The axis value has been parsed correctly. if (axis != UNKNOWN_AXIS) gline.m_axis[int(axis)] = float(v); @@ -125,13 +126,42 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair buffer(65536 * 10, 0); std::string line; m_parsing = true; - while (m_parsing && std::getline(f, line)) - this->parse_line(line, callback); + GCodeLine gline; + while (m_parsing && ! f.eof()) { + f.read(buffer.data(), buffer.size()); + if (! f.eof() && ! f.good()) + // Reading the input file failed. + return false; + auto it = buffer.begin(); + auto it_bufend = buffer.begin() + f.gcount(); + while (it != it_bufend) { + bool eol = false; + auto it_end = it; + for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ; + eol |= f.eof() && it_end == it_bufend; + if (eol) { + gline.reset(); + if (line.empty()) + this->parse_line(&(*it), &(*it_end), gline, callback); + else { + line.insert(line.end(), it, it_end); + this->parse_line(line.c_str(), line.c_str() + line.size(), gline, callback); + line.clear(); + } + } else + line.insert(line.end(), it, it_end); + // Skip all the empty lines. + for (it = it_end; it != it_bufend && (*it == '\r' || *it == '\n'); ++ it) ; + } + } + return true; } bool GCodeReader::GCodeLine::has(char axis) const diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index 6b58608e61..15376c0fc6 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -44,11 +44,7 @@ public: float y = this->has(Y) ? (this->y() - reader.y()) : 0; return sqrt(x*x + y*y); } - bool cmd_is(const char *cmd_test) const { - const char *cmd = GCodeReader::skip_whitespaces(m_raw.c_str()); - size_t len = strlen(cmd_test); - return strncmp(cmd, cmd_test, len) == 0 && GCodeReader::is_end_of_word(cmd[len]); - } + bool cmd_is(const char *cmd_test) const { return cmd_is(m_raw, cmd_test); } bool extruding(const GCodeReader &reader) const { return this->cmd_is("G1") && this->dist_E(reader) > 0; } bool retracting(const GCodeReader &reader) const { return this->cmd_is("G1") && this->dist_E(reader) < 0; } bool travel() const { return this->cmd_is("G1") && ! this->has(E); } @@ -66,6 +62,12 @@ public: float e() const { return m_axis[E]; } float f() const { return m_axis[F]; } + static bool cmd_is(const std::string &gcode_line, const char *cmd_test) { + const char *cmd = GCodeReader::skip_whitespaces(gcode_line.c_str()); + size_t len = strlen(cmd_test); + return strncmp(cmd, cmd_test, len) == 0 && GCodeReader::is_end_of_word(cmd[len]); + } + private: std::string m_raw; float m_axis[NUM_AXES]; @@ -75,7 +77,8 @@ public: typedef std::function callback_t; - GCodeReader() : m_verbose(false), m_extrusion_axis('E') { memset(m_position, 0, sizeof(m_position)); } + GCodeReader() : m_verbose(false), m_extrusion_axis('E') { this->reset(); } + void reset() { memset(m_position, 0, sizeof(m_position)); } void apply_config(const GCodeConfig &config); void apply_config(const DynamicPrintConfig &config); @@ -83,11 +86,12 @@ public: void parse_buffer(const std::string &buffer, Callback callback) { const char *ptr = buffer.c_str(); + const char *end = ptr + buffer.size(); GCodeLine gline; m_parsing = true; while (m_parsing && *ptr != 0) { gline.reset(); - ptr = this->parse_line(ptr, gline, callback); + ptr = this->parse_line(ptr, end, gline, callback); } } @@ -95,20 +99,21 @@ public: { this->parse_buffer(buffer, [](GCodeReader&, const GCodeReader::GCodeLine&){}); } template - const char* parse_line(const char *ptr, GCodeLine &gline, Callback &callback) + const char* parse_line(const char *ptr, const char *end, GCodeLine &gline, Callback &callback) { std::pair cmd; - const char *end = parse_line_internal(ptr, gline, cmd); + const char *line_end = parse_line_internal(ptr, end, gline, cmd); callback(*this, gline); update_coordinates(gline, cmd); - return end; + return line_end; } template void parse_line(const std::string &line, Callback callback) - { GCodeLine gline; this->parse_line(line.c_str(), gline, callback); } + { GCodeLine gline; this->parse_line(line.c_str(), line.c_str() + line.size(), gline, callback); } - void parse_file(const std::string &file, callback_t callback); + // Returns false if reading the file failed. + bool parse_file(const std::string &file, callback_t callback); void quit_parsing() { m_parsing = false; } float& x() { return m_position[X]; } @@ -127,7 +132,7 @@ public: // void set_extrusion_axis(char axis) { m_extrusion_axis = axis; } private: - const char* parse_line_internal(const char *ptr, GCodeLine &gline, std::pair &command); + const char* parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair &command); void update_coordinates(GCodeLine &gline, std::pair &command); static bool is_whitespace(char c) { return c == ' ' || c == '\t'; } diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 24549fd895..eb45e95c1f 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -1,17 +1,25 @@ #include "GCodeWriter.hpp" #include "CustomGCode.hpp" #include +#include #include #include #include #include +#ifdef __APPLE__ + #include +#endif + +#define XYZF_EXPORT_DIGITS 3 +#define E_EXPORT_DIGITS 5 + #define FLAVOR_IS(val) this->config.gcode_flavor == val #define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val #define COMMENT(comment) if (this->config.gcode_comments && !comment.empty()) gcode << " ; " << comment; #define PRECISION(val, precision) std::fixed << std::setprecision(precision) << (val) -#define XYZF_NUM(val) PRECISION(val, 3) -#define E_NUM(val) PRECISION(val, 5) +#define XYZF_NUM(val) PRECISION(val, XYZF_EXPORT_DIGITS) +#define E_NUM(val) PRECISION(val, E_EXPORT_DIGITS) namespace Slic3r { @@ -152,41 +160,6 @@ std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait return gcode.str(); } -std::string GCodeWriter::set_fan(unsigned int speed, bool dont_save) -{ - std::ostringstream gcode; - if (m_last_fan_speed != speed || dont_save) { - if (!dont_save) m_last_fan_speed = speed; - - if (speed == 0) { - if (FLAVOR_IS(gcfTeacup)) { - gcode << "M106 S0"; - } else if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { - gcode << "M127"; - } else { - gcode << "M107"; - } - if (this->config.gcode_comments) gcode << " ; disable fan"; - gcode << "\n"; - } else { - if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { - gcode << "M126"; - } else { - gcode << "M106 "; - if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { - gcode << "P"; - } else { - gcode << "S"; - } - gcode << (255.0 * speed / 100.0); - } - if (this->config.gcode_comments) gcode << " ; enable fan"; - gcode << "\n"; - } - } - return gcode.str(); -} - std::string GCodeWriter::set_acceleration(unsigned int acceleration) { // Clamp the acceleration to the allowed maximum. @@ -288,16 +261,119 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id) return gcode.str(); } +class G1Writer { +private: + static constexpr const size_t buflen = 256; + char buf[buflen]; + char *buf_end; + std::to_chars_result ptr_err; + +public: + G1Writer() { + this->buf[0] = 'G'; + this->buf[1] = '1'; + this->buf_end = this->buf + buflen; + this->ptr_err.ptr = this->buf + 2; + } + + void emit_axis(const char axis, const double v, size_t digits) { + assert(digits <= 6); + static constexpr const std::array pow_10{1, 10, 100, 1000, 10000, 100000, 1000000}; + *ptr_err.ptr++ = ' '; *ptr_err.ptr++ = axis; + + char *base_ptr = this->ptr_err.ptr; + auto v_int = int64_t(std::round(v * pow_10[digits])); + // Older stdlib on macOS doesn't support std::from_chars at all, so it is used boost::spirit::karma::generate instead of it. + // That is a little bit slower than std::to_chars but not much. +#ifdef __APPLE__ + boost::spirit::karma::generate(this->ptr_err.ptr, boost::spirit::karma::int_generator(), v_int); +#else + // this->buf_end minus 1 because we need space for adding the extra decimal point. + this->ptr_err = std::to_chars(this->ptr_err.ptr, this->buf_end - 1, v_int); +#endif + size_t writen_digits = (this->ptr_err.ptr - base_ptr) - (v_int < 0 ? 1 : 0); + if (writen_digits < digits) { + // Number is smaller than 10^digits, so that we will pad it with zeros. + size_t remaining_digits = digits - writen_digits; + // Move all newly inserted chars by remaining_digits to allocate space for padding with zeros. + for (char *from_ptr = this->ptr_err.ptr - 1, *to_ptr = from_ptr + remaining_digits; from_ptr >= this->ptr_err.ptr - writen_digits; --to_ptr, --from_ptr) + *to_ptr = *from_ptr; + + memset(this->ptr_err.ptr - writen_digits, '0', remaining_digits); + this->ptr_err.ptr += remaining_digits; + } + + // Move all newly inserted chars by one to allocate space for a decimal point. + for (char *to_ptr = this->ptr_err.ptr, *from_ptr = to_ptr - 1; from_ptr >= this->ptr_err.ptr - digits; --to_ptr, --from_ptr) + *to_ptr = *from_ptr; + + *(this->ptr_err.ptr - digits) = '.'; + for (size_t i = 0; i < digits; ++i) { + if (*this->ptr_err.ptr != '0') + break; + this->ptr_err.ptr--; + } + if (*this->ptr_err.ptr == '.') + this->ptr_err.ptr--; + if ((this->ptr_err.ptr + 1) == base_ptr || *this->ptr_err.ptr == '-') + *(++this->ptr_err.ptr) = '0'; + this->ptr_err.ptr++; + } + + void emit_xy(const Vec2d &point) { + this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS); + this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS); + } + + void emit_xyz(const Vec3d &point) { + this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS); + this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS); + this->emit_z(point.z()); + } + + void emit_z(const double z) { + this->emit_axis('Z', z, XYZF_EXPORT_DIGITS); + } + + void emit_e(const std::string &axis, double v) { + if (! axis.empty()) { + // not gcfNoExtrusion + this->emit_axis(axis[0], v, E_EXPORT_DIGITS); + } + } + + void emit_f(double speed) { + this->emit_axis('F', speed, XYZF_EXPORT_DIGITS); + } + + void emit_string(const std::string &s) { + strncpy(ptr_err.ptr, s.c_str(), s.size()); + ptr_err.ptr += s.size(); + } + + void emit_comment(bool allow_comments, const std::string &comment) { + if (allow_comments && ! comment.empty()) { + *ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' '; + this->emit_string(comment); + } + } + + std::string string() { + *ptr_err.ptr ++ = '\n'; + return std::string(this->buf, ptr_err.ptr - buf); + } +}; + std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker) const { assert(F > 0.); assert(F < 100000.); - std::ostringstream gcode; - gcode << "G1 F" << XYZF_NUM(F); - COMMENT(comment); - gcode << cooling_marker; - gcode << "\n"; - return gcode.str(); + + G1Writer w; + w.emit_f(F); + w.emit_comment(this->config.gcode_comments, comment); + w.emit_string(cooling_marker); + return w.string(); } std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment) @@ -305,13 +381,11 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com m_pos(0) = point(0); m_pos(1) = point(1); - std::ostringstream gcode; - gcode << "G1 X" << XYZF_NUM(point(0)) - << " Y" << XYZF_NUM(point(1)) - << " F" << XYZF_NUM(this->config.travel_speed.value * 60.0); - COMMENT(comment); - gcode << "\n"; - return gcode.str(); + G1Writer w; + w.emit_xy(point); + w.emit_f(this->config.travel_speed.value * 60.0); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); } std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment) @@ -340,14 +414,11 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co m_lifted = 0; m_pos = point; - std::ostringstream gcode; - gcode << "G1 X" << XYZF_NUM(point(0)) - << " Y" << XYZF_NUM(point(1)) - << " Z" << XYZF_NUM(point(2)) - << " F" << XYZF_NUM(this->config.travel_speed.value * 60.0); - COMMENT(comment); - gcode << "\n"; - return gcode.str(); + G1Writer w; + w.emit_xyz(point); + w.emit_f(this->config.travel_speed.value * 60.0); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); } std::string GCodeWriter::travel_to_z(double z, const std::string &comment) @@ -377,12 +448,11 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) if (speed == 0.) speed = this->config.travel_speed.value; - std::ostringstream gcode; - gcode << "G1 Z" << XYZF_NUM(z) - << " F" << XYZF_NUM(speed * 60.0); - COMMENT(comment); - gcode << "\n"; - return gcode.str(); + G1Writer w; + w.emit_z(z); + w.emit_f(speed * 60.0); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); } bool GCodeWriter::will_move_z(double z) const @@ -402,16 +472,12 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: m_pos(0) = point(0); m_pos(1) = point(1); m_extruder->extrude(dE); - - std::ostringstream gcode; - gcode << "G1 X" << XYZF_NUM(point(0)) - << " Y" << XYZF_NUM(point(1)); - if (! m_extrusion_axis.empty()) - // not gcfNoExtrusion - gcode << " " << m_extrusion_axis << E_NUM(m_extruder->E()); - COMMENT(comment); - gcode << "\n"; - return gcode.str(); + + G1Writer w; + w.emit_xy(point); + w.emit_e(m_extrusion_axis, m_extruder->E()); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); } std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment) @@ -420,16 +486,11 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std m_lifted = 0; m_extruder->extrude(dE); - std::ostringstream gcode; - gcode << "G1 X" << XYZF_NUM(point(0)) - << " Y" << XYZF_NUM(point(1)) - << " Z" << XYZF_NUM(point(2)); - if (! m_extrusion_axis.empty()) - // not gcfNoExtrusion - gcode << " " << m_extrusion_axis << E_NUM(m_extruder->E()); - COMMENT(comment); - gcode << "\n"; - return gcode.str(); + G1Writer w; + w.emit_xyz(point); + w.emit_e(m_extrusion_axis, m_extruder->E()); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); } std::string GCodeWriter::retract(bool before_wipe) @@ -549,4 +610,43 @@ std::string GCodeWriter::unlift() return gcode; } +std::string GCodeWriter::set_fan(const GCodeFlavor gcode_flavor, bool gcode_comments, unsigned int speed) +{ + std::ostringstream gcode; + if (speed == 0) { + switch (gcode_flavor) { + case gcfTeacup: + gcode << "M106 S0"; break; + case gcfMakerWare: + case gcfSailfish: + gcode << "M127"; break; + default: + gcode << "M107"; break; + } + if (gcode_comments) + gcode << " ; disable fan"; + gcode << "\n"; + } else { + switch (gcode_flavor) { + case gcfMakerWare: + case gcfSailfish: + gcode << "M126"; break; + case gcfMach3: + case gcfMachinekit: + gcode << "M106 P" << 255.0 * speed / 100.0; break; + default: + gcode << "M106 S" << 255.0 * speed / 100.0; break; + } + if (gcode_comments) + gcode << " ; enable fan"; + gcode << "\n"; + } + return gcode.str(); +} + +std::string GCodeWriter::set_fan(unsigned int speed) const +{ + return GCodeWriter::set_fan(this->config.gcode_flavor, this->config.gcode_comments, speed); +} + } diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 2de95ecc5b..dd602ca804 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -18,7 +18,7 @@ public: GCodeWriter() : multiple_extruders(false), m_extrusion_axis("E"), m_extruder(nullptr), m_single_extruder_multi_material(false), - m_last_acceleration(0), m_max_acceleration(0), m_last_fan_speed(0), + m_last_acceleration(0), m_max_acceleration(0), m_last_bed_temperature(0), m_last_bed_temperature_reached(true), m_lifted(0) {} @@ -42,7 +42,6 @@ public: std::string postamble() const; std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1) const; std::string set_bed_temperature(unsigned int temperature, bool wait = false); - std::string set_fan(unsigned int speed, bool dont_save = false); std::string set_acceleration(unsigned int acceleration); std::string reset_e(bool force = false); std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const; @@ -69,6 +68,12 @@ public: std::string unlift(); Vec3d get_position() const { return m_pos; } + // To be called by the CoolingBuffer from another thread. + static std::string set_fan(const GCodeFlavor gcode_flavor, bool gcode_comments, unsigned int speed); + // To be called by the main thread. It always emits the G-code, it does not remember the previous state. + // Keeping the state is left to the CoolingBuffer, which runs asynchronously on another thread. + std::string set_fan(unsigned int speed) const; + private: // Extruders are sorted by their ID, so that binary search is possible. std::vector m_extruders; @@ -79,7 +84,6 @@ private: // Limit for setting the acceleration, to respect the machine limits set for the Marlin firmware. // If set to zero, the limit is not in action. unsigned int m_max_acceleration; - unsigned int m_last_fan_speed; unsigned int m_last_bed_temperature; bool m_last_bed_temperature_reached; double m_lifted; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 6654d3a130..fb6ab56353 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -161,8 +161,10 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig if (!result) throw Slic3r::RuntimeError("Loading of a model file failed."); +#if !ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED if (model.objects.empty()) throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); +#endif // !ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED for (ModelObject *o : model.objects) { @@ -557,6 +559,21 @@ std::string Model::propose_export_file_name_and_path(const std::string &new_exte return boost::filesystem::path(this->propose_export_file_name_and_path()).replace_extension(new_extension).string(); } +bool Model::is_fdm_support_painted() const +{ + return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_fdm_support_painted(); }); +} + +bool Model::is_seam_painted() const +{ + return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_seam_painted(); }); +} + +bool Model::is_mm_painted() const +{ + return std::any_of(this->objects.cbegin(), this->objects.cend(), [](const ModelObject *mo) { return mo->is_mm_painted(); }); +} + ModelObject::~ModelObject() { this->clear_volumes(); @@ -733,6 +750,16 @@ void ModelObject::clear_volumes() this->invalidate_bounding_box(); } +bool ModelObject::is_fdm_support_painted() const +{ + return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_fdm_support_painted(); }); +} + +bool ModelObject::is_seam_painted() const +{ + return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_seam_painted(); }); +} + bool ModelObject::is_mm_painted() const { return std::any_of(this->volumes.cbegin(), this->volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); @@ -1198,9 +1225,9 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr for (ModelVolume *volume : volumes) { const auto volume_matrix = volume->get_matrix(); - volume->supported_facets.clear(); - volume->seam_facets.clear(); - volume->mmu_segmentation_facets.clear(); + volume->supported_facets.reset(); + volume->seam_facets.reset(); + volume->mmu_segmentation_facets.reset(); if (! volume->is_model_part()) { // Modifiers are not cut, but we still need to add the instance transformation @@ -2019,11 +2046,11 @@ bool FacetsAnnotation::set(const TriangleSelector& selector) return false; } -void FacetsAnnotation::clear() +void FacetsAnnotation::reset() { m_data.first.clear(); m_data.second.clear(); - this->reset_timestamp(); + this->touch(); } // Following function takes data from a triangle and encodes it as string diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index b89dd5aa1e..ba3156139a 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -285,6 +285,10 @@ public: void clear_volumes(); void sort_volumes(bool full_sort); bool is_multiparts() const { return volumes.size() > 1; } + // Checks if any of object volume is painted using the fdm support painting gizmo. + bool is_fdm_support_painted() const; + // Checks if any of object volume is painted using the seam painting gizmo. + bool is_seam_painted() const; // Checks if any of object volume is painted using the multi-material painting gizmo. bool is_mm_painted() const; @@ -541,7 +545,10 @@ public: indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const; bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const; bool empty() const { return m_data.first.empty(); } - void clear(); + + // Following method clears the config and increases its timestamp, so the deleted + // state is considered changed from perspective of the undo/redo stack. + void reset(); // Serialize triangle into string, for serialization into 3MF/AMF. std::string get_triangle_as_string(int i) const; @@ -720,6 +727,8 @@ public: this->mmu_segmentation_facets.set_new_unique_id(); } + bool is_fdm_support_painted() const { return !this->supported_facets.empty(); } + bool is_seam_painted() const { return !this->seam_facets.empty(); } bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } protected: @@ -1124,6 +1133,13 @@ public: // 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; + // Checks if any of objects is painted using the fdm support painting gizmo. + bool is_fdm_support_painted() const; + // Checks if any of objects is painted using the seam painting gizmo. + bool is_seam_painted() const; + // Checks if any of objects is painted using the multi-material painting gizmo. + bool is_mm_painted() const; + private: explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); } void assign_new_unique_ids_recursive(); diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index ea7c748a50..1030171e7f 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -105,9 +105,6 @@ protected: // The class tree will have virtual tables and type information. virtual ~ObjectWithTimestamp() = default; - // Resetting timestamp to 1 indicates the object is in its initial (cleared) state. - // To be called by the derived class's clear() method. - void reset_timestamp() { m_timestamp = 1; } // The timestamp uniquely identifies content of the derived class' data, therefore it makes sense to copy the timestamp if the content data was copied. void copy_timestamp(const ObjectWithTimestamp& rhs) { m_timestamp = rhs.m_timestamp; } diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index a6be642994..68c40fc204 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -120,7 +120,8 @@ void Polyline::simplify(double tolerance) this->points = MultiPoint::_douglas_peucker(this->points, tolerance); } -/* This method simplifies all *lines* contained in the supplied area */ +#if 0 +// This method simplifies all *lines* contained in the supplied area template void Polyline::simplify_by_visibility(const T &area) { @@ -141,6 +142,7 @@ void Polyline::simplify_by_visibility(const T &area) } template void Polyline::simplify_by_visibility(const ExPolygon &area); template void Polyline::simplify_by_visibility(const ExPolygonCollection &area); +#endif void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const { diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 51dcf9d36d..758fc38cd5 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -72,7 +72,7 @@ public: void extend_start(double distance); Points equally_spaced_points(double distance) const; void simplify(double tolerance); - template void simplify_by_visibility(const T &area); +// template void simplify_by_visibility(const T &area); void split_at(const Point &point, Polyline* p1, Polyline* p2) const; bool is_straight() const; bool is_closed() const { return this->points.front() == this->points.back(); } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 3883c4980b..f5c8235ed5 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -428,9 +428,9 @@ static std::vector s_Preset_print_options { #endif /* HAS_PRESSURE_EQUALIZER */ "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", - "bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", - "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield", - "min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", + "bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "first_layer_speed_over_raft", "perimeter_acceleration", "infill_acceleration", + "bridge_acceleration", "first_layer_acceleration", "first_layer_acceleration_over_raft", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield", + "min_skirt_length", "brim_width", "brim_separation", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", "raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_closing_radius", "support_material_style", "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers", diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 9ac8864f91..cb9f04e456 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -240,7 +240,6 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward if (! errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative); - // ysToDo : set prefered filament or sla_material (relates to print technology) and force o use of preffered printer model if it was added this->load_selections(config, preferred_selection); return substitutions; @@ -466,20 +465,9 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p // will be selected by the following call of this->update_compatible(PresetSelectCompatibleType::Always). const Preset *initial_printer = printers.find_preset(initial_printer_profile_name); + // If executed due to a Config Wizard update, preferred_printer contains the first newly installed printer, otherwise nullptr. const Preset *preferred_printer = printers.find_system_preset_by_model_and_variant(preferred_selection.printer_model_id, preferred_selection.printer_variant); - printers.select_preset_by_name( - (preferred_printer != nullptr /*&& (initial_printer == nullptr || !initial_printer->is_visible)*/) ? - preferred_printer->name : - initial_printer_profile_name, - true); - - // select preferred filament/sla_material profile if any exists and is visible - if (!preferred_selection.filament.empty()) - if (auto it = filaments.find_preset_internal(preferred_selection.filament); it != filaments.end() && it->is_visible) - initial_filament_profile_name = it->name; - if (!preferred_selection.sla_material.empty()) - if (auto it = sla_materials.find_preset_internal(preferred_selection.sla_material); it != sla_materials.end() && it->is_visible) - initial_sla_material_profile_name = it->name; + printers.select_preset_by_name(preferred_printer ? preferred_printer->name : initial_printer_profile_name, true); // Selects the profile, leaves it to -1 if the initial profile name is empty or if it was not found. prints.select_preset_by_name_strict(initial_print_profile_name); @@ -507,6 +495,21 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p this->update_compatible(PresetSelectCompatibleType::Always); this->update_multi_material_filament_presets(); + if (initial_printer != nullptr && (preferred_printer == nullptr || initial_printer == preferred_printer)) { + // Don't run the following code, as we want to activate default filament / SLA material profiles when installing and selecting a new printer. + // Only run this code if just a filament / SLA material was installed by Config Wizard for an active Printer. + auto printer_technology = printers.get_selected_preset().printer_technology(); + if (printer_technology == ptFFF && ! preferred_selection.filament.empty()) { + if (auto it = filaments.find_preset_internal(preferred_selection.filament); it != filaments.end() && it->is_visible) { + filaments.select_preset_by_name_strict(preferred_selection.filament); + this->filament_presets.front() = filaments.get_selected_preset_name(); + } + } else if (printer_technology == ptSLA && ! preferred_selection.sla_material.empty()) { + if (auto it = sla_materials.find_preset_internal(preferred_selection.sla_material); it != sla_materials.end() && it->is_visible) + sla_materials.select_preset_by_name_strict(preferred_selection.sla_material); + } + } + // Parse the initial physical printer name. std::string initial_physical_printer_name = remove_ini_suffix(config.get("presets", "physical_printer")); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 48737f8302..06052a62f3 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -88,7 +88,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "filament_cost", "filament_spool_weight", "first_layer_acceleration", + "first_layer_acceleration_over_raft", "first_layer_bed_temperature", + "first_layer_speed_over_raft", "gcode_comments", "gcode_label_objects", "infill_acceleration", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f010bad399..2917a9a19d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -505,10 +505,10 @@ void PrintConfigDef::init_fff_params() def->mode = comSimple; def->set_default_value(new ConfigOptionEnum(btOuterOnly)); - def = this->add("brim_offset", coFloat); - def->label = L("Brim offset"); + def = this->add("brim_separation", coFloat); + def->label = L("Brim separation gap"); def->category = L("Skirt and brim"); - def->tooltip = L("The offset of the brim from the printed object. The offset is applied after the elephant foot compensation."); + def->tooltip = L("Offset of brim from the printed object. The offset is applied after the elephant foot compensation."); def->sidetext = L("mm"); def->min = 0; def->mode = comAdvanced; @@ -1152,6 +1152,15 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("first_layer_acceleration_over_raft", coFloat); + def->label = L("First object layer over raft interface"); + def->tooltip = L("This is the acceleration your printer will use for first layer of object above raft interface. Set zero " + "to disable acceleration control for first layer of object above raft interface."); + def->sidetext = L("mm/s²"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("first_layer_bed_temperature", coInts); def->label = L("First layer"); def->full_label = L("First layer bed temperature"); @@ -1194,6 +1203,16 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(30, false)); + def = this->add("first_layer_speed_over_raft", coFloatOrPercent); + def->label = L("Speed of object first layer over raft interface"); + def->tooltip = L("If expressed as absolute value in mm/s, this speed will be applied to all the print moves " + "of the first object layer above raft interface, regardless of their type. If expressed as a percentage " + "(for example: 40%) it will scale the default speeds."); + def->sidetext = L("mm/s or %"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent(30, false)); + def = this->add("first_layer_temperature", coInts); def->label = L("First layer"); def->full_label = L("First layer nozzle temperature"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index b15651ba70..d7409d12ce 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -449,13 +449,15 @@ protected: \ PRINT_CONFIG_CLASS_DEFINE( PrintObjectConfig, - ((ConfigOptionFloat, brim_offset)) + ((ConfigOptionFloat, brim_separation)) ((ConfigOptionEnum, brim_type)) ((ConfigOptionFloat, brim_width)) ((ConfigOptionBool, clip_multipart_objects)) ((ConfigOptionBool, dont_support_bridges)) ((ConfigOptionFloat, elefant_foot_compensation)) ((ConfigOptionFloatOrPercent, extrusion_width)) + ((ConfigOptionFloat, first_layer_acceleration_over_raft)) + ((ConfigOptionFloatOrPercent, first_layer_speed_over_raft)) ((ConfigOptionBool, infill_only_where_needed)) // Force the generation of solid shells between adjacent materials/volumes. ((ConfigOptionBool, interface_shells)) @@ -1064,7 +1066,9 @@ Points get_bed_shape(const SLAPrinterConfig &cfg); class ModelConfig { public: - void clear() { m_data.clear(); m_timestamp = 1; } + // Following method clears the config and increases its timestamp, so the deleted + // state is considered changed from perspective of the undo/redo stack. + void reset() { m_data.clear(); touch(); } void assign_config(const ModelConfig &rhs) { if (m_timestamp != rhs.m_timestamp) { @@ -1076,7 +1080,7 @@ public: if (m_timestamp != rhs.m_timestamp) { m_data = std::move(rhs.m_data); m_timestamp = rhs.m_timestamp; - rhs.clear(); + rhs.reset(); } } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ee09e0f5b7..d9a4f2670a 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -500,7 +500,7 @@ bool PrintObject::invalidate_state_by_config_options( bool invalidated = false; for (const t_config_option_key &opt_key : opt_keys) { if ( opt_key == "brim_width" - || opt_key == "brim_offset" + || opt_key == "brim_separation" || opt_key == "brim_type") { // Brim is printed below supports, support invalidates brim and skirt. steps.emplace_back(posSupportMaterial); @@ -2293,10 +2293,24 @@ void PrintObject::project_and_append_custom_facets( const indexed_triangle_set custom_facets = seam ? mv->seam_facets.get_facets_strict(*mv, type) : mv->supported_facets.get_facets_strict(*mv, type); - if (! custom_facets.indices.empty()) - project_triangles_to_slabs(this->layers(), custom_facets, - (this->trafo_centered() * mv->get_matrix()).cast(), - seam, out); + if (! custom_facets.indices.empty()) { + if (seam) + project_triangles_to_slabs(this->layers(), custom_facets, + (this->trafo_centered() * mv->get_matrix()).cast(), + seam, out); + else { + std::vector projected; + slice_mesh_slabs(custom_facets, zs_from_layers(this->layers()), this->trafo_centered() * mv->get_matrix(), nullptr, &projected, [](){}); + // Merge these projections with the output, layer by layer. + assert(! projected.empty()); + assert(out.empty() || out.size() == projected.size()); + if (out.empty()) + out = std::move(projected); + else + for (size_t i = 0; i < out.size(); ++ i) + append(out[i], std::move(projected[i])); + } + } } } diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index 6efc5f4a9d..7efe15bb24 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -7,6 +7,10 @@ using namespace Slic3r; +#ifndef NDEBUG +// #define EXPENSIVE_DEBUG_CHECKS +#endif // NDEBUG + // only private namespace not neccessary be in .hpp namespace QuadricEdgeCollapse { using Vertices = std::vector; @@ -79,10 +83,13 @@ namespace QuadricEdgeCollapse { init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn); std::optional find_triangle_index1(uint32_t vi, const VertexInfo& v_info, uint32_t ti, const EdgeInfos& e_infos, const Indices& indices); + void reorder_edges(EdgeInfos &e_infos, const VertexInfo &v_info, uint32_t ti0, uint32_t ti1); bool is_flipped(const Vec3f &new_vertex, uint32_t ti0, uint32_t ti1, const VertexInfo& v_info, const TriangleInfos &t_infos, const EdgeInfos &e_infos, const indexed_triangle_set &its); bool degenerate(uint32_t vi, uint32_t ti0, uint32_t ti1, const VertexInfo &v_info, const EdgeInfos &e_infos, const Indices &indices); + bool create_no_volume(uint32_t vi0, uint32_t vi1, uint32_t ti0, uint32_t ti1, + const VertexInfo &v_info0, const VertexInfo &v_info1, const EdgeInfos &e_infos, const Indices &indices); // find edge with smallest error in triangle Vec3d calculate_3errors(const Triangle &t, const Vertices &vertices, const VertexInfos &v_infos); Error calculate_error(uint32_t ti, const Triangle& t,const Vertices &vertices, const VertexInfos& v_infos, unsigned char& min_index); @@ -92,14 +99,25 @@ namespace QuadricEdgeCollapse { const Triangle &t1, CopyEdgeInfos& infos, EdgeInfos &e_infos1); void compact(const VertexInfos &v_infos, const TriangleInfos &t_infos, const EdgeInfos &e_infos, indexed_triangle_set &its); -#ifndef NDEBUG +#ifdef EXPENSIVE_DEBUG_CHECKS void store_surround(const char *obj_filename, size_t triangle_index, int depth, const indexed_triangle_set &its, const VertexInfos &v_infos, const EdgeInfos &e_infos); bool check_neighbors(const indexed_triangle_set &its, const TriangleInfos &t_infos, const VertexInfos &v_infos, const EdgeInfos &e_infos); -#endif /* NDEBUG */ +#endif /* EXPENSIVE_DEBUG_CHECKS */ -} // namespace QuadricEdgeCollapse + // constants --> may be move to config + const uint32_t check_cancel_period = 16; // how many edge to reduce before call throw_on_cancel + const size_t max_triangle_count_for_one_vertex = 50; + // change speed of progress bargraph + const int status_init_size = 10; // in percents + // parts of init size + const int status_normal_size = 25; + const int status_sum_quadric = 25; + const int status_set_offsets = 10; + const int status_calc_errors = 30; + const int status_create_refs = 10; + } // namespace QuadricEdgeCollapse using namespace QuadricEdgeCollapse; @@ -110,10 +128,6 @@ void Slic3r::its_quadric_edge_collapse( std::function throw_on_cancel, std::function status_fn) { - // constants --> may be move to config - const int status_init_size = 10; // in percents - const int check_cancel_period = 16; // how many edge to reduce before call throw_on_cancel - // check input if (triangle_count >= its.indices.size()) return; float maximal_error = (max_error == nullptr)? std::numeric_limits::max() : *max_error; @@ -122,7 +136,8 @@ void Slic3r::its_quadric_edge_collapse( if (status_fn == nullptr) status_fn = [](int) {}; StatusFn init_status_fn = [&](int percent) { - status_fn(std::round((percent * status_init_size) / 100.)); + float n_percent = percent * status_init_size / 100.f; + status_fn(static_cast(std::round(n_percent))); }; TriangleInfos t_infos; // only normals with information about deleted triangle @@ -145,7 +160,6 @@ void Slic3r::its_quadric_edge_collapse( mpq.reserve(its.indices.size()); for (Error &error :errors) mpq.push(error); - const size_t max_triangle_count_for_one_vertex = 50; CopyEdgeInfos ceis; ceis.reserve(max_triangle_count_for_one_vertex); EdgeInfos e_infos_swap; @@ -162,8 +176,9 @@ void Slic3r::its_quadric_edge_collapse( (1. - reduced); status_fn(static_cast(std::round(status))); }; - // modulo for update status - uint32_t status_mod = std::max(uint32_t(16), count_triangle_to_reduce / 100); + // modulo for update status, call each percent only once + uint32_t status_mod = std::max(uint32_t(16), + count_triangle_to_reduce / (100 - status_init_size)); uint32_t iteration_number = 0; float last_collapsed_error = 0.f; @@ -195,14 +210,21 @@ void Slic3r::its_quadric_edge_collapse( q += v_info1.q; Vec3f new_vertex0 = calculate_vertex(vi0, vi1, q, its.vertices); // set of triangle indices that change quadric + uint32_t ti1 = -1; // triangle 1 index auto ti1_opt = (v_info0.count < v_info1.count)? find_triangle_index1(vi1, v_info0, ti0, e_infos, its.indices) : find_triangle_index1(vi0, v_info1, ti0, e_infos, its.indices) ; + if (ti1_opt.has_value()) { + ti1 = *ti1_opt; + reorder_edges(e_infos, v_info0, ti0, ti1); + reorder_edges(e_infos, v_info1, ti0, ti1); + } if (!ti1_opt.has_value() || // edge has only one triangle - degenerate(vi0, ti0, *ti1_opt, v_info1, e_infos, its.indices) || - degenerate(vi1, ti0, *ti1_opt, v_info0, e_infos, its.indices) || - is_flipped(new_vertex0, ti0, *ti1_opt, v_info0, t_infos, e_infos, its) || - is_flipped(new_vertex0, ti0, *ti1_opt, v_info1, t_infos, e_infos, its)) { + degenerate(vi0, ti0, ti1, v_info1, e_infos, its.indices) || + degenerate(vi1, ti0, ti1, v_info0, e_infos, its.indices) || + create_no_volume(vi0, vi1, ti0, ti1, v_info0, v_info1, e_infos, its.indices) || + is_flipped(new_vertex0, ti0, ti1, v_info0, t_infos, e_infos, its) || + is_flipped(new_vertex0, ti0, ti1, v_info1, t_infos, e_infos, its)) { // try other triangle's edge Vec3d errors = calculate_3errors(t0, its.vertices, v_infos); Vec3i ord = (errors[0] < errors[1]) ? @@ -227,29 +249,25 @@ void Slic3r::its_quadric_edge_collapse( mpq.push(e); continue; } - uint32_t ti1 = *ti1_opt; + last_collapsed_error = e.value; changed_triangle_indices.clear(); changed_triangle_indices.reserve(v_info0.count + v_info1.count - 4); // for each vertex0 triangles - uint32_t v_info0_end = v_info0.start + v_info0.count; + uint32_t v_info0_end = v_info0.start + v_info0.count - 2; for (uint32_t di = v_info0.start; di < v_info0_end; ++di) { assert(di < e_infos.size()); uint32_t ti = e_infos[di].t_index; - if (ti == ti0) continue; // ti0 will be deleted - if (ti == ti1) continue; // ti1 will be deleted changed_triangle_indices.emplace_back(ti); } // for each vertex1 triangles - uint32_t v_info1_end = v_info1.start + v_info1.count; + uint32_t v_info1_end = v_info1.start + v_info1.count - 2; for (uint32_t di = v_info1.start; di < v_info1_end; ++di) { assert(di < e_infos.size()); EdgeInfo &e_info = e_infos[di]; uint32_t ti = e_info.t_index; - if (ti == ti0) continue; // ti0 will be deleted - if (ti == ti1) continue; // ti1 will be deleted Triangle &t = its.indices[ti]; t[e_info.edge] = vi0; // change index changed_triangle_indices.emplace_back(ti); @@ -282,7 +300,9 @@ void Slic3r::its_quadric_edge_collapse( t_info1.set_deleted(); // triangle counter decrementation actual_triangle_count-=2; +#ifdef EXPENSIVE_DEBUG_CHECKS assert(check_neighbors(its, t_infos, v_infos, e_infos)); +#endif // EXPENSIVE_DEBUG_CHECKS } // compact triangle @@ -384,13 +404,6 @@ SymMat QuadricEdgeCollapse::create_quadric(const Triangle &t, std::tuple QuadricEdgeCollapse::init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn) { - // change speed of progress bargraph - const int status_normal_size = 25; - const int status_sum_quadric = 25; - const int status_set_offsets = 10; - const int status_calc_errors = 30; - const int status_create_refs = 10; - int status_offset = 0; TriangleInfos t_infos(its.indices.size()); VertexInfos v_infos(its.vertices.size()); @@ -506,6 +519,38 @@ std::optional QuadricEdgeCollapse::find_triangle_index1(uint32_t return {}; } +void QuadricEdgeCollapse::reorder_edges(EdgeInfos & e_infos, + const VertexInfo &v_info, + uint32_t ti0, + uint32_t ti1) +{ + // swap edge info of ti0 and ti1 to end(last one and one before) + size_t v_info_end = v_info.start + v_info.count - 2; + EdgeInfo &e_info_ti0 = e_infos[v_info_end]; + EdgeInfo &e_info_ti1 = e_infos[v_info_end+1]; + bool is_swaped = false; + for (size_t ei = v_info.start; ei < v_info_end; ++ei) { + EdgeInfo &e_info = e_infos[ei]; + if (e_info.t_index == ti0) { + std::swap(e_info, e_info_ti0); + if (is_swaped) return; + if (e_info.t_index == ti1) { + std::swap(e_info, e_info_ti1); + return; + } + is_swaped = true; + } else if (e_info.t_index == ti1) { + std::swap(e_info, e_info_ti1); + if (is_swaped) return; + if (e_info.t_index == ti0) { + std::swap(e_info, e_info_ti0); + return; + } + is_swaped = true; + } + } +} + bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex, uint32_t ti0, uint32_t ti1, @@ -519,12 +564,10 @@ bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex, static const float dot_thr = 0.2f; // Value from simplify mesh cca 80 DEG // for each vertex triangles - size_t v_info_end = v_info.start + v_info.count; + size_t v_info_end = v_info.start + v_info.count-2; for (size_t ei = v_info.start; ei < v_info_end; ++ei) { assert(ei < e_infos.size()); const EdgeInfo &e_info = e_infos[ei]; - if (e_info.t_index == ti0) continue; // ti0 will be deleted - if (e_info.t_index == ti1) continue; // ti1 will be deleted const Triangle &t = its.indices[e_info.t_index]; const Vec3f &normal = t_infos[e_info.t_index].n; const Vec3f &vf = its.vertices[t[(e_info.edge + 1) % 3]]; @@ -554,12 +597,10 @@ bool QuadricEdgeCollapse::degenerate(uint32_t vi, { // check surround triangle do not contain vertex index // protect from creation of triangle with two same vertices inside - size_t v_info_end = v_info.start + v_info.count; + size_t v_info_end = v_info.start + v_info.count - 2; for (size_t ei = v_info.start; ei < v_info_end; ++ei) { assert(ei < e_infos.size()); const EdgeInfo &e_info = e_infos[ei]; - if (e_info.t_index == ti0) continue; // ti0 will be deleted - if (e_info.t_index == ti1) continue; // ti1 will be deleted const Triangle &t = indices[e_info.t_index]; for (size_t i = 0; i < 3; ++i) if (static_cast(t[i]) == vi) return true; @@ -567,6 +608,52 @@ bool QuadricEdgeCollapse::degenerate(uint32_t vi, return false; } +bool QuadricEdgeCollapse::create_no_volume( + uint32_t vi0 , uint32_t vi1, + uint32_t ti0 , uint32_t ti1, + const VertexInfo &v_info0, const VertexInfo &v_info1, + const EdgeInfos & e_infos, const Indices &indices) +{ + // check that triangles around vertex0 doesn't have half edge + // with opposit order in set of triangles around vertex1 + // protect from creation of two triangles with oposit order - no volume space + size_t v_info0_end = v_info0.start + v_info0.count - 2; + size_t v_info1_end = v_info1.start + v_info1.count - 2; + for (size_t ei0 = v_info0.start; ei0 < v_info0_end; ++ei0) { + const EdgeInfo &e_info0 = e_infos[ei0]; + const Triangle &t0 = indices[e_info0.t_index]; + // edge CCW vertex indices are t0vi0, t0vi1 + size_t t0i = 0; + uint32_t t0vi0 = static_cast(t0[t0i]); + if (t0vi0 == vi0) { + ++t0i; + t0vi0 = static_cast(t0[t0i]); + } + ++t0i; + uint32_t t0vi1 = static_cast(t0[t0i]); + if (t0vi1 == vi0) { + ++t0i; + t0vi1 = static_cast(t0[t0i]); + } + for (size_t ei1 = v_info1.start; ei1 < v_info1_end; ++ei1) { + const EdgeInfo &e_info1 = e_infos[ei1]; + const Triangle &t1 = indices[e_info1.t_index]; + size_t t1i = 0; + for (; t1i < 3; ++t1i) if (static_cast(t1[t1i]) == t0vi1) break; + if (t1i >= 3) continue; // without vertex index from triangle 0 + // check if second index is same too + ++t1i; + if (t1i == 3) t1i = 0; // triangle loop(modulo 3) + if (static_cast(t1[t1i]) == vi1) { + ++t1i; + if (t1i == 3) t1i = 0; // triangle loop(modulo 3) + } + if (static_cast(t1[t1i]) == t0vi0) return true; + } + } + return false; +} + Vec3d QuadricEdgeCollapse::calculate_3errors(const Triangle & t, const Vertices & vertices, const VertexInfos &v_infos) @@ -732,7 +819,8 @@ void QuadricEdgeCollapse::compact(const VertexInfos & v_infos, its.indices.erase(its.indices.begin() + ti_new, its.indices.end()); } -#ifndef NDEBUG +#ifdef EXPENSIVE_DEBUG_CHECKS + // store triangle surrounding to file void QuadricEdgeCollapse::store_surround(const char *obj_filename, size_t triangle_index, @@ -842,4 +930,4 @@ bool QuadricEdgeCollapse::check_neighbors(const indexed_triangle_set &its, } return true; } -#endif /* NDEBUG */ +#endif /* EXPENSIVE_DEBUG_CHECKS */ diff --git a/src/libslic3r/SLA/IndexedMesh.cpp b/src/libslic3r/SLA/IndexedMesh.cpp index 887ef1555d..07c4203ab2 100644 --- a/src/libslic3r/SLA/IndexedMesh.cpp +++ b/src/libslic3r/SLA/IndexedMesh.cpp @@ -17,10 +17,18 @@ namespace sla { class IndexedMesh::AABBImpl { private: AABBTreeIndirect::Tree3f m_tree; + double m_triangle_ray_epsilon; public: - void init(const indexed_triangle_set &its) + void init(const indexed_triangle_set &its, bool calculate_epsilon) { + m_triangle_ray_epsilon = 0.000001; + if (calculate_epsilon) { + // Calculate epsilon from average triangle edge length. + double l = its_average_edge_length(its); + if (l > 0) + m_triangle_ray_epsilon = 0.000001 * l * l; + } m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( its.vertices, its.indices); } @@ -31,7 +39,7 @@ public: igl::Hit & hit) { AABBTreeIndirect::intersect_ray_first_hit(its.vertices, its.indices, - m_tree, s, dir, hit); + m_tree, s, dir, hit, m_triangle_ray_epsilon); } void intersect_ray(const indexed_triangle_set &its, @@ -40,7 +48,7 @@ public: std::vector & hits) { AABBTreeIndirect::intersect_ray_all_hits(its.vertices, its.indices, - m_tree, s, dir, hits); + m_tree, s, dir, hits, m_triangle_ray_epsilon); } double squared_distance(const indexed_triangle_set & its, @@ -60,25 +68,25 @@ public: } }; -template void IndexedMesh::init(const M &mesh) +template void IndexedMesh::init(const M &mesh, bool calculate_epsilon) { BoundingBoxf3 bb = bounding_box(mesh); m_ground_level += bb.min(Z); // Build the AABB accelaration tree - m_aabb->init(*m_tm); + m_aabb->init(*m_tm, calculate_epsilon); } -IndexedMesh::IndexedMesh(const indexed_triangle_set& tmesh) +IndexedMesh::IndexedMesh(const indexed_triangle_set& tmesh, bool calculate_epsilon) : m_aabb(new AABBImpl()), m_tm(&tmesh) { - init(tmesh); + init(tmesh, calculate_epsilon); } -IndexedMesh::IndexedMesh(const TriangleMesh &mesh) +IndexedMesh::IndexedMesh(const TriangleMesh &mesh, bool calculate_epsilon) : m_aabb(new AABBImpl()), m_tm(&mesh.its) { - init(mesh); + init(mesh, calculate_epsilon); } IndexedMesh::~IndexedMesh() {} diff --git a/src/libslic3r/SLA/IndexedMesh.hpp b/src/libslic3r/SLA/IndexedMesh.hpp index 25ab75b836..9348a97c9b 100644 --- a/src/libslic3r/SLA/IndexedMesh.hpp +++ b/src/libslic3r/SLA/IndexedMesh.hpp @@ -42,12 +42,14 @@ class IndexedMesh { std::vector m_holes; #endif - template void init(const M &mesh); + template void init(const M &mesh, bool calculate_epsilon); public: - explicit IndexedMesh(const indexed_triangle_set&); - explicit IndexedMesh(const TriangleMesh &mesh); + // calculate_epsilon ... calculate epsilon for triangle-ray intersection from an average triangle edge length. + // If set to false, a default epsilon is used, which works for "reasonable" meshes. + explicit IndexedMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon = false); + explicit IndexedMesh(const TriangleMesh &mesh, bool calculate_epsilon = false); IndexedMesh(const IndexedMesh& other); IndexedMesh& operator=(const IndexedMesh&); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index e2d7bf1995..1b66bcc533 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -37,6 +37,7 @@ #define DEBUG #define _DEBUG #undef NDEBUG + #include "utils.hpp" #include "SVG.hpp" #endif @@ -429,7 +430,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) for (const MyLayer *layer : top_contacts) Slic3r::SVG::export_expolygons( debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer->print_z), - union_ex(layer->polygons, false)); + union_ex(layer->polygons)); #endif /* SLIC3R_DEBUG */ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts"; @@ -447,7 +448,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) for (size_t layer_id = 0; layer_id < object.layers().size(); ++ layer_id) Slic3r::SVG::export_expolygons( debug_out_path("support-areas-%d-%lf.svg", iRun, object.layers()[layer_id]->print_z), - union_ex(layer_support_areas[layer_id], false)); + union_ex(layer_support_areas[layer_id])); #endif /* SLIC3R_DEBUG */ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating intermediate layers - indices"; @@ -466,7 +467,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) for (const MyLayer *layer : top_contacts) Slic3r::SVG::export_expolygons( debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z), - union_ex(layer->polygons, false)); + union_ex(layer->polygons)); #endif BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers"; @@ -478,7 +479,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++ it) Slic3r::SVG::export_expolygons( debug_out_path("support-base-layers-%d-%lf.svg", iRun, (*it)->print_z), - union_ex((*it)->polygons, false)); + union_ex((*it)->polygons)); #endif /* SLIC3R_DEBUG */ BOOST_LOG_TRIVIAL(info) << "Support generator - Trimming top contacts by bottom contacts"; @@ -507,11 +508,11 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) for (const MyLayer *l : interface_layers) Slic3r::SVG::export_expolygons( debug_out_path("support-interface-layers-%d-%lf.svg", iRun, l->print_z), - union_ex(l->polygons, false)); + union_ex(l->polygons)); for (const MyLayer *l : base_interface_layers) Slic3r::SVG::export_expolygons( debug_out_path("support-base-interface-layers-%d-%lf.svg", iRun, l->print_z), - union_ex(l->polygons, false)); + union_ex(l->polygons)); #endif // SLIC3R_DEBUG /* @@ -1308,9 +1309,9 @@ namespace SupportMaterialInternal { #ifdef SLIC3R_DEBUG static int iRun = 0; SVG::export_expolygons(debug_out_path("support-top-contacts-remove-bridges-run%d.svg", iRun ++), - { { { union_ex(offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), false) }, { "unsupported_bridge_edges", "orange", 0.5f } }, - { { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, - { { union_ex(bridges, false) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); + { { { union_ex(offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)) }, { "unsupported_bridge_edges", "orange", 0.5f } }, + { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, + { { union_ex(bridges) }, { "bridges", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ } } @@ -1416,13 +1417,35 @@ static inline std::tuple detect_overhangs( // Generate overhang / contact_polygons for non-raft layers. const Layer &lower_layer = *layer.lower_layer; const bool has_enforcer = ! annotations.enforcers_layers.empty() && ! annotations.enforcers_layers[layer_id].empty(); - float fw = 0; + + // Cache support trimming polygons derived from lower layer polygons, possible merged with "on build plate only" trimming polygons. + auto slices_margin_update = + [&slices_margin, &lower_layer, &lower_layer_polygons, buildplate_only, has_enforcer, &annotations, layer_id] + (float slices_margin_offset, float no_interface_offset) { + if (slices_margin.offset != slices_margin_offset) { + slices_margin.offset = slices_margin_offset; + slices_margin.polygons = (slices_margin_offset == 0.f) ? + lower_layer_polygons : + offset2(lower_layer.lslices, -no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); + if (buildplate_only && !annotations.buildplate_covered[layer_id].empty()) { + if (has_enforcer) + // Make a backup of trimming polygons before enforcing "on build plate only". + slices_margin.all_polygons = slices_margin.polygons; + // Trim the inflated contact surfaces by the top surfaces as well. + slices_margin.polygons = union_(slices_margin.polygons, annotations.buildplate_covered[layer_id]); + } + } + }; + + float fw = 0; + float lower_layer_offset = 0; + float no_interface_offset = 0; for (LayerRegion *layerm : layer.regions()) { // Extrusion width accounts for the roundings of the extrudates. // It is the maximum widh of the extrudate. fw = float(layerm->flow(frExternalPerimeter).scaled_width()); no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); - float lower_layer_offset = + lower_layer_offset = (layer_id < (size_t)object_config.support_material_enforce_layers.value) ? // Enforce a full possible support, ignore the overhang angle. 0.f : @@ -1494,7 +1517,7 @@ static inline std::tuple detect_overhangs( iRun, layer_id, std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin()), get_extents(diff_polygons)); - Slic3r::ExPolygons expolys = union_ex(diff_polygons, false); + Slic3r::ExPolygons expolys = union_ex(diff_polygons); svg.draw(expolys); } #endif /* SLIC3R_DEBUG */ @@ -1512,7 +1535,7 @@ static inline std::tuple detect_overhangs( iRun, layer_id, std::find_if(layer.regions().begin(), layer.regions().end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions().begin(), layer.print_z), - union_ex(diff_polygons, false)); + union_ex(diff_polygons)); #endif /* SLIC3R_DEBUG */ //FIXME the overhang_polygons are used to construct the support towers as well. @@ -1529,20 +1552,7 @@ static inline std::tuple detect_overhangs( //FIXME one should trim with the layer span colliding with the support layer, this layer // may be lower than lower_layer, so the support area needed may need to be actually bigger! // For the same reason, the non-bridging support area may be smaller than the bridging support area! - float slices_margin_offset = std::min(lower_layer_offset, float(scale_(gap_xy))); - if (slices_margin.offset != slices_margin_offset) { - slices_margin.offset = slices_margin_offset; - slices_margin.polygons = (slices_margin_offset == 0.f) ? - lower_layer_polygons : - offset2(lower_layer.lslices, - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS); - if (buildplate_only && ! annotations.buildplate_covered[layer_id].empty()) { - if (has_enforcer) - // Make a backup of trimming polygons before enforcing "on build plate only". - slices_margin.all_polygons = slices_margin.polygons; - // Trim the inflated contact surfaces by the top surfaces as well. - slices_margin.polygons = union_(slices_margin.polygons, annotations.buildplate_covered[layer_id]); - } - } + slices_margin_update(std::min(lower_layer_offset, float(scale_(gap_xy))), no_interface_offset); // Offset the contact polygons outside. #if 0 for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) { @@ -1572,12 +1582,13 @@ static inline std::tuple detect_overhangs( offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)); #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-top-contacts-enforcers-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { layer.lslices, { "layer.lslices", "gray", 0.2f } }, - { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "green", 0.5f } }, - { enforcers_united, { "enforcers", "blue", 0.5f } }, - { { union_ex(enforcer_polygons, true) }, { "new_contacts", "red", "black", "", scaled(0.1f), 0.5f } } }); + { { layer.lslices, { "layer.lslices", "gray", 0.2f } }, + { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "green", 0.5f } }, + { enforcers_united, { "enforcers", "blue", 0.5f } }, + { { union_safety_offset_ex(enforcer_polygons) }, { "new_contacts", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ polygons_append(overhang_polygons, enforcer_polygons); + slices_margin_update(std::min(lower_layer_offset, float(scale_(gap_xy))), no_interface_offset); polygons_append(contact_polygons, diff(enforcer_polygons, slices_margin.all_polygons.empty() ? slices_margin.polygons : slices_margin.all_polygons)); } } @@ -1738,19 +1749,19 @@ static inline void fill_contact_layer( #endif // SLIC3R_DEBUG ); #ifdef SLIC3R_DEBUG - SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, - { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, - { { union_ex(slices_margin.polygons, false) }, { "slices_margin_cached", "blue", 0.5f } }, - { { union_ex(dense_interface_polygons, false) }, { "dense_interface_polygons", "green", 0.5f } }, - { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); + SVG::export_expolygons(debug_out_path("support-top-contacts-final1-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), + { { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "gray", 0.2f } }, + { { union_ex(*new_layer.contact_polygons) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, + { { union_ex(slices_margin.polygons) }, { "slices_margin_cached", "blue", 0.5f } }, + { { union_ex(dense_interface_polygons) }, { "dense_interface_polygons", "green", 0.5f } }, + { { union_safety_offset_ex(new_layer.polygons) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); //support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z)); - SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, - { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, - { { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, - { { union_ex(dense_interface_polygons, false) }, { "dense_interface_polygons", "green", 0.5f } }, - { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); + SVG::export_expolygons(debug_out_path("support-top-contacts-final2-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), + { { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "gray", 0.2f } }, + { { union_ex(*new_layer.contact_polygons) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, + { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, + { { union_ex(dense_interface_polygons) }, { "dense_interface_polygons", "green", 0.5f } }, + { { union_safety_offset_ex(new_layer.polygons) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ } } @@ -1796,11 +1807,11 @@ static inline void fill_contact_layer( #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z), - { { { union_ex(lower_layer_polygons, false) }, { "lower_layer_polygons", "gray", 0.2f } }, - { { union_ex(*new_layer.contact_polygons, false) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, - { { union_ex(contact_polygons, false) }, { "contact_polygons", "blue", 0.5f } }, - { { union_ex(overhang_polygons, false) }, { "overhang_polygons", "green", 0.5f } }, - { { union_ex(new_layer.polygons, true) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); + { { { union_ex(lower_layer_polygons) }, { "lower_layer_polygons", "gray", 0.2f } }, + { { union_ex(*new_layer.contact_polygons) }, { "new_layer.contact_polygons", "yellow", 0.5f } }, + { { union_ex(contact_polygons) }, { "contact_polygons", "blue", 0.5f } }, + { { union_ex(overhang_polygons) }, { "overhang_polygons", "green", 0.5f } }, + { { union_safety_offset_ex(new_layer.polygons) }, { "new_layer.polygons", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. @@ -1964,10 +1975,10 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( Polygons top = collect_region_slices_by_type(layer, stTop); #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-bottom-layers-raw-%d-%lf.svg", iRun, layer.print_z), - { { { union_ex(top, false) }, { "top", "blue", 0.5f } }, - { { union_ex(supports_projected, true) }, { "overhangs", "magenta", 0.5f } }, - { layer.lslices, { "layer.lslices", "green", 0.5f } }, - { { union_ex(polygons_new, true) }, { "polygons_new", "red", "black", "", scaled(0.1f), 0.5f } } }); + { { { union_ex(top) }, { "top", "blue", 0.5f } }, + { { union_safety_offset_ex(supports_projected) }, { "overhangs", "magenta", 0.5f } }, + { layer.lslices, { "layer.lslices", "green", 0.5f } }, + { { union_safety_offset_ex(polygons_new) }, { "polygons_new", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ // Now find whether any projection of the contact surfaces above layer.print_z not yet supported by any @@ -2037,7 +2048,7 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer_new.print_z), - union_ex(layer_new.polygons, false)); + union_ex(layer_new.polygons)); #endif /* SLIC3R_DEBUG */ // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer. @@ -2050,14 +2061,14 @@ static inline PrintObjectSupportMaterial::MyLayer* detect_bottom_contacts( if (! layer_support_areas[layer_id_above].empty()) { #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-support-areas-raw-before-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), - { { { union_ex(touching, false) }, { "touching", "blue", 0.5f } }, - { { union_ex(layer_support_areas[layer_id_above], true) }, { "above", "red", "black", "", scaled(0.1f), 0.5f } } }); + { { { union_ex(touching) }, { "touching", "blue", 0.5f } }, + { { union_safety_offset_ex(layer_support_areas[layer_id_above]) }, { "above", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ layer_support_areas[layer_id_above] = diff(layer_support_areas[layer_id_above], touching); #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-support-areas-raw-after-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), - union_ex(layer_support_areas[layer_id_above], false)); + union_ex(layer_support_areas[layer_id_above])); #endif /* SLIC3R_DEBUG */ } } @@ -2080,8 +2091,8 @@ static inline std::pair project_support_to_grid(const Layer #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-%d-%lf.svg", debug_name, iRun, layer.print_z), - { { { union_ex(trimming, false) }, { "trimming", "blue", 0.5f } }, - { { union_ex(overhangs_projection, true) }, { "overhangs_projection", "red", "black", "", scaled(0.1f), 0.5f } } }); + { { { union_ex(trimming) }, { "trimming", "blue", 0.5f } }, + { { union_safety_offset_ex(overhangs_projection) }, { "overhangs_projection", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ remove_sticks(overhangs_projection); @@ -2089,8 +2100,8 @@ static inline std::pair project_support_to_grid(const Layer #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-support-areas-%s-raw-cleaned-%d-%lf.svg", debug_name, iRun, layer.print_z), - { { { union_ex(trimming, false) }, { "trimming", "blue", 0.5f } }, - { { union_ex(overhangs_projection, false) }, { "overhangs_projection", "red", "black", "", scaled(0.1f), 0.5f } } }); + { { { union_ex(trimming) }, { "trimming", "blue", 0.5f } }, + { { union_ex(overhangs_projection) }, { "overhangs_projection", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ SupportGridPattern support_grid_pattern(&overhangs_projection, &trimming, grid_params); @@ -2113,7 +2124,7 @@ static inline std::pair project_support_to_grid(const Layer #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-layer_support_area-gridded-%s-%d-%lf.svg", debug_name, iRun, layer.print_z), - union_ex(out.first, false)); + union_ex(out.first)); #endif /* SLIC3R_DEBUG */ }); @@ -2131,13 +2142,13 @@ static inline std::pair project_support_to_grid(const Layer #ifdef SLIC3R_DEBUG Slic3r::SVG::export_expolygons( debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), - union_ex(out.second, false)); + union_ex(out.second)); #endif /* SLIC3R_DEBUG */ #ifdef SLIC3R_DEBUG SVG::export_expolygons(debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z), - { { { union_ex(trimming, false) }, { "trimming", "gray", 0.5f } }, - { { union_ex(overhangs_projection, true) }, { "overhangs_projection", "blue", 0.5f } }, - { { union_ex(out.second, true) }, { "projection_new", "red", "black", "", scaled(0.1f), 0.5f } } }); + { { { union_ex(trimming) }, { "trimming", "gray", 0.5f } }, + { { union_safety_offset_ex(overhangs_projection) }, { "overhangs_projection", "blue", 0.5f } }, + { { union_safety_offset_ex(out.second) }, { "projection_new", "red", "black", "", scaled(0.1f), 0.5f } } }); #endif /* SLIC3R_DEBUG */ }); @@ -2667,10 +2678,10 @@ void PrintObjectSupportMaterial::generate_base_layers( BoundingBox bbox = get_extents(polygons_new); bbox.merge(get_extents(polygons_trimming)); ::Slic3r::SVG svg(debug_out_path("support-intermediate-layers-raw-%d-%lf.svg", iRun, layer_intermediate.print_z), bbox); - svg.draw(union_ex(polygons_new, false), "blue", 0.5f); - svg.draw(to_polylines(polygons_new), "blue"); - svg.draw(union_ex(polygons_trimming, true), "red", 0.5f); - svg.draw(to_polylines(polygons_trimming), "red"); + svg.draw(union_ex(polygons_new), "blue", 0.5f); + svg.draw(to_polylines(polygons_new), "blue"); + svg.draw(union_safety_offset_ex(polygons_trimming), "red", 0.5f); + svg.draw(to_polylines(polygons_trimming), "red"); } #endif /* SLIC3R_DEBUG */ @@ -2706,7 +2717,7 @@ void PrintObjectSupportMaterial::generate_base_layers( for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++it) ::Slic3r::SVG::export_expolygons( debug_out_path("support-intermediate-layers-untrimmed-%d-%lf.svg", iRun, (*it)->print_z), - union_ex((*it)->polygons, false)); + union_ex((*it)->polygons)); ++ iRun; #endif /* SLIC3R_DEBUG */ @@ -2799,22 +2810,22 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf Polygons brim; if (object.has_brim()) { // Calculate the area covered by the brim. - const BrimType brim_type = object.config().brim_type; - const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; - const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; - const auto brim_offset = scaled(object.config().brim_offset.value + object.config().brim_width.value); + const BrimType brim_type = object.config().brim_type; + const bool brim_outer = brim_type == btOuterOnly || brim_type == btOuterAndInner; + const bool brim_inner = brim_type == btInnerOnly || brim_type == btOuterAndInner; + const auto brim_separation = scaled(object.config().brim_separation.value + object.config().brim_width.value); for (const ExPolygon &ex : object.layers().front()->lslices) { if (brim_outer && brim_inner) - polygons_append(brim, offset(ex, brim_offset)); + polygons_append(brim, offset(ex, brim_separation)); else { if (brim_outer) - polygons_append(brim, offset(ex.contour, brim_offset, ClipperLib::jtRound, float(scale_(0.1)))); + polygons_append(brim, offset(ex.contour, brim_separation, ClipperLib::jtRound, float(scale_(0.1)))); else brim.emplace_back(ex.contour); if (brim_inner) { Polygons holes = ex.holes; polygons_reverse(holes); - holes = offset(holes, - brim_offset, ClipperLib::jtRound, float(scale_(0.1))); + holes = offset(holes, - brim_separation, ClipperLib::jtRound, float(scale_(0.1))); polygons_reverse(holes); polygons_append(brim, std::move(holes)); } else diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 02d8fccd45..ac92ecd108 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -37,18 +37,35 @@ //==================== -// 2.4.0.alpha0 techs +// 2.4.0.alpha1 techs //==================== -#define ENABLE_2_4_0_ALPHA0 1 +#define ENABLE_2_4_0_ALPHA1 1 // Enable delayed rendering of transparent volumes -#define ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING (1 && ENABLE_2_4_0_ALPHA0) +#define ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING (1 && ENABLE_2_4_0_ALPHA1) // Enable the fix of importing color print view from gcode files into GCodeViewer -#define ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER (1 && ENABLE_2_4_0_ALPHA0) +#define ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER (1 && ENABLE_2_4_0_ALPHA1) // Enable drawing contours, at cut level, for sinking volumes -#define ENABLE_SINKING_CONTOURS (1 && ENABLE_2_4_0_ALPHA0) +#define ENABLE_SINKING_CONTOURS (1 && ENABLE_2_4_0_ALPHA1) // Enable implementation of retract acceleration in gcode processor -#define ENABLE_RETRACT_ACCELERATION (1 && ENABLE_2_4_0_ALPHA0) +#define ENABLE_RETRACT_ACCELERATION (1 && ENABLE_2_4_0_ALPHA1) +// Enable the fix for exporting and importing to/from 3mf file of mirrored volumes +#define ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT (1 && ENABLE_2_4_0_ALPHA1) +// Enable rendering seams (and other options) in preview using models +#define ENABLE_SEAMS_USING_MODELS (1 && ENABLE_2_4_0_ALPHA1) +// Enable save and save as commands to be enabled also when the plater is empty and allow to load empty projects +#define ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED (1 && ENABLE_2_4_0_ALPHA1) + + +//==================== +// 2.4.0.alpha2 techs +//==================== +#define ENABLE_2_4_0_ALPHA2 1 + +// Enable rendering seams (and other options) in preview using batched models on systems not supporting OpenGL 3.3 +#define ENABLE_SEAMS_USING_BATCHED_MODELS (1 && ENABLE_SEAMS_USING_MODELS && ENABLE_2_4_0_ALPHA2) +// Enable fixing the z position of color change, pause print and custom gcode markers in preview +#define ENABLE_FIX_PREVIEW_OPTIONS_Z (1 && ENABLE_SEAMS_USING_MODELS && ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER && ENABLE_2_4_0_ALPHA2) #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index d4baabc977..fa8f8bce64 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1275,6 +1275,21 @@ float its_volume(const indexed_triangle_set &its) return volume; } +float its_average_edge_length(const indexed_triangle_set &its) +{ + if (its.indices.empty()) + return 0.f; + + double edge_length = 0.f; + for (size_t i = 0; i < its.indices.size(); ++ i) { + const its_triangle v = its_triangle_vertices(its, i); + edge_length += (v[1] - v[0]).cast().norm() + + (v[2] - v[0]).cast().norm() + + (v[1] - v[2]).cast().norm(); + } + return float(edge_length / (3 * its.indices.size())); +} + std::vector its_split(const indexed_triangle_set &its) { return its_split<>(its); diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index b7a1bebb15..c463af5a22 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -199,6 +199,7 @@ inline stl_normal its_unnormalized_normal(const indexed_triangle_set &its, } float its_volume(const indexed_triangle_set &its); +float its_average_edge_length(const indexed_triangle_set &its); void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B); void its_merge(indexed_triangle_set &A, const std::vector &triangles); diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 1b02e03310..a01e63166b 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -58,6 +58,11 @@ void set_data_dir(const std::string &path); // Return a full path to the GUI resource files. const std::string& data_dir(); +// Format an output path for debugging purposes. +// Writes out the output path prefix to the console for the first time the function is called, +// so the user knows where to search for the debugging output. +std::string debug_out_path(const char *name, ...); + // A special type for strings encoded in the local Windows 8-bit code page. // This type is only needed for Perl bindings to relay to Perl that the string is raw, not UTF-8 encoded. typedef std::string local_encoded_string; diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 8d8d15df9d..a294361893 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -65,18 +65,6 @@ static constexpr double EXTERNAL_INFILL_MARGIN = 3.; #define SCALED_EPSILON scale_(EPSILON) -#define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/" - -inline std::string debug_out_path(const char *name, ...) -{ - char buffer[2048]; - va_list args; - va_start(args, name); - std::vsprintf(buffer, name, args); - va_end(args); - return std::string(SLIC3R_DEBUG_OUT_PATH_PREFIX) + std::string(buffer); -} - #ifndef UNUSED #define UNUSED(x) (void)(x) #endif /* UNUSED */ diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 29f955e924..c5dbdac9cd 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -1,6 +1,7 @@ #include "Utils.hpp" #include "I18N.hpp" +#include #include #include #include @@ -207,6 +208,23 @@ std::string custom_shapes_dir() return (boost::filesystem::path(g_data_dir) / "shapes").string(); } +static std::atomic debug_out_path_called(false); + +std::string debug_out_path(const char *name, ...) +{ + static constexpr const char *SLIC3R_DEBUG_OUT_PATH_PREFIX = "out/"; + if (! debug_out_path_called.exchange(true)) { + std::string path = boost::filesystem::system_complete(SLIC3R_DEBUG_OUT_PATH_PREFIX).string(); + printf("Debugging output files will be written to %s\n", path.c_str()); + } + char buffer[2048]; + va_list args; + va_start(args, name); + std::vsprintf(buffer, name, args); + va_end(args); + return std::string(SLIC3R_DEBUG_OUT_PATH_PREFIX) + std::string(buffer); +} + #ifdef _WIN32 // The following helpers are borrowed from the LLVM project https://github.com/llvm namespace WindowsSupport @@ -966,16 +984,11 @@ std::string log_memory_info(bool ignore_loglevel) } PROCESS_MEMORY_COUNTERS_EX, *PPROCESS_MEMORY_COUNTERS_EX; #endif /* PROCESS_MEMORY_COUNTERS_EX */ - - HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ::GetCurrentProcessId()); - if (hProcess != nullptr) { - PROCESS_MEMORY_COUNTERS_EX pmc; - if (GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) - out = " WorkingSet: " + format_memsize_MB(pmc.WorkingSetSize) + "; PrivateBytes: " + format_memsize_MB(pmc.PrivateUsage) + "; Pagefile(peak): " + format_memsize_MB(pmc.PagefileUsage) + "(" + format_memsize_MB(pmc.PeakPagefileUsage) + ")"; - else - out += " Used memory: N/A"; - CloseHandle(hProcess); - } + PROCESS_MEMORY_COUNTERS_EX pmc; + if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) + out = " WorkingSet: " + format_memsize_MB(pmc.WorkingSetSize) + "; PrivateBytes: " + format_memsize_MB(pmc.PrivateUsage) + "; Pagefile(peak): " + format_memsize_MB(pmc.PagefileUsage) + "(" + format_memsize_MB(pmc.PeakPagefileUsage) + ")"; + else + out += " Used memory: N/A"; #elif defined(__linux__) or defined(__APPLE__) // Get current memory usage. #ifdef __APPLE__ diff --git a/src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.desktop b/src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.desktop deleted file mode 100755 index 8987222984..0000000000 --- a/src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.desktop +++ /dev/null @@ -1,12 +0,0 @@ -[Desktop Entry] -Name=PrusaSlicer -GenericName=3D Printing Software -Icon=com.prusa3d.PrusaSlicer -Exec=prusa-slicer %F -Terminal=false -Type=Application -MimeType=model/stl;model/x-wavefront-obj;model/3mf;model/x-geomview-off;application/x-amf; -Categories=Graphics;3DGraphics;Engineering; -Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA -StartupNotify=false -StartupWMClass=prusa-slicer \ No newline at end of file diff --git a/src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.metainfo.xml b/src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.metainfo.xml deleted file mode 100755 index b62a57e480..0000000000 --- a/src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.metainfo.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - com.prusa3d.PrusaSlicer - com.prusa3d.PrusaSlicer.desktop - - prusa-slicer.desktop - - PrusaSlicer - Powerful 3D printing slicer optimized for Prusa printers - 0BSD - AGPL-3.0-only - -

- PrusaSlicer takes 3D models (STL, OBJ, AMF) and converts them into G-code - instructions for FFF printers or PNG layers for mSLA 3D printers. It's - compatible with any modern printer based on the RepRap toolchain, including all - those based on the Marlin, Prusa, Sprinter and Repetier firmware. It also works - with Mach3, LinuxCNC and Machinekit controllers. -

-

- PrusaSlicer is based on Slic3r by Alessandro Ranelucci and the RepRap community. -

-

- What are some of PrusaSlicer's main features? -

-
    -
  • multi-platform (Linux/Mac/Win) and packaged as standalone-app with no dependencies required
  • -
  • complete command-line interface to use it with no GUI
  • -
  • multi-material (multiple extruders) object printing
  • -
  • multiple G-code flavors supported (RepRap, Makerbot, Mach3, Machinekit etc.)
  • -
  • ability to plate multiple objects having distinct print settings
  • -
  • multithread processing
  • -
  • STL auto-repair (tolerance for broken models)
  • -
  • wide automated unit testing
  • -
-
- https://www.prusa3d.com/prusaslicer/ - https://help.prusa3d.com - https://github.com/prusa3d/PrusaSlicer/issues - - - https://user-images.githubusercontent.com/590307/78981854-24d07580-7b21-11ea-9441-77923534a659.png - - - https://user-images.githubusercontent.com/590307/78981860-2863fc80-7b21-11ea-8c2d-8ff79ced2578.png - - - https://user-images.githubusercontent.com/590307/78981862-28fc9300-7b21-11ea-9b0d-d03e16b709d3.png - - - - - - -

This is final release of PrusaSlicer 2.2.0 introducing SLA hollowing and hole drilling, support for 3rd party printer vendors, 3Dconnexion support, - automatic variable layer height, macOS dark mode support, greatly improved ColorPrint feature and much, much more. - Several bugs found in the previous release candidate are fixed in this final release. See the respective change logs of the previous releases for all the - new features, improvements and bugfixes in the 2.2.0 series.

-
-
-
-
diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index d7fb937d04..5b7218c87e 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -119,7 +119,7 @@ void Bed3D::Axes::render() const glsafe(::glEnable(GL_DEPTH_TEST)); shader->start_using(); - shader->set_uniform("emission_factor", 0.0); + shader->set_uniform("emission_factor", 0.0f); // x axis const_cast(&m_arrow)->set_color(-1, { 0.75f, 0.0f, 0.0f, 1.0f }); @@ -141,11 +141,13 @@ void Bed3D::Axes::render() const bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) { auto check_texture = [](const std::string& texture) { - return !texture.empty() && (boost::algorithm::iends_with(texture, ".png") || boost::algorithm::iends_with(texture, ".svg")) && boost::filesystem::exists(texture); + boost::system::error_code ec; // so the exists call does not throw (e.g. after a permission problem) + return !texture.empty() && (boost::algorithm::iends_with(texture, ".png") || boost::algorithm::iends_with(texture, ".svg")) && boost::filesystem::exists(texture, ec); }; auto check_model = [](const std::string& model) { - return !model.empty() && boost::algorithm::iends_with(model, ".stl") && boost::filesystem::exists(model); + boost::system::error_code ec; + return !model.empty() && boost::algorithm::iends_with(model, ".stl") && boost::filesystem::exists(model, ec); }; EType type; @@ -161,12 +163,16 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c } std::string texture_filename = custom_texture.empty() ? texture : custom_texture; - if (!check_texture(texture_filename)) + if (! texture_filename.empty() && ! check_texture(texture_filename)) { + BOOST_LOG_TRIVIAL(error) << "Unable to load bed texture: " << texture_filename; texture_filename.clear(); + } std::string model_filename = custom_model.empty() ? model : custom_model; - if (!check_model(model_filename)) + if (! model_filename.empty() && ! check_model(model_filename)) { + BOOST_LOG_TRIVIAL(error) << "Unable to load bed model: " << model_filename; model_filename.clear(); + } if (m_shape == shape && m_type == type && m_texture_filename == texture_filename && m_model_filename == model_filename) // No change, no need to update the UI. @@ -498,7 +504,7 @@ void Bed3D::render_model() const GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) { shader->start_using(); - shader->set_uniform("emission_factor", 0.0); + shader->set_uniform("emission_factor", 0.0f); glsafe(::glPushMatrix()); glsafe(::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z())); model->render(); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 7a9fdc3886..ba72c432d8 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -409,6 +409,11 @@ GLVolume::GLVolume(float r, float g, float b, float a) set_render_color(color); } +void GLVolume::set_color(const std::array& rgba) +{ + color = rgba; +} + void GLVolume::set_render_color(float r, float g, float b, float a) { render_color = { r, g, b, a }; @@ -458,8 +463,9 @@ void GLVolume::set_render_color() render_color[3] = color[3]; } -void GLVolume::set_color_from_model_volume(const ModelVolume& model_volume) +std::array color_from_model_volume(const ModelVolume& model_volume) { + std::array color; if (model_volume.is_negative_volume()) { color[0] = 0.2f; color[1] = 0.2f; @@ -481,6 +487,7 @@ void GLVolume::set_color_from_model_volume(const ModelVolume& model_volume) color[2] = 1.0f; } color[3] = model_volume.is_model_part() ? 1.f : 0.5f; + return color; } Transform3d GLVolume::world_matrix() const @@ -635,7 +642,7 @@ int GLVolumeCollection::load_object_volume( color[3] = model_volume->is_model_part() ? 1.f : 0.5f; this->volumes.emplace_back(new GLVolume(color)); GLVolume& v = *this->volumes.back(); - v.set_color_from_model_volume(*model_volume); + v.set_color(color_from_model_volume(*model_volume)); #if ENABLE_SMOOTH_NORMALS v.indexed_vertex_array.load_mesh(mesh, true); #else @@ -827,6 +834,12 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func) const { +#if ENABLE_SINKING_CONTOURS + GLVolumeWithIdAndZList to_render = volumes_to_render(volumes, type, view_matrix, filter_func); + if (to_render.empty()) + return; +#endif // ENABLE_SINKING_CONTOURS + GLShaderProgram* shader = GUI::wxGetApp().get_current_shader(); if (shader == nullptr) return; @@ -841,7 +854,6 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDisable(GL_CULL_FACE)); #if ENABLE_SINKING_CONTOURS - GLVolumeWithIdAndZList to_render = volumes_to_render(volumes, type, view_matrix, filter_func); for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); @@ -961,8 +973,10 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M if (opt == nullptr) return false; - BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); - BoundingBoxf3 print_volume({ unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0 }, { unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config->opt_float("max_print_height") }); + const BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); + BoundingBoxf3 print_volume({ unscale(bed_box_2D.min.x()), unscale(bed_box_2D.min.y()), 0.0 }, + { unscale(bed_box_2D.max.x()), unscale(bed_box_2D.max.y()), + config->opt_float("max_print_height") }); // Allow the objects to protrude below the print bed print_volume.min(2) = -1e10; print_volume.min(0) -= BedEpsilon; @@ -975,7 +989,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M 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)) + if (volume->is_modifier || (!volume->shader_outside_printer_detection_enabled && (volume->is_wipe_tower || volume->composite_id.volume_id < 0))) continue; const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); @@ -985,8 +999,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M if (!volume->printable) continue; - if (contained) - contained_min_one = true; + contained_min_one |= contained; if (state == ModelInstancePVS_Inside && volume->is_outside) state = ModelInstancePVS_Fully_Outside; @@ -1001,56 +1014,6 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M return contained_min_one; } -bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut) const -{ - if (config == nullptr) - return false; - - const ConfigOptionPoints* opt = dynamic_cast(config->option("bed_shape")); - if (opt == nullptr) - return false; - - const BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); - BoundingBoxf3 print_volume(Vec3d(unscale(bed_box_2D.min.x()), unscale(bed_box_2D.min.y()), 0.0), Vec3d(unscale(bed_box_2D.max.x()), unscale(bed_box_2D.max.y()), config->opt_float("max_print_height"))); - // Allow the objects to protrude below the print bed - print_volume.min(2) = -1e10; - print_volume.min(0) -= BedEpsilon; - print_volume.min(1) -= BedEpsilon; - print_volume.max(0) += BedEpsilon; - print_volume.max(1) += BedEpsilon; - - bool contained_min_one = false; - - partlyOut = false; - fullyOut = 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)) - continue; - - const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); - bool contained = print_volume.contains(bb); - - volume->is_outside = !contained; - if (!volume->printable) - continue; - - if (contained) - contained_min_one = true; - - if (volume->is_outside) { - if (print_volume.intersects(bb)) - partlyOut = true; - else - fullyOut = true; - } - } - /* - if (out_state != nullptr) - *out_state = state; - */ - return contained_min_one; -} - void GLVolumeCollection::reset_outside_state() { for (GLVolume* volume : this->volumes) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index e5298ca931..1a85cc41e1 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -39,6 +39,10 @@ class ModelObject; class ModelVolume; enum ModelInstanceEPrintVolumeState : unsigned char; +// Return appropriate color based on the ModelVolume. +std::array color_from_model_volume(const ModelVolume& model_volume); + + // A container for interleaved arrays of 3D vertices and normals, // possibly indexed by triangles and / or quads. class GLIndexedVertexArray { @@ -393,6 +397,7 @@ public: return out; } + void set_color(const std::array& rgba); void set_render_color(float r, float g, float b, float a); void set_render_color(const std::array& rgba); // Sets render color in dependence of current state @@ -614,7 +619,6 @@ public: // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const; - bool check_outside_state(const DynamicPrintConfig* config, bool& partlyOut, bool& fullyOut) const; void reset_outside_state(); void update_colors_by_extruder(const DynamicPrintConfig* config); diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 72f1421f68..2aae9270dc 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -122,7 +122,9 @@ void CopyrightsDialog::fill_entries() { "AppImage packaging for Linux using AppImageKit" , "2004-2019 Simon Peter and contributors" , "https://appimage.org/" }, { "lib_fts" - , "Forrest Smith" , "https://www.forrestthewoods.com/" } + , "Forrest Smith" , "https://www.forrestthewoods.com/" }, + { "fast_float" + , "Daniel Lemire, João Paulo Magalhaes and contributors", "https://github.com/fastfloat/fast_float" } }; } diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 8a58e5ec93..b4de4a509f 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -574,11 +574,8 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn // Some FFF status was invalidated, and the G-code was not exported yet. // Let the G-code preview UI know that the final G-code preview is not valid. // In addition, this early memory deallocation reduces memory footprint. - if (m_gcode_result != nullptr) { - //FIXME calling platter from here is not a staple of a good architecture. - GUI::wxGetApp().plater()->stop_mapping_gcode_window(); + if (m_gcode_result != nullptr) m_gcode_result->reset(); - } } return invalidated; } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 54b87786a5..070773c116 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -171,23 +171,14 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con "- Detect bridging perimeters")); if (is_global_config) msg_text += "\n\n" + _(L("Shall I adjust those settings for supports?")); - //wxMessageDialog dialog(nullptr, msg_text, _(L("Support Generator")), - MessageDialog dialog(nullptr, msg_text, _(L("Support Generator")), - wxICON_WARNING | (is_global_config ? wxYES | wxNO | wxCANCEL : wxOK)); + MessageDialog dialog(nullptr, msg_text, _L("Support Generator"), wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK)); DynamicPrintConfig new_conf = *config; auto answer = dialog.ShowModal(); if (!is_global_config || answer == wxID_YES) { // Enable "detect bridging perimeters". new_conf.set_key_value("overhangs", new ConfigOptionBool(true)); } - else if (answer == wxID_NO) { - // Do nothing, leave supports on and "detect bridging perimeters" off. - } - else if (answer == wxID_CANCEL) { - // Disable supports. - new_conf.set_key_value("support_material", new ConfigOptionBool(false)); - support_material_overhangs_queried = false; - } + //else Do nothing, leave supports on and "detect bridging perimeters" off. apply(config, &new_conf); } } @@ -281,7 +272,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field(el, have_skirt); bool have_brim = config->opt_enum("brim_type") != btNoBrim; - for (auto el : { "brim_width", "brim_offset" }) + for (auto el : { "brim_width", "brim_separation" }) toggle_field(el, have_brim); // perimeter_extruder uses the same logic as in Print::extruders() toggle_field("perimeter_extruder", have_perimeters || have_brim); @@ -312,7 +303,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field("support_material_speed", have_support_material || have_brim || have_skirt); toggle_field("raft_contact_distance", have_raft && !have_support_soluble); - toggle_field("raft_expansion", have_raft); + for (auto el : { "raft_expansion", "first_layer_acceleration_over_raft", "first_layer_speed_over_raft" }) + toggle_field(el, have_raft); bool has_ironing = config->opt_bool("ironing"); for (auto el : { "ironing_type", "ironing_flowrate", "ironing_spacing", "ironing_speed" }) diff --git a/src/slic3r/GUI/DesktopIntegrationDialog.cpp b/src/slic3r/GUI/DesktopIntegrationDialog.cpp index fb4a62f918..24552ec585 100644 --- a/src/slic3r/GUI/DesktopIntegrationDialog.cpp +++ b/src/slic3r/GUI/DesktopIntegrationDialog.cpp @@ -22,6 +22,95 @@ namespace Slic3r { namespace GUI { namespace { + +// escaping of path string according to +// https://cgit.freedesktop.org/xdg/xdg-specs/tree/desktop-entry/desktop-entry-spec.xml +std::string escape_string(const std::string& str) +{ + // The buffer needs to be bigger if escaping <,>,& + std::vector out(str.size() * 4, 0); + char *outptr = out.data(); + for (size_t i = 0; i < str.size(); ++ i) { + char c = str[i]; + // must be escaped + if (c == '\"') { //double quote + (*outptr ++) = '\\'; + (*outptr ++) = '\"'; + } else if (c == '`') { // backtick character + (*outptr ++) = '\\'; + (*outptr ++) = '`'; + } else if (c == '$') { // dollar sign + (*outptr ++) = '\\'; + (*outptr ++) = '$'; + } else if (c == '\\') { // backslash character + (*outptr ++) = '\\'; + (*outptr ++) = '\\'; + (*outptr ++) = '\\'; + (*outptr ++) = '\\'; + // Reserved characters + // At Ubuntu, all these characters must NOT be escaped for desktop integration to work + /* + } else if (c == ' ') { // space + (*outptr ++) = '\\'; + (*outptr ++) = ' '; + } else if (c == '\t') { // tab + (*outptr ++) = '\\'; + (*outptr ++) = '\t'; + } else if (c == '\n') { // newline + (*outptr ++) = '\\'; + (*outptr ++) = '\n'; + } else if (c == '\'') { // single quote + (*outptr ++) = '\\'; + (*outptr ++) = '\''; + } else if (c == '>') { // greater-than sign + (*outptr ++) = '\\'; + (*outptr ++) = '&'; + (*outptr ++) = 'g'; + (*outptr ++) = 't'; + (*outptr ++) = ';'; + } else if (c == '<') { //less-than sign + (*outptr ++) = '\\'; + (*outptr ++) = '&'; + (*outptr ++) = 'l'; + (*outptr ++) = 't'; + (*outptr ++) = ';'; + } else if (c == '~') { // tilde + (*outptr ++) = '\\'; + (*outptr ++) = '~'; + } else if (c == '|') { // vertical bar + (*outptr ++) = '\\'; + (*outptr ++) = '|'; + } else if (c == '&') { // ampersand + (*outptr ++) = '\\'; + (*outptr ++) = '&'; + (*outptr ++) = 'a'; + (*outptr ++) = 'm'; + (*outptr ++) = 'p'; + (*outptr ++) = ';'; + } else if (c == ';') { // semicolon + (*outptr ++) = '\\'; + (*outptr ++) = ';'; + } else if (c == '*') { //asterisk + (*outptr ++) = '\\'; + (*outptr ++) = '*'; + } else if (c == '?') { // question mark + (*outptr ++) = '\\'; + (*outptr ++) = '?'; + } else if (c == '#') { // hash mark + (*outptr ++) = '\\'; + (*outptr ++) = '#'; + } else if (c == '(') { // parenthesis + (*outptr ++) = '\\'; + (*outptr ++) = '('; + } else if (c == ')') { + (*outptr ++) = '\\'; + (*outptr ++) = ')'; + */ + } else + (*outptr ++) = c; + } + return std::string(out.data(), outptr - out.data()); +} // Disects path strings stored in system variable divided by ':' and adds into vector void resolve_path_from_var(const std::string& var, std::vector& paths) { @@ -157,7 +246,8 @@ void DesktopIntegrationDialog::perform_desktop_integration() } // Escape ' characters in appimage, other special symbols will be esacaped in desktop file by 'excutable_path' - boost::replace_all(excutable_path, "'", "'\\''"); + //boost::replace_all(excutable_path, "'", "'\\''"); + excutable_path = escape_string(excutable_path); // Find directories icons and applications // $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored. @@ -243,14 +333,14 @@ void DesktopIntegrationDialog::perform_desktop_integration() "Name=PrusaSlicer%1%\n" "GenericName=3D Printing Software\n" "Icon=PrusaSlicer%2%\n" - "Exec=\'%3%\' %%F\n" + "Exec=\"%3%\" %%F\n" "Terminal=false\n" "Type=Application\n" "MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n" "Categories=Graphics;3DGraphics;Engineering;\n" "Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n" "StartupNotify=false\n" - "StartupWMClass=prusa-slicer", name_suffix, version_suffix, excutable_path); + "StartupWMClass=prusa-slicer\n", name_suffix, version_suffix, excutable_path); std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix); if (create_desktop_file(path, desktop_file)){ @@ -292,40 +382,44 @@ void DesktopIntegrationDialog::perform_desktop_integration() app_config->set("desktop_integration_app_path", GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix)); // Repeat for Gcode viewer - use same paths as for slicer files - // Icon - if (!target_dir_icons.empty()) - { - std::string icon_path = GUI::format("%1%/icons/PrusaSlicer-gcodeviewer_192px.png",resources_dir()); - std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer-gcodeviewer%3%.png", target_dir_icons, icon_theme_path, version_suffix); - if (copy_icon(icon_path, dest_path)) - // save path to icon - app_config->set("desktop_integration_icon_viewer_path", dest_path); - else - BOOST_LOG_TRIVIAL(error) << "Copying Gcode Viewer icon to icons directory failed."; - } + // Do NOT add gcode viewer desktop file on ChromeOS + if (platform_flavor() != PlatformFlavor::LinuxOnChromium) { + // Icon + if (!target_dir_icons.empty()) + { + std::string icon_path = GUI::format("%1%/icons/PrusaSlicer-gcodeviewer_192px.png",resources_dir()); + std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer-gcodeviewer%3%.png", target_dir_icons, icon_theme_path, version_suffix); + if (copy_icon(icon_path, dest_path)) + // save path to icon + app_config->set("desktop_integration_icon_viewer_path", dest_path); + else + BOOST_LOG_TRIVIAL(error) << "Copying Gcode Viewer icon to icons directory failed."; + } - // Desktop file - std::string desktop_file = GUI::format( - "[Desktop Entry]\n" - "Name=Prusa Gcode Viewer%1%\n" - "GenericName=3D Printing Software\n" - "Icon=PrusaSlicer-gcodeviewer%2%\n" - "Exec=\'%3%\' --gcodeviwer %%F\n" - "Terminal=false\n" - "Type=Application\n" - "MimeType=text/x.gcode;\n" - "Categories=Graphics;3DGraphics;\n" - "Keywords=3D;Printing;Slicer;\n" - "StartupNotify=false", name_suffix, version_suffix, excutable_path); + // Desktop file + std::string desktop_file = GUI::format( + "[Desktop Entry]\n" + "Name=Prusa Gcode Viewer%1%\n" + "GenericName=3D Printing Software\n" + "Icon=PrusaSlicer-gcodeviewer%2%\n" + "Exec=\"%3%\" --gcodeviewer %%F\n" + "Terminal=false\n" + "Type=Application\n" + "MimeType=text/x.gcode;\n" + "Categories=Graphics;3DGraphics;\n" + "Keywords=3D;Printing;Slicer;\n" + "StartupNotify=false\n", name_suffix, version_suffix, excutable_path); - std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerGcodeViewer%2%.desktop", target_dir_desktop, version_suffix); - if (create_desktop_file(desktop_path, desktop_file)) - // save path to desktop file - app_config->set("desktop_integration_app_viewer_path", desktop_path); - else { - BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create Gcodeviewer desktop file"; - show_error(nullptr, _L("Performing desktop integration failed - could not create Gcodeviewer desktop file. PrusaSlicer desktop file was probably created successfully.")); + std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerGcodeViewer%2%.desktop", target_dir_desktop, version_suffix); + if (create_desktop_file(desktop_path, desktop_file)) + // save path to desktop file + app_config->set("desktop_integration_app_viewer_path", desktop_path); + else { + BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create Gcodeviewer desktop file"; + show_error(nullptr, _L("Performing desktop integration failed - could not create Gcodeviewer desktop file. PrusaSlicer desktop file was probably created successfully.")); + } } + wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess); } void DesktopIntegrationDialog::undo_desktop_intgration() @@ -343,17 +437,20 @@ void DesktopIntegrationDialog::undo_desktop_intgration() BOOST_LOG_TRIVIAL(debug) << "removing " << path; std::remove(path.c_str()); } - // gcode viwer .desktop - path = std::string(app_config->get("desktop_integration_app_viewer_path")); - if (!path.empty()) { - BOOST_LOG_TRIVIAL(debug) << "removing " << path; - std::remove(path.c_str()); - } - // gcode viewer icon - path = std::string(app_config->get("desktop_integration_icon_viewer_path")); - if (!path.empty()) { - BOOST_LOG_TRIVIAL(debug) << "removing " << path; - std::remove(path.c_str()); + // No gcode viewer at ChromeOS + if (platform_flavor() != PlatformFlavor::LinuxOnChromium) { + // gcode viewer .desktop + path = std::string(app_config->get("desktop_integration_app_viewer_path")); + if (!path.empty()) { + BOOST_LOG_TRIVIAL(debug) << "removing " << path; + std::remove(path.c_str()); + } + // gcode viewer icon + path = std::string(app_config->get("desktop_integration_icon_viewer_path")); + if (!path.empty()) { + BOOST_LOG_TRIVIAL(debug) << "removing " << path; + std::remove(path.c_str()); + } } wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess); } diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index e9fb7339f3..5fe86db275 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -197,6 +197,17 @@ wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRec labelRect.SetWidth(labelRect.GetWidth() - bmp_width); } +#ifdef __WXMSW__ + // Case when from some reason we try to create next EditorCtrl till old one was not deleted + if (auto children = parent->GetChildren(); children.GetCount() > 0) + for (auto child : children) + if (dynamic_cast(child)) { + parent->RemoveChild(child); + child->Destroy(); + break; + } +#endif // __WXMSW__ + wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(), position, labelRect.GetSize(), wxTE_PROCESS_ENTER); text_editor->SetInsertionPointEnd(); diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 1aaac54690..06cfd0a1c1 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -230,18 +230,21 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true } double val; + bool is_na_value = m_opt.nullable && str == na_value(); + const char dec_sep = is_decimal_separator_point() ? '.' : ','; const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; - // Replace the first incorrect separator in decimal number. - if (str.Replace(dec_sep_alt, dec_sep, false) != 0) + // Replace the first incorrect separator in decimal number, + // if this value doesn't "N/A" value in some language + // see https://github.com/prusa3d/PrusaSlicer/issues/6921 + if (!is_na_value && str.Replace(dec_sep_alt, dec_sep, false) != 0) set_value(str, false); - if (str == dec_sep) val = 0.0; else { - if (m_opt.nullable && str == na_value()) + if (is_na_value) val = ConfigOptionFloatsNullable::nil_value(); else if (!str.ToDouble(&val)) { diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 090accb6a4..9b7d8ec47c 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -44,10 +44,10 @@ static EMoveType buffer_type(unsigned char id) { return static_cast(static_cast(EMoveType::Retract) + id); } -static std::array decode_color(const std::string& color) { +static std::array decode_color(const std::string& color) { static const float INV_255 = 1.0f / 255.0f; - std::array ret = { 0.0f, 0.0f, 0.0f }; + std::array ret = { 0.0f, 0.0f, 0.0f, 1.0f }; const char* c = color.data() + 1; if (color.size() == 7 && color.front() == '#') { for (size_t j = 0; j < 3; ++j) { @@ -62,26 +62,17 @@ static std::array decode_color(const std::string& color) { return ret; } -static std::vector> decode_colors(const std::vector& colors) { - std::vector> output(colors.size(), { 0.0f, 0.0f, 0.0f }); +static std::vector> decode_colors(const std::vector& colors) { + std::vector> output(colors.size(), { 0.0f, 0.0f, 0.0f, 1.0f }); for (size_t i = 0; i < colors.size(); ++i) { output[i] = decode_color(colors[i]); } return output; } -static float round_to_nearest(float value, unsigned int decimals) +static float round_to_nearest_percent(float value) { - float res = 0.0f; - if (decimals == 0) - res = std::round(value); - else { - char buf[64]; - // locales should not matter, both sprintf and stof are sensitive, so... - sprintf(buf, "%.*g", decimals, value); - res = std::stof(buf); - } - return res; + return std::round(value * 100.f) * 0.01f; } void GCodeViewer::VBuffer::reset() @@ -95,6 +86,26 @@ void GCodeViewer::VBuffer::reset() count = 0; } +#if ENABLE_SEAMS_USING_MODELS +void GCodeViewer::InstanceVBuffer::Ranges::reset() +{ + for (Range& range : ranges) { + // release gpu memory + if (range.vbo > 0) + glsafe(::glDeleteBuffers(1, &range.vbo)); + } + + ranges.clear(); +} + +void GCodeViewer::InstanceVBuffer::reset() +{ + s_ids.clear(); + buffer.clear(); + render_ranges.reset(); +} +#endif // ENABLE_SEAMS_USING_MODELS + void GCodeViewer::IBuffer::reset() { // release gpu memory @@ -125,8 +136,8 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const case EMoveType::Extrude: { // use rounding to reduce the number of generated paths return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && - move.position[2] <= sub_paths.front().first.position[2] && feedrate == move.feedrate && fan_speed == move.fan_speed && - height == round_to_nearest(move.height, 2) && width == round_to_nearest(move.width, 2) && + move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed && + height == round_to_nearest_percent(move.height) && width == round_to_nearest_percent(move.width) && matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f); } case EMoveType::Travel: { @@ -136,18 +147,26 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const } } +#if ENABLE_SEAMS_USING_MODELS +void GCodeViewer::TBuffer::Model::reset() +{ + instances.reset(); +} +#endif // ENABLE_SEAMS_USING_MODELS + void GCodeViewer::TBuffer::reset() { - // release gpu memory vertices.reset(); for (IBuffer& buffer : indices) { buffer.reset(); } - // release cpu memory indices.clear(); paths.clear(); render_paths.clear(); +#if ENABLE_SEAMS_USING_MODELS + model.reset(); +#endif // ENABLE_SEAMS_USING_MODELS } void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) @@ -155,7 +174,7 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsi Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; // use rounding to reduce the number of generated paths paths.push_back({ move.type, move.extrusion_role, move.delta_extruder, - round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), + round_to_nearest_percent(move.height), round_to_nearest_percent(move.width), move.feedrate, move.fan_speed, move.temperature, move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); } @@ -176,7 +195,7 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) con const float local_t = std::clamp(global_t - static_cast(color_low_idx), 0.0f, 1.0f); // Interpolate between the low and high colors to find exactly which color the input value should get - Color ret; + Color ret = { 0.0f, 0.0f, 0.0f, 1.0f }; for (unsigned int i = 0; i < 3; ++i) { ret[i] = lerp(Range_Colors[color_low_idx][i], Range_Colors[color_high_idx][i], local_t); } @@ -195,7 +214,7 @@ void GCodeViewer::SequentialRangeCap::reset() { buffer = nullptr; ibo = 0; vbo = 0; - color = { 0.0f, 0.0f, 0.0f }; + color = { 0.0f, 0.0f, 0.0f, 1.0f }; } void GCodeViewer::SequentialView::Marker::init() @@ -207,7 +226,7 @@ void GCodeViewer::SequentialView::Marker::init() void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position) { m_world_position = position; - m_world_transform = (Geometry::assemble_transform((position + m_z_offset * Vec3f::UnitZ()).cast()) * Geometry::assemble_transform(m_model.get_bounding_box().size()[2] * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast(); + m_world_transform = (Geometry::assemble_transform((position + m_z_offset * Vec3f::UnitZ()).cast()) * Geometry::assemble_transform(m_model.get_bounding_box().size().z() * Vec3d::UnitZ(), { M_PI, 0.0, 0.0 })).cast(); } void GCodeViewer::SequentialView::Marker::render() const @@ -223,7 +242,7 @@ void GCodeViewer::SequentialView::Marker::render() const glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); shader->start_using(); - shader->set_uniform("emission_factor", 0.0); + shader->set_uniform("emission_factor", 0.0f); glsafe(::glPushMatrix()); glsafe(::glMultMatrixf(m_world_transform.data())); @@ -265,45 +284,14 @@ void GCodeViewer::SequentialView::Marker::render() const ImGui::PopStyleVar(); } -void GCodeViewer::SequentialView::GCodeWindow::load_gcode() +void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, std::vector &&lines_ends) { - if (m_filename.empty()) - return; - + assert(! m_file.is_open()); if (m_file.is_open()) return; - try - { - // generate mapping for accessing data in file by line number - boost::nowide::ifstream f(m_filename); - - f.seekg(0, f.end); - uint64_t file_length = static_cast(f.tellg()); - f.seekg(0, f.beg); - - std::string line; - uint64_t offset = 0; - while (std::getline(f, line)) { - uint64_t line_length = static_cast(line.length()); - m_lines_map.push_back({ offset, line_length }); - offset += static_cast(line_length) + 1; - } - - if (offset != file_length) { - // if the final offset does not match with file length, lines are terminated with CR+LF - // so update all offsets accordingly - for (size_t i = 0; i < m_lines_map.size(); ++i) { - m_lines_map[i].first += static_cast(i); - } - } - } - catch (...) - { - BOOST_LOG_TRIVIAL(error) << "Unable to load data from " << m_filename << ". Cannot show G-code window."; - reset(); - return; - } + m_filename = filename; + m_lines_ends = std::move(lines_ends); m_selected_line_id = 0; m_last_lines_size = 0; @@ -326,7 +314,9 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, u ret.reserve(end_id - start_id + 1); for (uint64_t id = start_id; id <= end_id; ++id) { // read line from file - std::string gline(m_file.data() + m_lines_map[id - 1].first, m_lines_map[id - 1].second); + const size_t start = id == 1 ? 0 : m_lines_ends[id - 2]; + const size_t len = m_lines_ends[id - 1] - start; + std::string gline(m_file.data() + start, len); std::string command; std::string parameters; @@ -360,7 +350,7 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, u static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f }; - if (!m_visible || m_filename.empty() || m_lines_map.empty() || curr_line_id == 0) + if (!m_visible || m_filename.empty() || m_lines_ends.empty() || curr_line_id == 0) return; // window height @@ -378,8 +368,8 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, u const uint64_t half_lines_count = lines_count / 2; uint64_t start_id = (curr_line_id >= half_lines_count) ? curr_line_id - half_lines_count : 0; uint64_t end_id = start_id + lines_count - 1; - if (end_id >= static_cast(m_lines_map.size())) { - end_id = static_cast(m_lines_map.size()) - 1; + if (end_id >= static_cast(m_lines_ends.size())) { + end_id = static_cast(m_lines_ends.size()) - 1; start_id = end_id - lines_count + 1; } @@ -484,58 +474,60 @@ void GCodeViewer::SequentialView::render(float legend_height) const } const std::vector GCodeViewer::Extrusion_Role_Colors {{ - { 0.75f, 0.75f, 0.75f }, // erNone - { 1.00f, 0.90f, 0.30f }, // erPerimeter - { 1.00f, 0.49f, 0.22f }, // erExternalPerimeter - { 0.12f, 0.12f, 1.00f }, // erOverhangPerimeter - { 0.69f, 0.19f, 0.16f }, // erInternalInfill - { 0.59f, 0.33f, 0.80f }, // erSolidInfill - { 0.94f, 0.25f, 0.25f }, // erTopSolidInfill - { 1.00f, 0.55f, 0.41f }, // erIroning - { 0.30f, 0.50f, 0.73f }, // erBridgeInfill - { 1.00f, 1.00f, 1.00f }, // erGapFill - { 0.00f, 0.53f, 0.43f }, // erSkirt - { 0.00f, 1.00f, 0.00f }, // erSupportMaterial - { 0.00f, 0.50f, 0.00f }, // erSupportMaterialInterface - { 0.70f, 0.89f, 0.67f }, // erWipeTower - { 0.37f, 0.82f, 0.58f }, // erCustom - { 0.00f, 0.00f, 0.00f } // erMixed + { 0.90f, 0.70f, 0.70f, 1.0f }, // erNone + { 1.00f, 0.90f, 0.30f, 1.0f }, // erPerimeter + { 1.00f, 0.49f, 0.22f, 1.0f }, // erExternalPerimeter + { 0.12f, 0.12f, 1.00f, 1.0f }, // erOverhangPerimeter + { 0.69f, 0.19f, 0.16f, 1.0f }, // erInternalInfill + { 0.59f, 0.33f, 0.80f, 1.0f }, // erSolidInfill + { 0.94f, 0.25f, 0.25f, 1.0f }, // erTopSolidInfill + { 1.00f, 0.55f, 0.41f, 1.0f }, // erIroning + { 0.30f, 0.50f, 0.73f, 1.0f }, // erBridgeInfill + { 1.00f, 1.00f, 1.00f, 1.0f }, // erGapFill + { 0.00f, 0.53f, 0.43f, 1.0f }, // erSkirt + { 0.00f, 1.00f, 0.00f, 1.0f }, // erSupportMaterial + { 0.00f, 0.50f, 0.00f, 1.0f }, // erSupportMaterialInterface + { 0.70f, 0.89f, 0.67f, 1.0f }, // erWipeTower + { 0.37f, 0.82f, 0.58f, 1.0f }, // erCustom + { 0.00f, 0.00f, 0.00f, 1.0f } // erMixed }}; const std::vector GCodeViewer::Options_Colors {{ - { 0.803f, 0.135f, 0.839f }, // Retractions - { 0.287f, 0.679f, 0.810f }, // Unretractions - { 0.900f, 0.900f, 0.900f }, // Seams - { 0.758f, 0.744f, 0.389f }, // ToolChanges - { 0.856f, 0.582f, 0.546f }, // ColorChanges - { 0.322f, 0.942f, 0.512f }, // PausePrints - { 0.886f, 0.825f, 0.262f } // CustomGCodes + { 0.803f, 0.135f, 0.839f, 1.0f }, // Retractions + { 0.287f, 0.679f, 0.810f, 1.0f }, // Unretractions + { 0.900f, 0.900f, 0.900f, 1.0f }, // Seams + { 0.758f, 0.744f, 0.389f, 1.0f }, // ToolChanges + { 0.856f, 0.582f, 0.546f, 1.0f }, // ColorChanges + { 0.322f, 0.942f, 0.512f, 1.0f }, // PausePrints + { 0.886f, 0.825f, 0.262f, 1.0f } // CustomGCodes }}; const std::vector GCodeViewer::Travel_Colors {{ - { 0.219f, 0.282f, 0.609f }, // Move - { 0.112f, 0.422f, 0.103f }, // Extrude - { 0.505f, 0.064f, 0.028f } // Retract + { 0.219f, 0.282f, 0.609f, 1.0f }, // Move + { 0.112f, 0.422f, 0.103f, 1.0f }, // Extrude + { 0.505f, 0.064f, 0.028f, 1.0f } // Retract }}; -const GCodeViewer::Color GCodeViewer::Wipe_Color = { 1.0f, 1.0f, 0.0f }; - const std::vector GCodeViewer::Range_Colors {{ - { 0.043f, 0.173f, 0.478f }, // bluish - { 0.075f, 0.349f, 0.522f }, - { 0.110f, 0.533f, 0.569f }, - { 0.016f, 0.839f, 0.059f }, - { 0.667f, 0.949f, 0.000f }, - { 0.988f, 0.975f, 0.012f }, - { 0.961f, 0.808f, 0.039f }, - { 0.890f, 0.533f, 0.125f }, - { 0.820f, 0.408f, 0.188f }, - { 0.761f, 0.322f, 0.235f }, - { 0.581f, 0.149f, 0.087f } // reddish + { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish + { 0.075f, 0.349f, 0.522f, 1.0f }, + { 0.110f, 0.533f, 0.569f, 1.0f }, + { 0.016f, 0.839f, 0.059f, 1.0f }, + { 0.667f, 0.949f, 0.000f, 1.0f }, + { 0.988f, 0.975f, 0.012f, 1.0f }, + { 0.961f, 0.808f, 0.039f, 1.0f }, + { 0.890f, 0.533f, 0.125f, 1.0f }, + { 0.820f, 0.408f, 0.188f, 1.0f }, + { 0.761f, 0.322f, 0.235f, 1.0f }, + { 0.581f, 0.149f, 0.087f, 1.0f } // reddish }}; +const GCodeViewer::Color GCodeViewer::Wipe_Color = { 1.0f, 1.0f, 0.0f, 1.0f }; +const GCodeViewer::Color GCodeViewer::Neutral_Color = { 0.25f, 0.25f, 0.25f, 1.0f }; + GCodeViewer::GCodeViewer() { +#if !ENABLE_SEAMS_USING_MODELS // initializes non OpenGL data of TBuffers // OpenGL data are initialized into render().init_gl_data() for (size_t i = 0; i < m_buffers.size(); ++i) { @@ -569,6 +561,10 @@ GCodeViewer::GCodeViewer() } set_toolpath_move_type_visible(EMoveType::Extrude, true); +#endif // !ENABLE_SEAMS_USING_MODELS + + m_extrusions.reset_role_visibility_flags(); + // m_sequential_view.skip_invisible_moves = true; } @@ -583,8 +579,9 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& // release gpu memory, if used reset(); - m_sequential_view.gcode_window.set_filename(gcode_result.filename); - m_sequential_view.gcode_window.load_gcode(); + m_sequential_view.gcode_window.load_gcode(gcode_result.filename, + // Stealing out lines_ends should be safe because this gcode_result is processed only once (see the 1st if in this function). + std::move(const_cast&>(gcode_result.lines_ends))); #if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER if (wxGetApp().is_gcode_viewer()) @@ -610,7 +607,7 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& if (!gcode_result.bed_shape.empty()) { // bed shape detected in the gcode bed_shape = gcode_result.bed_shape; - auto bundle = wxGetApp().preset_bundle; + const auto bundle = wxGetApp().preset_bundle; if (bundle != nullptr && !m_settings_ids.printer.empty()) { const Preset* preset = bundle->printers.find_preset(m_settings_ids.printer); if (preset != nullptr) { @@ -622,23 +619,23 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& else { // adjust printbed size in dependence of toolpaths bbox const double margin = 10.0; - Vec2d min(m_paths_bounding_box.min(0) - margin, m_paths_bounding_box.min(1) - margin); - Vec2d max(m_paths_bounding_box.max(0) + margin, m_paths_bounding_box.max(1) + margin); + const Vec2d min(m_paths_bounding_box.min.x() - margin, m_paths_bounding_box.min.y() - margin); + const Vec2d max(m_paths_bounding_box.max.x() + margin, m_paths_bounding_box.max.y() + margin); - Vec2d size = max - min; + const Vec2d size = max - min; bed_shape = { - { min(0), min(1) }, - { max(0), min(1) }, - { max(0), min(1) + 0.442265 * size[1]}, - { max(0) - 10.0, min(1) + 0.4711325 * size[1]}, - { max(0) + 10.0, min(1) + 0.5288675 * size[1]}, - { max(0), min(1) + 0.557735 * size[1]}, - { max(0), max(1) }, - { min(0) + 0.557735 * size[0], max(1)}, - { min(0) + 0.5288675 * size[0], max(1) - 10.0}, - { min(0) + 0.4711325 * size[0], max(1) + 10.0}, - { min(0) + 0.442265 * size[0], max(1)}, - { min(0), max(1) } }; + { min.x(), min.y() }, + { max.x(), min.y() }, + { max.x(), min.y() + 0.442265 * size.y()}, + { max.x() - 10.0, min.y() + 0.4711325 * size.y()}, + { max.x() + 10.0, min.y() + 0.5288675 * size.y()}, + { max.x(), min.y() + 0.557735 * size.y()}, + { max.x(), max.y() }, + { min.x() + 0.557735 * size.x(), max.y()}, + { min.x() + 0.5288675 * size.x(), max.y() - 10.0}, + { min.x() + 0.4711325 * size.x(), max.y() + 10.0}, + { min.x() + 0.442265 * size.x(), max.y()}, + { min.x(), max.y() } }; } wxGetApp().plater()->set_bed_shape(bed_shape, texture, model, gcode_result.bed_shape.empty()); @@ -647,7 +644,7 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& m_print_statistics = gcode_result.print_statistics; if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) { - float time = m_print_statistics.modes[static_cast(m_time_estimate_mode)].time; + const float time = m_print_statistics.modes[static_cast(m_time_estimate_mode)].time; if (time == 0.0f || short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time))) m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal; @@ -672,8 +669,8 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: // update tool colors m_tool_colors = decode_colors(str_tool_colors); - // ensure at least one (default) color is defined - if (m_tool_colors.empty()) + // ensure there are enough colors defined + while (m_tool_colors.size() < std::max(size_t(1), gcode_result.extruders_count)) m_tool_colors.push_back(decode_color("#FF8000")); // update ranges for coloring / legend @@ -689,11 +686,11 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: { case EMoveType::Extrude: { - m_extrusions.ranges.height.update_from(round_to_nearest(curr.height, 2)); - m_extrusions.ranges.width.update_from(round_to_nearest(curr.width, 2)); + m_extrusions.ranges.height.update_from(round_to_nearest_percent(curr.height)); + m_extrusions.ranges.width.update_from(round_to_nearest_percent(curr.width)); m_extrusions.ranges.fan_speed.update_from(curr.fan_speed); m_extrusions.ranges.temperature.update_from(curr.temperature); - m_extrusions.ranges.volumetric_rate.update_from(round_to_nearest(curr.volumetric_rate(), 2)); + m_extrusions.ranges.volumetric_rate.update_from(round_to_nearest_percent(curr.volumetric_rate())); [[fallthrough]]; } case EMoveType::Travel: @@ -741,7 +738,6 @@ void GCodeViewer::reset() m_extruder_ids = std::vector(); m_filament_diameters = std::vector(); m_filament_densities = std::vector(); - m_extrusions.reset_role_visibility_flags(); m_extrusions.reset_ranges(); m_shells.volumes.clear(); m_layers.reset(); @@ -757,13 +753,14 @@ void GCodeViewer::reset() #endif // ENABLE_GCODE_VIEWER_STATISTICS } -void GCodeViewer::render() const +void GCodeViewer::render() { auto init_gl_data = [this]() { // initializes opengl data of TBuffers for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& buffer = const_cast(m_buffers[i]); - switch (buffer_type(i)) + TBuffer& buffer = m_buffers[i]; + EMoveType type = buffer_type(i); + switch (type) { default: { break; } case EMoveType::Tool_change: @@ -773,33 +770,85 @@ void GCodeViewer::render() const case EMoveType::Retract: case EMoveType::Unretract: case EMoveType::Seam: { +#if ENABLE_SEAMS_USING_MODELS +#if ENABLE_SEAMS_USING_BATCHED_MODELS + if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) { + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::InstancedModel; + buffer.shader = "gouraud_light_instanced"; + buffer.model.model.init_from(diamond(16)); + buffer.model.color = option_color(type); + buffer.model.instances.format = InstanceVBuffer::EFormat::InstancedModel; + } + else { + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::BatchedModel; + buffer.vertices.format = VBuffer::EFormat::PositionNormal3; + buffer.shader = "gouraud_light"; + + buffer.model.data = diamond(16); + buffer.model.color = option_color(type); + buffer.model.instances.format = InstanceVBuffer::EFormat::BatchedModel; + } + break; +#else + if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) { + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Model; + buffer.shader = "gouraud_light_instanced"; + buffer.model.model.init_from(diamond(16)); + buffer.model.color = option_color(type); + } + else { + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point; + buffer.vertices.format = VBuffer::EFormat::Position; + buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; + } + break; +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS +#else + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point; + buffer.vertices.format = VBuffer::EFormat::Position; buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; +#endif // ENABLE_SEAMS_USING_MODELS } case EMoveType::Wipe: case EMoveType::Extrude: { +#if ENABLE_SEAMS_USING_MODELS + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle; + buffer.vertices.format = VBuffer::EFormat::PositionNormal3; +#endif // ENABLE_SEAMS_USING_MODELS buffer.shader = "gouraud_light"; break; } case EMoveType::Travel: { +#if ENABLE_SEAMS_USING_MODELS + buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line; + buffer.vertices.format = VBuffer::EFormat::PositionNormal1; +#endif // ENABLE_SEAMS_USING_MODELS buffer.shader = "toolpaths_lines"; break; } } + +#if ENABLE_SEAMS_USING_MODELS + set_toolpath_move_type_visible(EMoveType::Extrude, true); +#endif // ENABLE_SEAMS_USING_MODELS } // initializes tool marker - const_cast(&m_sequential_view)->marker.init(); + m_sequential_view.marker.init(); // initializes point sizes std::array point_sizes; ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data()); - *const_cast*>(&m_detected_point_sizes) = { static_cast(point_sizes[0]), static_cast(point_sizes[1]) }; - *const_cast(&m_gl_data_initialized) = true; + m_detected_point_sizes = { static_cast(point_sizes[0]), static_cast(point_sizes[1]) }; + m_gl_data_initialized = true; }; #if ENABLE_GCODE_VIEWER_STATISTICS - const_cast(&m_statistics)->reset_opengl(); + m_statistics.reset_opengl(); +#if ENABLE_SEAMS_USING_MODELS + m_statistics.total_instances_gpu_size = 0; +#endif // ENABLE_SEAMS_USING_MODELS #endif // ENABLE_GCODE_VIEWER_STATISTICS // OpenGL data must be initialized after the glContext has been created. @@ -815,10 +864,9 @@ void GCodeViewer::render() const render_shells(); float legend_height = 0.0f; render_legend(legend_height); - SequentialView* sequential_view = const_cast(&m_sequential_view); - if (sequential_view->current.last != sequential_view->endpoints.last) { - sequential_view->marker.set_world_position(sequential_view->current_position); - sequential_view->render(legend_height); + if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { + m_sequential_view.marker.set_world_position(m_sequential_view.current_position); + m_sequential_view.render(legend_height); } #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); @@ -844,8 +892,8 @@ void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned in return false; }; - int first_diff = static_cast(first) - static_cast(m_sequential_view.last_current.first); - int last_diff = static_cast(last) - static_cast(m_sequential_view.last_current.last); + const int first_diff = static_cast(first) - static_cast(m_sequential_view.last_current.first); + const int last_diff = static_cast(last) - static_cast(m_sequential_view.last_current.last); unsigned int new_first = first; unsigned int new_last = last; @@ -1036,13 +1084,13 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const // save vertices to file fprintf(fp, "\n# vertices\n"); for (const Vec3f& v : out_vertices) { - fprintf(fp, "v %g %g %g\n", v[0], v[1], v[2]); + fprintf(fp, "v %g %g %g\n", v.x(), v.y(), v.z()); } // save normals to file fprintf(fp, "\n# normals\n"); for (const Vec3f& n : out_normals) { - fprintf(fp, "vn %g %g %g\n", n[0], n[1], n[2]); + fprintf(fp, "vn %g %g %g\n", n.x(), n.y(), n.z()); } size_t i = 0; @@ -1091,16 +1139,6 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const fclose(fp); } -void GCodeViewer::start_mapping_gcode_window() -{ - m_sequential_view.gcode_window.load_gcode(); -} - -void GCodeViewer::stop_mapping_gcode_window() -{ - m_sequential_view.gcode_window.stop_mapping_file(); -} - void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { // max index buffer size, in bytes @@ -1124,9 +1162,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // format data into the buffers to be rendered as points auto add_vertices_as_point = [](const GCodeProcessor::MoveVertex& curr, VertexBuffer& vertices) { - vertices.push_back(curr.position[0]); - vertices.push_back(curr.position[1]); - vertices.push_back(curr.position[2]); + vertices.push_back(curr.position.x()); + vertices.push_back(curr.position.y()); + vertices.push_back(curr.position.z()); }; auto add_indices_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { @@ -1137,13 +1175,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // format data into the buffers to be rendered as lines auto add_vertices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, VertexBuffer& vertices) { // x component of the normal to the current segment (the normal is parallel to the XY plane) - float normal_x = (curr.position - prev.position).normalized()[1]; + const float normal_x = (curr.position - prev.position).normalized().y(); auto add_vertex = [&vertices, normal_x](const GCodeProcessor::MoveVertex& vertex) { // add position - vertices.push_back(vertex.position[0]); - vertices.push_back(vertex.position[1]); - vertices.push_back(vertex.position[2]); + vertices.push_back(vertex.position.x()); + vertices.push_back(vertex.position.y()); + vertices.push_back(vertex.position.z()); // add normal x component vertices.push_back(normal_x); }; @@ -1155,9 +1193,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) }; auto add_indices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) { // add starting index - indices.push_back(static_cast(indices.size())); + indices.push_back(static_cast(indices.size())); buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1); buffer.paths.back().sub_paths.front().first.position = prev.position; } @@ -1165,11 +1203,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) Path& last_path = buffer.paths.back(); if (last_path.sub_paths.front().first.i_id != last_path.sub_paths.back().last.i_id) { // add previous index - indices.push_back(static_cast(indices.size())); + indices.push_back(static_cast(indices.size())); } // add current index - indices.push_back(static_cast(indices.size())); + indices.push_back(static_cast(indices.size())); last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; }; @@ -1177,13 +1215,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) auto add_vertices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id) { auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { // append position - vertices.push_back(position[0]); - vertices.push_back(position[1]); - vertices.push_back(position[2]); + vertices.push_back(position.x()); + vertices.push_back(position.y()); + vertices.push_back(position.z()); // append normal - vertices.push_back(normal[0]); - vertices.push_back(normal[1]); - vertices.push_back(normal[2]); + vertices.push_back(normal.x()); + vertices.push_back(normal.y()); + vertices.push_back(normal.z()); }; if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { @@ -1193,19 +1231,19 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) Path& last_path = buffer.paths.back(); - Vec3f dir = (curr.position - prev.position).normalized(); - Vec3f right = Vec3f(dir[1], -dir[0], 0.0f).normalized(); - Vec3f left = -right; - Vec3f up = right.cross(dir); - Vec3f down = -up; - float half_width = 0.5f * last_path.width; - float half_height = 0.5f * last_path.height; - Vec3f prev_pos = prev.position - half_height * up; - Vec3f curr_pos = curr.position - half_height * up; - Vec3f d_up = half_height * up; - Vec3f d_down = -half_height * up; - Vec3f d_right = half_width * right; - Vec3f d_left = -half_width * right; + const Vec3f dir = (curr.position - prev.position).normalized(); + const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized(); + const Vec3f left = -right; + const Vec3f up = right.cross(dir); + const Vec3f down = -up; + const float half_width = 0.5f * last_path.width; + const float half_height = 0.5f * last_path.height; + const Vec3f prev_pos = prev.position - half_height * up; + const Vec3f curr_pos = curr.position - half_height * up; + const Vec3f d_up = half_height * up; + const Vec3f d_down = -half_height * up; + const Vec3f d_right = half_width * right; + const Vec3f d_left = -half_width * right; // vertices 1st endpoint if (last_path.vertices_count() == 1 || vertices.empty()) { @@ -1284,14 +1322,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) Path& last_path = buffer.paths.back(); - Vec3f dir = (curr.position - prev.position).normalized(); - Vec3f right = Vec3f(dir[1], -dir[0], 0.0f).normalized(); - Vec3f up = right.cross(dir); - float sq_length = (curr.position - prev.position).squaredNorm(); + const Vec3f dir = (curr.position - prev.position).normalized(); + const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized(); + const Vec3f up = right.cross(dir); + const float sq_length = (curr.position - prev.position).squaredNorm(); const std::array first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); const std::array non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); - bool is_first_segment = (last_path.vertices_count() == 1); + const bool is_first_segment = (last_path.vertices_count() == 1); if (is_first_segment || vbuffer_size == 0) { // 1st segment or restart into a new vertex buffer // =============================================== @@ -1310,20 +1348,20 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // any other segment // ================= float displacement = 0.0f; - float cos_dir = prev_dir.dot(dir); + const float cos_dir = prev_dir.dot(dir); if (cos_dir > -0.9998477f) { // if the angle between adjacent segments is smaller than 179 degrees - Vec3f med_dir = (prev_dir + dir).normalized(); - float half_width = 0.5f * last_path.width; + const Vec3f med_dir = (prev_dir + dir).normalized(); + const float half_width = 0.5f * last_path.width; displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); } - float sq_displacement = sqr(displacement); - bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_length; + const float sq_displacement = sqr(displacement); + const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_length; - bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; + const bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; // whether the angle between adjacent segments is greater than 45 degrees - bool is_sharp = cos_dir < 0.7071068f; + const bool is_sharp = cos_dir < 0.7071068f; bool right_displaced = false; bool left_displaced = false; @@ -1371,6 +1409,66 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) sq_prev_length = sq_length; }; +#if ENABLE_SEAMS_USING_MODELS + // format data into the buffers to be rendered as instanced model + auto add_model_instance = [](const GCodeProcessor::MoveVertex& curr, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { + // append position + instances.push_back(curr.position.x()); + instances.push_back(curr.position.y()); + instances.push_back(curr.position.z()); + // append width + instances.push_back(curr.width); + // append height + instances.push_back(curr.height); + + // append id + instances_ids.push_back(move_id); + }; + +#if ENABLE_SEAMS_USING_BATCHED_MODELS + // format data into the buffers to be rendered as batched model + auto add_vertices_as_model_batch = [](const GCodeProcessor::MoveVertex& curr, const GLModel::InitializationData& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { + const double width = static_cast(1.5f * curr.width); + const double height = static_cast(1.5f * curr.height); + + const Transform3d trafo = Geometry::assemble_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast(), Vec3d::Zero(), { width, width, height }); + const Eigen::Matrix normal_matrix = trafo.matrix().template block<3, 3>(0, 0).inverse().transpose(); + + for (const auto& entity : data.entities) { + // append vertices + for (size_t i = 0; i < entity.positions.size(); ++i) { + // append position + const Vec3d position = trafo * entity.positions[i].cast(); + vertices.push_back(static_cast(position.x())); + vertices.push_back(static_cast(position.y())); + vertices.push_back(static_cast(position.z())); + + // append normal + const Vec3d normal = normal_matrix * entity.normals[i].cast(); + vertices.push_back(static_cast(normal.x())); + vertices.push_back(static_cast(normal.y())); + vertices.push_back(static_cast(normal.z())); + } + } + + // append instance position + instances.push_back(curr.position.x()); + instances.push_back(curr.position.y()); + instances.push_back(curr.position.z()); + // append instance id + instances_ids.push_back(move_id); + }; + + auto add_indices_as_model_batch = [](const GLModel::InitializationData& data, IndexBuffer& indices, IBufferType base_index) { + for (const auto& entity : data.entities) { + for (size_t i = 0; i < entity.indices.size(); ++i) { + indices.push_back(static_cast(entity.indices[i] + base_index)); + } + } + }; +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS +#endif // ENABLE_SEAMS_USING_MODELS + #if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessor::MoveVertex); @@ -1413,6 +1511,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) std::vector vertices(m_buffers.size()); std::vector indices(m_buffers.size()); +#if ENABLE_SEAMS_USING_MODELS + std::vector instances(m_buffers.size()); + std::vector instances_ids(m_buffers.size()); +#endif // ENABLE_SEAMS_USING_MODELS std::vector options_zs; // toolpaths data -> extract vertices from result @@ -1434,9 +1536,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) progress_count = 0; } - unsigned char id = buffer_id(curr.type); + const unsigned char id = buffer_id(curr.type); TBuffer& t_buffer = m_buffers[id]; MultiVertexBuffer& v_multibuffer = vertices[id]; +#if ENABLE_SEAMS_USING_MODELS + InstanceBuffer& inst_buffer = instances[id]; + InstanceIdBuffer& inst_id_buffer = instances_ids[id]; +#endif // ENABLE_SEAMS_USING_MODELS // ensure there is at least one vertex buffer if (v_multibuffer.empty()) @@ -1444,7 +1550,12 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer // add another vertex buffer +#if ENABLE_SEAMS_USING_BATCHED_MODELS + size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes(); + if (v_multibuffer.back().size() * sizeof(float) > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) { +#else if (v_multibuffer.back().size() * sizeof(float) > t_buffer.vertices.max_size_bytes() - t_buffer.max_vertices_per_segment_size_bytes()) { +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS v_multibuffer.push_back(VertexBuffer()); if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { Path& last_path = t_buffer.paths.back(); @@ -1460,6 +1571,35 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) case TBuffer::ERenderPrimitiveType::Point: { add_vertices_as_point(curr, v_buffer); break; } case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; } case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast(v_multibuffer.size()) - 1, v_buffer, i); break; } +#if ENABLE_SEAMS_USING_MODELS +#if ENABLE_SEAMS_USING_BATCHED_MODELS + case TBuffer::ERenderPrimitiveType::InstancedModel: + { + add_model_instance(curr, inst_buffer, inst_id_buffer, i); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.instances_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + break; + } + case TBuffer::ERenderPrimitiveType::BatchedModel: + { + add_vertices_as_model_batch(curr, t_buffer.model.data, v_buffer, inst_buffer, inst_id_buffer, i); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.batched_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + break; + } +#else + case TBuffer::ERenderPrimitiveType::Model: + { + add_model_instance(curr, inst_buffer, inst_id_buffer, i); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.instances_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + break; + } +#endif // ENABLE_SEAMS_USING_MODELS +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS } // collect options zs for later use @@ -1476,24 +1616,24 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) return Vec3f(vertices[offset + 0], vertices[offset + 1], vertices[offset + 2]); }; auto update_position_at = [](VertexBuffer& vertices, size_t offset, const Vec3f& position) { - vertices[offset + 0] = position[0]; - vertices[offset + 1] = position[1]; - vertices[offset + 2] = position[2]; + vertices[offset + 0] = position.x(); + vertices[offset + 1] = position.y(); + vertices[offset + 2] = position.z(); }; auto match_right_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path, size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) { if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; // offset into the vertex buffer of the next segment 1st vertex - size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; + const size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; // offset into the vertex buffer of the right vertex of the previous segment - size_t prev_right_offset = prev_sub_path.last.i_id - next_1st_offset - 3 * vertex_size_floats; + const size_t prev_right_offset = prev_sub_path.last.i_id - next_1st_offset - 3 * vertex_size_floats; // new position of the right vertices - Vec3f shared_vertex = extract_position_at(vbuffer, prev_right_offset) + displacement_vec; + const Vec3f shared_vertex = extract_position_at(vbuffer, prev_right_offset) + displacement_vec; // update previous segment update_position_at(vbuffer, prev_right_offset, shared_vertex); // offset into the vertex buffer of the right vertex of the next segment - size_t next_right_offset = next_sub_path.last.i_id - next_1st_offset; + const size_t next_right_offset = next_sub_path.last.i_id - next_1st_offset; // update next segment update_position_at(vbuffer, next_right_offset, shared_vertex); } @@ -1501,13 +1641,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; // offset into the previous vertex buffer of the right vertex of the previous segment - size_t prev_right_offset = prev_sub_path.last.i_id - 3 * vertex_size_floats; + const size_t prev_right_offset = prev_sub_path.last.i_id - 3 * vertex_size_floats; // new position of the right vertices - Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_right_offset) + displacement_vec; + const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_right_offset) + displacement_vec; // update previous segment update_position_at(prev_vbuffer, prev_right_offset, shared_vertex); // offset into the next vertex buffer of the right vertex of the next segment - size_t next_right_offset = next_sub_path.first.i_id + 1 * vertex_size_floats; + const size_t next_right_offset = next_sub_path.first.i_id + 1 * vertex_size_floats; // update next segment update_position_at(next_vbuffer, next_right_offset, shared_vertex); } @@ -1517,15 +1657,15 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id]; // offset into the vertex buffer of the next segment 1st vertex - size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; + const size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats; // offset into the vertex buffer of the left vertex of the previous segment - size_t prev_left_offset = prev_sub_path.last.i_id - next_1st_offset - 1 * vertex_size_floats; + const size_t prev_left_offset = prev_sub_path.last.i_id - next_1st_offset - 1 * vertex_size_floats; // new position of the left vertices - Vec3f shared_vertex = extract_position_at(vbuffer, prev_left_offset) + displacement_vec; + const Vec3f shared_vertex = extract_position_at(vbuffer, prev_left_offset) + displacement_vec; // update previous segment update_position_at(vbuffer, prev_left_offset, shared_vertex); // offset into the vertex buffer of the left vertex of the next segment - size_t next_left_offset = next_sub_path.last.i_id - next_1st_offset + 1 * vertex_size_floats; + const size_t next_left_offset = next_sub_path.last.i_id - next_1st_offset + 1 * vertex_size_floats; // update next segment update_position_at(vbuffer, next_left_offset, shared_vertex); } @@ -1533,13 +1673,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id]; VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id]; // offset into the previous vertex buffer of the left vertex of the previous segment - size_t prev_left_offset = prev_sub_path.last.i_id - 1 * vertex_size_floats; + const size_t prev_left_offset = prev_sub_path.last.i_id - 1 * vertex_size_floats; // new position of the left vertices - Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_left_offset) + displacement_vec; + const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_left_offset) + displacement_vec; // update previous segment update_position_at(prev_vbuffer, prev_left_offset, shared_vertex); // offset into the next vertex buffer of the left vertex of the next segment - size_t next_left_offset = next_sub_path.first.i_id + 3 * vertex_size_floats; + const size_t next_left_offset = next_sub_path.first.i_id + 3 * vertex_size_floats; // update next segment update_position_at(next_vbuffer, next_left_offset, shared_vertex); } @@ -1551,8 +1691,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // to two different vertex buffers size_t prev_sub_path_id = 0; size_t next_sub_path_id = 0; - size_t path_vertices_count = path.vertices_count(); - float half_width = 0.5f * path.width; + const size_t path_vertices_count = path.vertices_count(); + const float half_width = 0.5f * path.width; for (size_t j = 1; j < path_vertices_count - 1; ++j) { size_t curr_s_id = path.sub_paths.front().first.s_id + j; const Vec3f& prev = gcode_result.moves[curr_s_id - 1].position; @@ -1567,16 +1707,16 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) const Path::Sub_Path& prev_sub_path = path.sub_paths[prev_sub_path_id]; const Path::Sub_Path& next_sub_path = path.sub_paths[next_sub_path_id]; - Vec3f prev_dir = (curr - prev).normalized(); - Vec3f prev_right = Vec3f(prev_dir[1], -prev_dir[0], 0.0f).normalized(); - Vec3f prev_up = prev_right.cross(prev_dir); + const Vec3f prev_dir = (curr - prev).normalized(); + const Vec3f prev_right = Vec3f(prev_dir.y(), -prev_dir.x(), 0.0f).normalized(); + const Vec3f prev_up = prev_right.cross(prev_dir); - Vec3f next_dir = (next - curr).normalized(); + const Vec3f next_dir = (next - curr).normalized(); - bool is_right_turn = prev_up.dot(prev_dir.cross(next_dir)) <= 0.0f; - float cos_dir = prev_dir.dot(next_dir); + const bool is_right_turn = prev_up.dot(prev_dir.cross(next_dir)) <= 0.0f; + const float cos_dir = prev_dir.dot(next_dir); // whether the angle between adjacent segments is greater than 45 degrees - bool is_sharp = cos_dir < 0.7071068f; + const bool is_sharp = cos_dir < 0.7071068f; float displacement = 0.0f; if (cos_dir > -0.9998477f) { @@ -1585,10 +1725,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) displacement = half_width * ::tan(::acos(std::clamp(next_dir.dot(med_dir), -1.0f, 1.0f))); } - float sq_prev_length = (curr - prev).squaredNorm(); - float sq_next_length = (next - curr).squaredNorm(); - float sq_displacement = sqr(displacement); - bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_next_length; + const float sq_prev_length = (curr - prev).squaredNorm(); + const float sq_next_length = (next - curr).squaredNorm(); + const float sq_displacement = sqr(displacement); + const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_next_length; if (can_displace) { // displacement to apply to the vertices to match @@ -1638,31 +1778,62 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } } - // send vertices data to gpu + // send vertices data to gpu, where needed for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& t_buffer = m_buffers[i]; - - const MultiVertexBuffer& v_multibuffer = vertices[i]; - for (const VertexBuffer& v_buffer : v_multibuffer) { - size_t size_elements = v_buffer.size(); - size_t size_bytes = size_elements * sizeof(float); - size_t vertices_count = size_elements / t_buffer.vertices.vertex_size_floats(); - t_buffer.vertices.count += vertices_count; +#if ENABLE_SEAMS_USING_MODELS +#if ENABLE_SEAMS_USING_BATCHED_MODELS + if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { + const InstanceBuffer& inst_buffer = instances[i]; + if (!inst_buffer.empty()) { + t_buffer.model.instances.buffer = inst_buffer; + t_buffer.model.instances.s_ids = instances_ids[i]; + } + } + else { + if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { + const InstanceBuffer& inst_buffer = instances[i]; + if (!inst_buffer.empty()) { + t_buffer.model.instances.buffer = inst_buffer; + t_buffer.model.instances.s_ids = instances_ids[i]; + } + } +#else + if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Model) { + const InstanceBuffer& inst_buffer = instances[i]; + if (!inst_buffer.empty()) { + t_buffer.model.instances.buffer = inst_buffer; + t_buffer.model.instances.s_ids = instances_ids[i]; + } + } + else { +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS +#endif // ENABLE_SEAMS_USING_MODELS + const MultiVertexBuffer& v_multibuffer = vertices[i]; + for (const VertexBuffer& v_buffer : v_multibuffer) { + const size_t size_elements = v_buffer.size(); + const size_t size_bytes = size_elements * sizeof(float); + const size_t vertices_count = size_elements / t_buffer.vertices.vertex_size_floats(); + t_buffer.vertices.count += vertices_count; #if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.total_vertices_gpu_size += static_cast(size_bytes); - m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast(size_bytes)); - ++m_statistics.vbuffers_count; + m_statistics.total_vertices_gpu_size += static_cast(size_bytes); + m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast(size_bytes)); + ++m_statistics.vbuffers_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS - GLuint id = 0; - glsafe(::glGenBuffers(1, &id)); - t_buffer.vertices.vbos.push_back(static_cast(id)); - t_buffer.vertices.sizes.push_back(size_bytes); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, size_bytes, v_buffer.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + GLuint id = 0; + glsafe(::glGenBuffers(1, &id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, size_bytes, v_buffer.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + t_buffer.vertices.vbos.push_back(static_cast(id)); + t_buffer.vertices.sizes.push_back(size_bytes); + } +#if ENABLE_SEAMS_USING_MODELS } +#endif // ENABLE_SEAMS_USING_MODELS } #if ENABLE_GCODE_VIEWER_STATISTICS @@ -1673,6 +1844,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // dismiss vertices data, no more needed std::vector().swap(vertices); +#if ENABLE_SEAMS_USING_MODELS + std::vector().swap(instances); + std::vector().swap(instances_ids); +#endif // ENABLE_SEAMS_USING_MODELS // toolpaths data -> extract indices from result // paths may have been filled while extracting vertices, @@ -1709,7 +1884,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) progress_count = 0; } - unsigned char id = buffer_id(curr.type); + const unsigned char id = buffer_id(curr.type); TBuffer& t_buffer = m_buffers[id]; MultiIndexBuffer& i_multibuffer = indices[id]; CurrVertexBuffer& curr_vertex_buffer = curr_vertex_buffers[id]; @@ -1718,15 +1893,28 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // ensure there is at least one index buffer if (i_multibuffer.empty()) { i_multibuffer.push_back(IndexBuffer()); - vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); +#if ENABLE_SEAMS_USING_MODELS + if (!t_buffer.vertices.vbos.empty()) +#endif // ENABLE_SEAMS_USING_MODELS + vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); } // if adding the indices for the current segment exceeds the threshold size of the current index buffer // create another index buffer +#if ENABLE_SEAMS_USING_BATCHED_MODELS + size_t indiced_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.indices_size_bytes() : t_buffer.max_indices_per_segment_size_bytes(); + if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - indiced_size_to_add) { +#else if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - t_buffer.max_indices_per_segment_size_bytes()) { +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS i_multibuffer.push_back(IndexBuffer()); vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); +#if ENABLE_SEAMS_USING_BATCHED_MODELS + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point && + t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { +#else if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) { +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS Path& last_path = t_buffer.paths.back(); last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, i - 1); } @@ -1734,14 +1922,24 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer // create another index buffer +#if ENABLE_SEAMS_USING_BATCHED_MODELS + size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes(); + if (curr_vertex_buffer.second * t_buffer.vertices.vertex_size_bytes() > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) { +#else if (curr_vertex_buffer.second * t_buffer.vertices.vertex_size_bytes() > t_buffer.vertices.max_size_bytes() - t_buffer.max_vertices_per_segment_size_bytes()) { +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS i_multibuffer.push_back(IndexBuffer()); ++curr_vertex_buffer.first; curr_vertex_buffer.second = 0; vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); +#if ENABLE_SEAMS_USING_BATCHED_MODELS + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point && + t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) { +#else if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) { +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS Path& last_path = t_buffer.paths.back(); last_path.add_sub_path(prev, static_cast(i_multibuffer.size()) - 1, 0, i - 1); } @@ -1765,6 +1963,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, i); break; } +#if ENABLE_SEAMS_USING_BATCHED_MODELS + case TBuffer::ERenderPrimitiveType::BatchedModel: { + add_indices_as_model_batch(t_buffer.model.data, i_buffer, curr_vertex_buffer.second); + curr_vertex_buffer.second += t_buffer.model.data.vertices_count(); + break; + } +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + default: { break; } } } @@ -1777,28 +1983,38 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // toolpaths data -> send indices data to gpu for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& t_buffer = m_buffers[i]; - const MultiIndexBuffer& i_multibuffer = indices[i]; - for (const IndexBuffer& i_buffer : i_multibuffer) { - size_t size_elements = i_buffer.size(); - size_t size_bytes = size_elements * sizeof(IBufferType); +#if ENABLE_SEAMS_USING_MODELS +#if ENABLE_SEAMS_USING_BATCHED_MODELS + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel) { +#else + if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Model) { +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS +#endif // ENABLE_SEAMS_USING_MODELS + const MultiIndexBuffer& i_multibuffer = indices[i]; + for (const IndexBuffer& i_buffer : i_multibuffer) { + const size_t size_elements = i_buffer.size(); + const size_t size_bytes = size_elements * sizeof(IBufferType); - // stores index buffer informations into TBuffer - t_buffer.indices.push_back(IBuffer()); - IBuffer& ibuf = t_buffer.indices.back(); - ibuf.count = size_elements; - ibuf.vbo = vbo_indices[i][t_buffer.indices.size() - 1]; + // stores index buffer informations into TBuffer + t_buffer.indices.push_back(IBuffer()); + IBuffer& ibuf = t_buffer.indices.back(); + ibuf.count = size_elements; + ibuf.vbo = vbo_indices[i][t_buffer.indices.size() - 1]; #if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.total_indices_gpu_size += static_cast(size_bytes); - m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast(size_bytes)); - ++m_statistics.ibuffers_count; + m_statistics.total_indices_gpu_size += static_cast(size_bytes); + m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast(size_bytes)); + ++m_statistics.ibuffers_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS - glsafe(::glGenBuffers(1, &ibuf.ibo)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf.ibo)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_bytes, i_buffer.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + glsafe(::glGenBuffers(1, &ibuf.ibo)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf.ibo)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_bytes, i_buffer.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } +#if ENABLE_SEAMS_USING_MODELS } +#endif // ENABLE_SEAMS_USING_MODELS } if (progress_dialog != nullptr) { @@ -1844,7 +2060,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (move.type == EMoveType::Extrude) { // layers zs const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back(); - double z = static_cast(move.position[2]); + const double z = static_cast(move.position.z()); if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) m_layers.append(z, { last_travel_s_id, i }); else @@ -1881,7 +2097,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (!options_zs.empty()) { TBuffer& extrude_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; for (Path& path : extrude_buffer.paths) { - float z = path.sub_paths.front().first.position[2]; + const float z = path.sub_paths.front().first.position.z(); if (std::find_if(options_zs.begin(), options_zs.end(), [z](float f) { return f - EPSILON <= z && z <= f + EPSILON; }) != options_zs.end()) path.cp_color_id = 255 - path.cp_color_id; } @@ -1918,12 +2134,12 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) { // adds wipe tower's volume - double max_z = print.objects()[0]->model_object()->get_model()->bounding_box().max(2); + const double max_z = print.objects()[0]->model_object()->get_model()->bounding_box().max(2); const PrintConfig& config = print.config(); - size_t extruders_count = config.nozzle_diameter.size(); - if ((extruders_count > 1) && config.wipe_tower && !config.complete_objects) { - float depth = print.wipe_tower_data(extruders_count).depth; - float brim_width = print.wipe_tower_data(extruders_count).brim_width; + const size_t extruders_count = config.nozzle_diameter.size(); + if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) { + const float depth = print.wipe_tower_data(extruders_count).depth; + const float brim_width = print.wipe_tower_data(extruders_count).brim_width; m_shells.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, !print.is_step_done(psWipeTower), brim_width, initialized); @@ -1968,18 +2184,14 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } case EViewType::ColorPrint: { - if (path.cp_color_id >= static_cast(m_tool_colors.size())) { - color = { 0.5f, 0.5f, 0.5f }; -// // complementary color -// color = m_tool_colors[255 - path.cp_color_id]; -// color = { 1.0f - color[0], 1.0f - color[1], 1.0f - color[2] }; - } + if (path.cp_color_id >= static_cast(m_tool_colors.size())) + color = { 0.5f, 0.5f, 0.5f, 1.0f }; else color = m_tool_colors[path.cp_color_id]; break; } - default: { color = { 1.0f, 1.0f, 1.0f }; break; } + default: { color = { 1.0f, 1.0f, 1.0f, 1.0f }; break; } } return color; @@ -2018,8 +2230,8 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool path.sub_paths.back().last = buffer.paths[last].sub_paths.back().last; } - size_t min_s_id = m_layers.get_endpoints_at(min_id).first; - size_t max_s_id = m_layers.get_endpoints_at(max_id).last; + const size_t min_s_id = m_layers.get_endpoints_at(min_id).first; + const size_t max_s_id = m_layers.get_endpoints_at(max_id).last; return (min_s_id <= path.sub_paths.front().first.s_id && path.sub_paths.front().first.s_id <= max_s_id) || (min_s_id <= path.sub_paths.back().last.s_id && path.sub_paths.back().last.s_id <= max_s_id); @@ -2028,9 +2240,12 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool #if ENABLE_GCODE_VIEWER_STATISTICS Statistics* statistics = const_cast(&m_statistics); statistics->render_paths_size = 0; +#if ENABLE_SEAMS_USING_MODELS + statistics->models_instances_size = 0; +#endif // ENABLE_SEAMS_USING_MODELS #endif // ENABLE_GCODE_VIEWER_STATISTICS - bool top_layer_only = get_app_config()->get("seq_top_layer_only") == "1"; + const bool top_layer_only = get_app_config()->get("seq_top_layer_only") == "1"; SequentialView::Endpoints global_endpoints = { m_moves_count , 0 }; SequentialView::Endpoints top_layer_endpoints = global_endpoints; @@ -2048,84 +2263,136 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool if (!buffer.visible) continue; - for (size_t i = 0; i < buffer.paths.size(); ++i) { - const Path& path = buffer.paths[i]; - if (path.type == EMoveType::Travel) { - if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1])) +#if ENABLE_SEAMS_USING_MODELS +#if ENABLE_SEAMS_USING_BATCHED_MODELS + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || + buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { +#else + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Model) { +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + for (size_t id : buffer.model.instances.s_ids) { + if (id < m_layers.get_endpoints_at(m_layers_z_range[0]).first || m_layers.get_endpoints_at(m_layers_z_range[1]).last < id) continue; + + global_endpoints.first = std::min(global_endpoints.first, id); + global_endpoints.last = std::max(global_endpoints.last, id); + + if (top_layer_only) { + if (id < m_layers.get_endpoints_at(m_layers_z_range[1]).first || m_layers.get_endpoints_at(m_layers_z_range[1]).last < id) + continue; + + top_layer_endpoints.first = std::min(top_layer_endpoints.first, id); + top_layer_endpoints.last = std::max(top_layer_endpoints.last, id); + } } - else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1])) - continue; - - if (path.type == EMoveType::Extrude && !is_visible(path)) - continue; - - // store valid path - for (size_t j = 0; j < path.sub_paths.size(); ++j) { - paths.push_back({ static_cast(b), path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); - } - - global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id); - global_endpoints.last = std::max(global_endpoints.last, path.sub_paths.back().last.s_id); - - if (top_layer_only) { + } + else { +#endif // ENABLE_SEAMS_USING_MODELS + for (size_t i = 0; i < buffer.paths.size(); ++i) { + const Path& path = buffer.paths[i]; if (path.type == EMoveType::Travel) { - if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) { + if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1])) + continue; + } + else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1])) + continue; + + if (path.type == EMoveType::Extrude && !is_visible(path)) + continue; + + // store valid path + for (size_t j = 0; j < path.sub_paths.size(); ++j) { + paths.push_back({ static_cast(b), path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); + } + + global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id); + global_endpoints.last = std::max(global_endpoints.last, path.sub_paths.back().last.s_id); + + if (top_layer_only) { + if (path.type == EMoveType::Travel) { + if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) { + top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); + top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); + } + } + else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) { top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); } } - else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) { - top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id); - } } +#if ENABLE_SEAMS_USING_MODELS } +#endif // ENABLE_SEAMS_USING_MODELS } // update current sequential position sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first; sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last; - // get the world position from gpu + // get the world position from the vertex buffer bool found = false; for (const TBuffer& buffer : m_buffers) { - // searches the path containing the current position - for (const Path& path : buffer.paths) { - if (path.contains(m_sequential_view.current.last)) { - int sub_path_id = path.get_id_of_sub_path_containing(m_sequential_view.current.last); - if (sub_path_id != -1) { - const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; - unsigned int offset = static_cast(m_sequential_view.current.last - sub_path.first.s_id); - if (offset > 0) { - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) - offset = 2 * offset - 1; - else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - unsigned int indices_count = buffer.indices_per_segment(); - offset = indices_count * (offset - 1) + (indices_count - 2); - if (sub_path_id == 0) - offset += 6; // add 2 triangles for starting cap - } - } - offset += static_cast(sub_path.first.i_id); - - // gets the vertex index from the index buffer on gpu - const IBuffer& i_buffer = buffer.indices[sub_path.first.b_id]; - unsigned int index = 0; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&index))); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // gets the position from the vertices buffer on gpu - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(sequential_view->current_position.data()))); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +#if ENABLE_SEAMS_USING_MODELS +#if ENABLE_SEAMS_USING_BATCHED_MODELS + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel || + buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { +#else + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Model) { +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + for (size_t i = 0; i < buffer.model.instances.s_ids.size(); ++i) { + if (buffer.model.instances.s_ids[i] == m_sequential_view.current.last) { + size_t offset = i * buffer.model.instances.instance_size_floats(); + sequential_view->current_position.x() = buffer.model.instances.buffer[offset + 0]; + sequential_view->current_position.y() = buffer.model.instances.buffer[offset + 1]; + sequential_view->current_position.z() = buffer.model.instances.buffer[offset + 2]; found = true; break; } } } + else { +#endif // ENABLE_SEAMS_USING_MODELS + // searches the path containing the current position + for (const Path& path : buffer.paths) { + if (path.contains(m_sequential_view.current.last)) { + const int sub_path_id = path.get_id_of_sub_path_containing(m_sequential_view.current.last); + if (sub_path_id != -1) { + const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; + unsigned int offset = static_cast(m_sequential_view.current.last - sub_path.first.s_id); + if (offset > 0) { + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) + offset = 2 * offset - 1; + else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { + unsigned int indices_count = buffer.indices_per_segment(); + offset = indices_count * (offset - 1) + (indices_count - 2); + if (sub_path_id == 0) + offset += 6; // add 2 triangles for starting cap + } + } + offset += static_cast(sub_path.first.i_id); + + // gets the vertex index from the index buffer on gpu + const IBuffer& i_buffer = buffer.indices[sub_path.first.b_id]; + unsigned int index = 0; + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(IBufferType)), static_cast(sizeof(IBufferType)), static_cast(&index))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + // gets the position from the vertices buffer on gpu + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(sequential_view->current_position.data()))); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + found = true; + break; + } + } + } +#if ENABLE_SEAMS_USING_MODELS + } +#endif // ENABLE_SEAMS_USING_MODELS if (found) break; @@ -2143,20 +2410,20 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool Color color; switch (path.type) { - case EMoveType::Tool_change: { color = Options_Colors[static_cast(EOptionsColors::ToolChanges)]; break; } - case EMoveType::Color_change: { color = Options_Colors[static_cast(EOptionsColors::ColorChanges)]; break; } - case EMoveType::Pause_Print: { color = Options_Colors[static_cast(EOptionsColors::PausePrints)]; break; } - case EMoveType::Custom_GCode: { color = Options_Colors[static_cast(EOptionsColors::CustomGCodes)]; break; } - case EMoveType::Retract: { color = Options_Colors[static_cast(EOptionsColors::Retractions)]; break; } - case EMoveType::Unretract: { color = Options_Colors[static_cast(EOptionsColors::Unretractions)]; break; } - case EMoveType::Seam: { color = Options_Colors[static_cast(EOptionsColors::Seams)]; break; } + case EMoveType::Tool_change: + case EMoveType::Color_change: + case EMoveType::Pause_Print: + case EMoveType::Custom_GCode: + case EMoveType::Retract: + case EMoveType::Unretract: + case EMoveType::Seam: { color = option_color(path.type); break; } case EMoveType::Extrude: { if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) color = extrusion_color(path); else - color = { 0.25f, 0.25f, 0.25f }; + color = Neutral_Color; break; } @@ -2164,12 +2431,12 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_layers_range(path_id, m_layers_z_range[1], m_layers_z_range[1])) color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); else - color = { 0.25f, 0.25f, 0.25f }; + color = Neutral_Color; break; } case EMoveType::Wipe: { color = Wipe_Color; break; } - default: { color = { 0.0f, 0.0f, 0.0f }; break; } + default: { color = { 0.0f, 0.0f, 0.0f, 1.0f }; break; } } RenderPath key{ tbuffer_id, color, static_cast(ibuffer_id), path_id }; @@ -2193,6 +2460,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool size_in_indices = buffer.indices_per_segment() * segments_count; break; } + default: { break; } } if (size_in_indices == 0) @@ -2231,9 +2499,70 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool #endif } +#if ENABLE_SEAMS_USING_MODELS +#if ENABLE_SEAMS_USING_BATCHED_MODELS + // second pass: for buffers using instanced and batched models, update the instances render ranges +#else + // second pass: for buffers using instanced models, update the instances render ranges +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + for (size_t b = 0; b < m_buffers.size(); ++b) { + TBuffer& buffer = const_cast(m_buffers[b]); +#if ENABLE_SEAMS_USING_BATCHED_MODELS + if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel && + buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) +#else + if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Model) +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + continue; + + buffer.model.instances.render_ranges.reset(); + + if (!buffer.visible || buffer.model.instances.s_ids.empty()) + continue; + + buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, buffer.model.color }); + bool has_second_range = top_layer_only && m_sequential_view.current.last != m_sequential_view.global.last; + if (has_second_range) + buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, Neutral_Color }); + + if (m_sequential_view.current.first <= buffer.model.instances.s_ids.back() && buffer.model.instances.s_ids.front() <= m_sequential_view.current.last) { + for (size_t id : buffer.model.instances.s_ids) { + if (has_second_range) { +#if ENABLE_FIX_PREVIEW_OPTIONS_Z + if (id < m_sequential_view.endpoints.first) { +#else + if (id <= m_sequential_view.endpoints.first) { +#endif // ENABLE_FIX_PREVIEW_OPTIONS_Z + ++buffer.model.instances.render_ranges.ranges.front().offset; + if (id <= m_sequential_view.current.first) + ++buffer.model.instances.render_ranges.ranges.back().offset; + else + ++buffer.model.instances.render_ranges.ranges.back().count; + } + else if (id <= m_sequential_view.current.last) + ++buffer.model.instances.render_ranges.ranges.front().count; + else + break; + } + else { + if (id <= m_sequential_view.current.first) + ++buffer.model.instances.render_ranges.ranges.front().offset; + else if (id <= m_sequential_view.current.last) + ++buffer.model.instances.render_ranges.ranges.front().count; + else + break; + } + } + } + } +#endif // ENABLE_SEAMS_USING_MODELS + // set sequential data to their final value sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first; +#if ENABLE_SEAMS_USING_MODELS + sequential_view->global = global_endpoints; +#endif // ENABLE_SEAMS_USING_MODELS // updates sequential range caps std::array* sequential_range_caps = const_cast*>(&m_sequential_range_caps); @@ -2355,12 +2684,17 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); } +#if ENABLE_SEAMS_USING_MODELS + statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.buffer, float); + statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.s_ids, size_t); + statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.render_ranges.ranges, InstanceVBuffer::Ranges::Range); +#endif // ENABLE_SEAMS_USING_MODELS } statistics->refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS } -void GCodeViewer::render_toolpaths() const +void GCodeViewer::render_toolpaths() { #if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS float point_size = 20.0f; @@ -2374,15 +2708,10 @@ void GCodeViewer::render_toolpaths() const float near_plane_height = camera.get_type() == Camera::EType::Perspective ? static_cast(viewport[3]) / (2.0f * static_cast(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) : static_cast(viewport[3]) * 0.0005; - auto set_uniform_color = [](const std::array& color, GLShaderProgram& shader) { - std::array color4 = { color[0], color[1], color[2], 1.0f }; - shader.set_uniform("uniform_color", color4); - }; - #if ENABLE_GCODE_VIEWER_STATISTICS - auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color] + auto render_as_points = [this, zoom, point_size, near_plane_height] #else - auto render_as_points = [zoom, point_size, near_plane_height, set_uniform_color] + auto render_as_points = [zoom, point_size, near_plane_height] #endif // ENABLE_GCODE_VIEWER_STATISTICS (const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { #if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS @@ -2401,10 +2730,10 @@ void GCodeViewer::render_toolpaths() const for (const RenderPath& path : buffer.render_paths) { if (path.ibuffer_id == ibuffer_id) { - set_uniform_color(path.color, shader); + shader.set_uniform("uniform_color", path.color); glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++const_cast(&m_statistics)->gl_multi_points_calls_count; + ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } } @@ -2414,51 +2743,140 @@ void GCodeViewer::render_toolpaths() const }; #if ENABLE_GCODE_VIEWER_STATISTICS - auto render_as_lines = [this, light_intensity, set_uniform_color] + auto render_as_lines = [this, light_intensity] #else - auto render_as_lines = [light_intensity, set_uniform_color] + auto render_as_lines = [light_intensity] #endif // ENABLE_GCODE_VIEWER_STATISTICS (const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { shader.set_uniform("light_intensity", light_intensity); for (const RenderPath& path : buffer.render_paths) { if (path.ibuffer_id == ibuffer_id) { - set_uniform_color(path.color, shader); + shader.set_uniform("uniform_color", path.color); glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++const_cast(&m_statistics)->gl_multi_lines_calls_count; + ++m_statistics.gl_multi_lines_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } } }; #if ENABLE_GCODE_VIEWER_STATISTICS - auto render_as_triangles = [this, set_uniform_color] + auto render_as_triangles = [this] #else - auto render_as_triangles = [set_uniform_color] + auto render_as_triangles = [] #endif // ENABLE_GCODE_VIEWER_STATISTICS -(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { + (const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { for (const RenderPath& path : buffer.render_paths) { if (path.ibuffer_id == ibuffer_id) { - set_uniform_color(path.color, shader); + shader.set_uniform("uniform_color", path.color); glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++const_cast(&m_statistics)->gl_multi_triangles_calls_count; + ++m_statistics.gl_multi_triangles_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS } } }; +#if ENABLE_SEAMS_USING_MODELS +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_as_instanced_model = [this] +#else + auto render_as_instanced_model = [] +#endif // ENABLE_GCODE_VIEWER_STATISTICS + (TBuffer& buffer, GLShaderProgram & shader) { + for (auto& range : buffer.model.instances.render_ranges.ranges) { + if (range.vbo == 0 && range.count > 0) { + glsafe(::glGenBuffers(1, &range.vbo)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, range.vbo)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, range.count * buffer.model.instances.instance_size_bytes(), (const void*)&buffer.model.instances.buffer[range.offset * buffer.model.instances.instance_size_floats()], GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } + + if (range.vbo > 0) { + buffer.model.model.set_color(-1, range.color); + buffer.model.model.render_instanced(range.vbo, range.count); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_instanced_models_calls_count; + m_statistics.total_instances_gpu_size += static_cast(range.count * buffer.model.instances.instance_size_bytes()); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } + } + }; + +#if ENABLE_SEAMS_USING_BATCHED_MODELS +#if ENABLE_GCODE_VIEWER_STATISTICS + auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader) { +#else + auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader) { +#endif // ENABLE_GCODE_VIEWER_STATISTICS + + struct Range + { + unsigned int first; + unsigned int last; + bool intersects(const Range& other) const { return (other.last < first || other.first > last) ? false : true; } + }; + Range buffer_range = { 0, 0 }; + size_t indices_per_instance = buffer.model.data.indices_count(); + + for (size_t j = 0; j < buffer.indices.size(); ++j) { + const IBuffer& i_buffer = buffer.indices[j]; + buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance; + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); + glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + bool has_normals = buffer.vertices.normal_size_floats() > 0; + if (has_normals) { + glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + } + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + + for (auto& range : buffer.model.instances.render_ranges.ranges) { + Range range_range = { range.offset, range.offset + range.count }; + if (range_range.intersects(buffer_range)) { + shader.set_uniform("uniform_color", range.color); + unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0; + size_t offset_bytes = static_cast(offset) * indices_per_instance * sizeof(IBufferType); + Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) }; + size_t count = static_cast(render_range.last - render_range.first) * indices_per_instance; + if (count > 0) { + glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)count, GL_UNSIGNED_SHORT, (const void*)offset_bytes)); +#if ENABLE_GCODE_VIEWER_STATISTICS + ++m_statistics.gl_batched_models_calls_count; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + } + } + } + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + if (has_normals) + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + + buffer_range.first = buffer_range.last; + } + }; +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS +#endif // ENABLE_SEAMS_USING_MODELS + auto line_width = [](double zoom) { return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); }; - glsafe(::glLineWidth(static_cast(line_width(zoom)))); - unsigned char begin_id = buffer_id(EMoveType::Retract); unsigned char end_id = buffer_id(EMoveType::Count); for (unsigned char i = begin_id; i < end_id; ++i) { +#if ENABLE_SEAMS_USING_MODELS + TBuffer& buffer = m_buffers[i]; +#else const TBuffer& buffer = m_buffers[i]; +#endif // ENABLE_SEAMS_USING_MODELS if (!buffer.visible || !buffer.has_data()) continue; @@ -2466,53 +2884,77 @@ void GCodeViewer::render_toolpaths() const if (shader != nullptr) { shader->start_using(); - for (size_t j = 0; j < buffer.indices.size(); ++j) { - const IBuffer& i_buffer = buffer.indices[j]; - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - bool has_normals = buffer.vertices.normal_size_floats() > 0; - if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); - - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: { - render_as_points(buffer, static_cast(j), *shader); - break; - } - case TBuffer::ERenderPrimitiveType::Line: { - render_as_lines(buffer, static_cast(j), *shader); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: { - render_as_triangles(buffer, static_cast(j), *shader); - break; - } - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +#if ENABLE_SEAMS_USING_MODELS +#if ENABLE_SEAMS_USING_BATCHED_MODELS + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { +#else + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Model) { +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + shader->set_uniform("emission_factor", 0.25f); + render_as_instanced_model(buffer, *shader); + shader->set_uniform("emission_factor", 0.0f); } +#if ENABLE_SEAMS_USING_BATCHED_MODELS + else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { + shader->set_uniform("emission_factor", 0.25f); + render_as_batched_model(buffer, *shader); + shader->set_uniform("emission_factor", 0.0f); + } +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + else { +#endif // ENABLE_SEAMS_USING_MODELS + for (size_t j = 0; j < buffer.indices.size(); ++j) { + const IBuffer& i_buffer = buffer.indices[j]; + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); + glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + bool has_normals = buffer.vertices.normal_size_floats() > 0; + if (has_normals) { + glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + } + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); + + switch (buffer.render_primitive_type) + { + case TBuffer::ERenderPrimitiveType::Point: { + render_as_points(buffer, static_cast(j), *shader); + break; + } + case TBuffer::ERenderPrimitiveType::Line: { + glsafe(::glLineWidth(static_cast(line_width(zoom)))); + render_as_lines(buffer, static_cast(j), *shader); + break; + } + case TBuffer::ERenderPrimitiveType::Triangle: { + render_as_triangles(buffer, static_cast(j), *shader); + break; + } + default: { break; } + } + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + if (has_normals) + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } +#if ENABLE_SEAMS_USING_MODELS + } +#endif // ENABLE_SEAMS_USING_MODELS shader->stop_using(); } } #if ENABLE_GCODE_VIEWER_STATISTICS - auto render_sequential_range_cap = [this, set_uniform_color] + auto render_sequential_range_cap = [this] #else - auto render_sequential_range_cap = [set_uniform_color] + auto render_sequential_range_cap = [] #endif // ENABLE_GCODE_VIEWER_STATISTICS (const SequentialRangeCap& cap) { GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str()); @@ -2528,14 +2970,14 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); } - set_uniform_color(cap.color, *shader); + shader->set_uniform("uniform_color", cap.color); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); #if ENABLE_GCODE_VIEWER_STATISTICS - ++const_cast(&m_statistics)->gl_triangles_calls_count; + ++m_statistics.gl_triangles_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS if (has_normals) @@ -2554,7 +2996,7 @@ void GCodeViewer::render_toolpaths() const } } -void GCodeViewer::render_shells() const +void GCodeViewer::render_shells() { if (!m_shells.visible || m_shells.volumes.empty()) return; @@ -2572,7 +3014,7 @@ void GCodeViewer::render_shells() const // glsafe(::glDepthMask(GL_TRUE)); } -void GCodeViewer::render_legend(float& legend_height) const +void GCodeViewer::render_legend(float& legend_height) { if (!m_legend_enabled) return; @@ -2606,7 +3048,7 @@ void GCodeViewer::render_legend(float& legend_height) const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - auto append_item = [this, icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label, + auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label, bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f, 0.0f, 0.0f }, double used_filament_m = 0.0, double used_filament_g = 0.0, std::function callback = nullptr) { @@ -2624,6 +3066,7 @@ void GCodeViewer::render_legend(float& legend_height) const } case EItemType::Circle: { ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); +#if !ENABLE_SEAMS_USING_BATCHED_MODELS if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); @@ -2633,6 +3076,7 @@ void GCodeViewer::render_legend(float& legend_height) const draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); } else +#endif // !ENABLE_SEAMS_USING_BATCHED_MODELS draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); break; @@ -2935,8 +3379,7 @@ void GCodeViewer::render_legend(float& legend_height) const const bool visible = is_visible(role); append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], visible, times[i], percents[i], max_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { - Extrusions* extrusions = const_cast(&m_extrusions); - extrusions->role_visibility_flags = visible ? extrusions->role_visibility_flags & ~(1 << role) : extrusions->role_visibility_flags | (1 << role); + m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << role) : m_extrusions.role_visibility_flags | (1 << role); // update buffers' render paths refresh_render_paths(false, false); wxGetApp().plater()->update_preview_moves_slider(); @@ -3279,7 +3722,11 @@ void GCodeViewer::render_legend(float& legend_height) const auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { const TBuffer& buffer = m_buffers[buffer_id(move_type)]; if (buffer.visible && buffer.has_data()) +#if ENABLE_SEAMS_USING_BATCHED_MODELS + append_item(EItemType::Circle, Options_Colors[static_cast(color)], text); +#else append_item((buffer.shader == "options_110") ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS }; // options section @@ -3408,7 +3855,7 @@ void GCodeViewer::render_legend(float& legend_height) const auto show_mode_button = [this, &imgui, can_show_mode_button](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) { if (can_show_mode_button(mode)) { if (imgui.button(label)) { - *const_cast(&m_time_estimate_mode) = mode; + m_time_estimate_mode = mode; wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); } @@ -3435,7 +3882,7 @@ void GCodeViewer::render_legend(float& legend_height) const } #if ENABLE_GCODE_VIEWER_STATISTICS -void GCodeViewer::render_statistics() const +void GCodeViewer::render_statistics() { static const float offset = 275.0f; @@ -3498,6 +3945,13 @@ void GCodeViewer::render_statistics() const add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count); +#if ENABLE_SEAMS_USING_MODELS + ImGui::Separator(); + add_counter(std::string("Instanced models:"), m_statistics.gl_instanced_models_calls_count); +#if ENABLE_SEAMS_USING_BATCHED_MODELS + add_counter(std::string("Batched models:"), m_statistics.gl_batched_models_calls_count); +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS +#endif // ENABLE_SEAMS_USING_MODELS } if (ImGui::CollapsingHeader("CPU memory")) { @@ -3506,11 +3960,17 @@ void GCodeViewer::render_statistics() const ImGui::Separator(); add_memory(std::string("Paths:"), m_statistics.paths_size); add_memory(std::string("Render paths:"), m_statistics.render_paths_size); +#if ENABLE_SEAMS_USING_MODELS + add_memory(std::string("Models instances:"), m_statistics.models_instances_size); +#endif // ENABLE_SEAMS_USING_MODELS } if (ImGui::CollapsingHeader("GPU memory")) { add_memory(std::string("Vertices:"), m_statistics.total_vertices_gpu_size); add_memory(std::string("Indices:"), m_statistics.total_indices_gpu_size); +#if ENABLE_SEAMS_USING_MODELS + add_memory(std::string("Instances:"), m_statistics.total_instances_gpu_size); +#endif // ENABLE_SEAMS_USING_MODELS ImGui::Separator(); add_memory(std::string("Max VBuffer:"), m_statistics.max_vbuffer_gpu_size); add_memory(std::string("Max IBuffer:"), m_statistics.max_ibuffer_gpu_size); @@ -3520,6 +3980,12 @@ void GCodeViewer::render_statistics() const add_counter(std::string("Travel segments count:"), m_statistics.travel_segments_count); add_counter(std::string("Wipe segments count:"), m_statistics.wipe_segments_count); add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count); +#if ENABLE_SEAMS_USING_MODELS + add_counter(std::string("Instances count:"), m_statistics.instances_count); +#if ENABLE_SEAMS_USING_BATCHED_MODELS + add_counter(std::string("Batched count:"), m_statistics.batched_count); +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS +#endif // ENABLE_SEAMS_USING_MODELS ImGui::Separator(); add_counter(std::string("VBuffers count:"), m_statistics.vbuffers_count); add_counter(std::string("IBuffers count:"), m_statistics.ibuffers_count); @@ -3550,6 +4016,21 @@ void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) } } +GCodeViewer::Color GCodeViewer::option_color(EMoveType move_type) const +{ + switch (move_type) + { + case EMoveType::Tool_change: { return Options_Colors[static_cast(EOptionsColors::ToolChanges)]; } + case EMoveType::Color_change: { return Options_Colors[static_cast(EOptionsColors::ColorChanges)]; } + case EMoveType::Pause_Print: { return Options_Colors[static_cast(EOptionsColors::PausePrints)]; } + case EMoveType::Custom_GCode: { return Options_Colors[static_cast(EOptionsColors::CustomGCodes)]; } + case EMoveType::Retract: { return Options_Colors[static_cast(EOptionsColors::Retractions)]; } + case EMoveType::Unretract: { return Options_Colors[static_cast(EOptionsColors::Unretractions)]; } + case EMoveType::Seam: { return Options_Colors[static_cast(EOptionsColors::Seams)]; } + default: { return { 0.0f, 0.0f, 0.0f, 1.0f }; } + } +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 41307bad97..f566c80bd5 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -22,17 +22,22 @@ namespace GUI { class GCodeViewer { using IBufferType = unsigned short; - using Color = std::array; + using Color = std::array; using VertexBuffer = std::vector; using MultiVertexBuffer = std::vector; using IndexBuffer = std::vector; using MultiIndexBuffer = std::vector; +#if ENABLE_SEAMS_USING_MODELS + using InstanceBuffer = std::vector; + using InstanceIdBuffer = std::vector; +#endif // ENABLE_SEAMS_USING_MODELS static const std::vector Extrusion_Role_Colors; static const std::vector Options_Colors; static const std::vector Travel_Colors; - static const Color Wipe_Color; static const std::vector Range_Colors; + static const Color Wipe_Color; + static const Color Neutral_Color; enum class EOptionsColors : unsigned char { @@ -80,7 +85,10 @@ class GCodeViewer size_t position_size_floats() const { return 3; } size_t position_size_bytes() const { return position_size_floats() * sizeof(float); } - size_t normal_offset_floats() const { return position_size_floats(); } + size_t normal_offset_floats() const { + assert(format == EFormat::PositionNormal1 || format == EFormat::PositionNormal3); + return position_size_floats(); + } size_t normal_offset_bytes() const { return normal_offset_floats() * sizeof(float); } size_t normal_size_floats() const { @@ -96,6 +104,75 @@ class GCodeViewer void reset(); }; +#if ENABLE_SEAMS_USING_MODELS +#if ENABLE_SEAMS_USING_BATCHED_MODELS + // buffer containing instances data used to render a toolpaths using instanced or batched models + // instance record format: + // instanced models: 5 floats -> position.x|position.y|position.z|width|height (which are sent to the shader as -> vec3 (offset) + vec2 (scales) in GLModel::render_instanced()) + // batched models: 3 floats -> position.x|position.y|position.z +#else + // buffer containing instances data used to render a toolpaths using instanced models + // instance record format: 5 floats -> position.x|position.y|position.z|width|height + // which is sent to the shader as -> vec3 (offset) + vec2 (scales) in GLModel::render_instanced() +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + struct InstanceVBuffer + { + // ranges used to render only subparts of the intances + struct Ranges + { + struct Range + { + // offset in bytes of the 1st instance to render + unsigned int offset; + // count of instances to render + unsigned int count; + // vbo id + unsigned int vbo{ 0 }; + // Color to apply to the instances + Color color; + }; + + std::vector ranges; + + void reset(); + }; + +#if ENABLE_SEAMS_USING_BATCHED_MODELS + enum class EFormat : unsigned char + { + InstancedModel, + BatchedModel + }; + + EFormat format; +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + + // cpu-side buffer containing all instances data + InstanceBuffer buffer; + // indices of the moves for all instances + std::vector s_ids; + Ranges render_ranges; + + size_t data_size_bytes() const { return s_ids.size() * instance_size_bytes(); } + +#if ENABLE_SEAMS_USING_BATCHED_MODELS + size_t instance_size_floats() const { + switch (format) + { + case EFormat::InstancedModel: { return 5; } + case EFormat::BatchedModel: { return 3; } + default: { return 0; } + } + } +#else + size_t instance_size_floats() const { return 5; } +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + size_t instance_size_bytes() const { return instance_size_floats() * sizeof(float); } + + void reset(); + }; +#endif // ENABLE_SEAMS_USING_MODELS + // ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type struct IBuffer { @@ -229,13 +306,42 @@ class GCodeViewer { Point, Line, +#if ENABLE_SEAMS_USING_MODELS + Triangle, +#if ENABLE_SEAMS_USING_BATCHED_MODELS + InstancedModel, + BatchedModel +#else + Model +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS +#else Triangle +#endif // ENABLE_SEAMS_USING_MODELS }; ERenderPrimitiveType render_primitive_type; + + // buffers for point, line and triangle primitive types VBuffer vertices; std::vector indices; +#if ENABLE_SEAMS_USING_MODELS + struct Model + { + GLModel model; + Color color; + InstanceVBuffer instances; +#if ENABLE_SEAMS_USING_BATCHED_MODELS + GLModel::InitializationData data; +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + + void reset(); + }; + + // contain the buffer for model primitive types + Model model; +#endif // ENABLE_SEAMS_USING_MODELS + std::string shader; std::vector paths; // std::set seems to perform significantly better, at least on Windows. @@ -283,9 +389,32 @@ class GCodeViewer } size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); } +#if ENABLE_SEAMS_USING_MODELS + bool has_data() const { + switch (render_primitive_type) + { + case ERenderPrimitiveType::Point: + case ERenderPrimitiveType::Line: + case ERenderPrimitiveType::Triangle: { + return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; + } +#if ENABLE_SEAMS_USING_BATCHED_MODELS + case ERenderPrimitiveType::InstancedModel: { return model.model.is_initialized() && !model.instances.buffer.empty(); } + case ERenderPrimitiveType::BatchedModel: { + return model.data.vertices_count() > 0 && model.data.indices_count() && + !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; + } +#else + case ERenderPrimitiveType::Model: { return model.model.is_initialized() && !model.instances.buffer.empty(); } +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + default: { return false; } + } + } +#else bool has_data() const { return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; } +#endif // ENABLE_SEAMS_USING_MODELS }; // helper to render shells @@ -433,18 +562,36 @@ class GCodeViewer int64_t gl_multi_lines_calls_count{ 0 }; int64_t gl_multi_triangles_calls_count{ 0 }; int64_t gl_triangles_calls_count{ 0 }; +#if ENABLE_SEAMS_USING_MODELS + int64_t gl_instanced_models_calls_count{ 0 }; +#if ENABLE_SEAMS_USING_BATCHED_MODELS + int64_t gl_batched_models_calls_count{ 0 }; +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS +#endif // ENABLE_SEAMS_USING_MODELS // memory int64_t results_size{ 0 }; int64_t total_vertices_gpu_size{ 0 }; int64_t total_indices_gpu_size{ 0 }; +#if ENABLE_SEAMS_USING_MODELS + int64_t total_instances_gpu_size{ 0 }; +#endif // ENABLE_SEAMS_USING_MODELS int64_t max_vbuffer_gpu_size{ 0 }; int64_t max_ibuffer_gpu_size{ 0 }; int64_t paths_size{ 0 }; int64_t render_paths_size{ 0 }; +#if ENABLE_SEAMS_USING_MODELS + int64_t models_instances_size{ 0 }; +#endif // ENABLE_SEAMS_USING_MODELS // other int64_t travel_segments_count{ 0 }; int64_t wipe_segments_count{ 0 }; int64_t extrude_segments_count{ 0 }; +#if ENABLE_SEAMS_USING_MODELS + int64_t instances_count{ 0 }; +#if ENABLE_SEAMS_USING_BATCHED_MODELS + int64_t batched_count{ 0 }; +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS +#endif // ENABLE_SEAMS_USING_MODELS int64_t vbuffers_count{ 0 }; int64_t ibuffers_count{ 0 }; @@ -470,22 +617,40 @@ class GCodeViewer gl_multi_lines_calls_count = 0; gl_multi_triangles_calls_count = 0; gl_triangles_calls_count = 0; +#if ENABLE_SEAMS_USING_MODELS + gl_instanced_models_calls_count = 0; +#if ENABLE_SEAMS_USING_BATCHED_MODELS + gl_batched_models_calls_count = 0; +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS +#endif // ENABLE_SEAMS_USING_MODELS } void reset_sizes() { results_size = 0; total_vertices_gpu_size = 0; total_indices_gpu_size = 0; +#if ENABLE_SEAMS_USING_MODELS + total_instances_gpu_size = 0; +#endif // ENABLE_SEAMS_USING_MODELS max_vbuffer_gpu_size = 0; max_ibuffer_gpu_size = 0; paths_size = 0; render_paths_size = 0; +#if ENABLE_SEAMS_USING_MODELS + models_instances_size = 0; +#endif // ENABLE_SEAMS_USING_MODELS } void reset_others() { travel_segments_count = 0; wipe_segments_count = 0; extrude_segments_count = 0; +#if ENABLE_SEAMS_USING_MODELS + instances_count = 0; +#if ENABLE_SEAMS_USING_BATCHED_MODELS + batched_count = 0; +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS +#endif // ENABLE_SEAMS_USING_MODELS vbuffers_count = 0; ibuffers_count = 0; } @@ -530,18 +695,17 @@ public: std::string m_filename; boost::iostreams::mapped_file_source m_file; // map for accessing data in file by line number - std::vector> m_lines_map; + std::vector m_lines_ends; // current visible lines std::vector m_lines; public: GCodeWindow() = default; ~GCodeWindow() { stop_mapping_file(); } - void set_filename(const std::string& filename) { m_filename = filename; } - void load_gcode(); + void load_gcode(const std::string& filename, std::vector &&lines_ends); void reset() { stop_mapping_file(); - m_lines_map.clear(); + m_lines_ends.clear(); m_lines.clear(); m_filename.clear(); } @@ -563,6 +727,9 @@ public: Endpoints endpoints; Endpoints current; Endpoints last_current; +#if ENABLE_SEAMS_USING_MODELS + Endpoints global; +#endif // ENABLE_SEAMS_USING_MODELS Vec3f current_position{ Vec3f::Zero() }; Marker marker; GCodeWindow gcode_window; @@ -632,7 +799,7 @@ public: void update_shells_color_by_extruder(const DynamicPrintConfig* config); void reset(); - void render() const; + void render(); bool has_data() const { return !m_roles.empty(); } bool can_export_toolpaths() const; @@ -665,8 +832,6 @@ public: void export_toolpaths_to_obj(const char* filename) const; - void start_mapping_gcode_window(); - void stop_mapping_gcode_window(); void toggle_gcode_window_visibility() { m_sequential_view.gcode_window.toggle_visibility(); } #if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER @@ -678,17 +843,18 @@ private: void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; - void render_toolpaths() const; - void render_shells() const; - void render_legend(float& legend_height) const; + void render_toolpaths(); + void render_shells(); + void render_legend(float& legend_height); #if ENABLE_GCODE_VIEWER_STATISTICS - void render_statistics() const; + void render_statistics(); #endif // ENABLE_GCODE_VIEWER_STATISTICS bool is_visible(ExtrusionRole role) const { return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0; } bool is_visible(const Path& path) const { return is_visible(path.role); } void log_memory_used(const std::string& label, int64_t additional = 0) const; + Color option_color(EMoveType move_type) const; }; } // namespace GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ee1d3b14a4..551760ee93 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1107,21 +1107,11 @@ void GLCanvas3D::reset_volumes() _set_warning_notification(EWarning::ObjectOutside, false); } -int GLCanvas3D::check_volumes_outside_state() const +ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const { ModelInstanceEPrintVolumeState state; m_volumes.check_outside_state(m_config, &state); - return (int)state; -} - -void GLCanvas3D::start_mapping_gcode_window() -{ - m_gcode_viewer.start_mapping_gcode_window(); -} - -void GLCanvas3D::stop_mapping_gcode_window() -{ - m_gcode_viewer.stop_mapping_gcode_window(); + return state; } void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx) @@ -1855,7 +1845,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re volume->extruder_id = extruder_id; volume->is_modifier = !mvs->model_volume->is_model_part(); - volume->set_color_from_model_volume(*mvs->model_volume); + volume->set_color(color_from_model_volume(*mvs->model_volume)); // updates volumes transformations volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); @@ -2049,9 +2039,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // checks for geometry outside the print volume to render it accordingly if (!m_volumes.empty()) { - bool partlyOut = false; - bool fullyOut = false; - const bool contained_min_one = m_volumes.check_outside_state(m_config, partlyOut, fullyOut); + ModelInstanceEPrintVolumeState state; + const bool contained_min_one = m_volumes.check_outside_state(m_config, &state); + const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside); + const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside); _set_warning_notification(EWarning::ObjectClashed, partlyOut); _set_warning_notification(EWarning::ObjectOutside, fullyOut); @@ -2101,7 +2092,7 @@ static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& vol_old.finalize_geometry(gl_initialized); } -void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) +void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) { m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); @@ -2109,10 +2100,7 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) m_gcode_viewer.update_shells_color_by_extruder(m_config); _set_warning_notification_if_needed(EWarning::ToolpathOutside); } -} -void GLCanvas3D::refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors) -{ m_gcode_viewer.refresh(gcode_result, str_tool_colors); set_as_dirty(); request_extra_frame(); @@ -2127,12 +2115,14 @@ void GLCanvas3D::refresh_gcode_preview_render_paths() void GLCanvas3D::load_sla_preview() { - const SLAPrint* print = this->sla_print(); + const SLAPrint* print = sla_print(); if (m_canvas != nullptr && print != nullptr) { _set_current(); // Release OpenGL data before generating new data. - this->reset_volumes(); + reset_volumes(); _load_sla_shells(); + const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); + m_volumes.set_print_box(float(bed_bb.min.x()) - BedEpsilon, float(bed_bb.min.y()) - BedEpsilon, 0.0f, float(bed_bb.max.x()) + BedEpsilon, float(bed_bb.max.y()) + BedEpsilon, (float)m_config->opt_float("max_print_height")); _update_sla_shells_outside_state(); _set_warning_notification_if_needed(EWarning::SlaSupportsOutside); } @@ -4108,13 +4098,24 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const } } +#if !ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED if (visible_volumes.empty()) return; +#endif // !ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED BoundingBoxf3 volumes_box; - for (const GLVolume* vol : visible_volumes) { - volumes_box.merge(vol->transformed_bounding_box()); +#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED + if (!visible_volumes.empty()) { +#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED + for (const GLVolume* vol : visible_volumes) { + volumes_box.merge(vol->transformed_bounding_box()); + } +#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED } + else + // This happens for empty projects + volumes_box = wxGetApp().plater()->get_bed().get_bounding_box(true); +#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED Camera camera; camera.set_type(camera_type); @@ -4148,7 +4149,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const glsafe(::glEnable(GL_DEPTH_TEST)); shader->start_using(); - shader->set_uniform("emission_factor", 0.0); + shader->set_uniform("emission_factor", 0.0f); for (GLVolume* vol : visible_volumes) { shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? orange : gray); @@ -5170,7 +5171,7 @@ void GLCanvas3D::_render_objects() m_camera_clipping_plane = ClippingPlane::ClipsNothing(); } -void GLCanvas3D::_render_gcode() const +void GLCanvas3D::_render_gcode() { m_gcode_viewer.render(); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index ba8430c07f..704adb010c 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -615,15 +615,12 @@ public: unsigned int get_volumes_count() const; const GLVolumeCollection& get_volumes() const { return m_volumes; } void reset_volumes(); - int check_volumes_outside_state() const; + ModelInstanceEPrintVolumeState check_volumes_outside_state() const; void reset_gcode_toolpaths() { m_gcode_viewer.reset(); } const GCodeViewer::SequentialView& get_gcode_sequential_view() const { return m_gcode_viewer.get_sequential_view(); } void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); } - void start_mapping_gcode_window(); - void stop_mapping_gcode_window(); - 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 update_instance_printable_state_for_object(size_t obj_idx); @@ -725,8 +722,7 @@ public: void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); - void load_gcode_preview(const GCodeProcessor::Result& gcode_result); - void refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors); + void load_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector& str_tool_colors); void refresh_gcode_preview_render_paths(); void set_gcode_view_preview_type(GCodeViewer::EViewType type) { return m_gcode_viewer.set_view_type(type); } GCodeViewer::EViewType get_gcode_view_preview_type() const { return m_gcode_viewer.get_view_type(); } @@ -904,7 +900,7 @@ private: #else void _render_objects(); #endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING - void _render_gcode() const; + void _render_gcode(); void _render_selection() const; void _render_sequential_clearance(); #if ENABLE_RENDER_SELECTION_CENTER diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index a9550bc04f..d22925673b 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -19,6 +19,26 @@ namespace Slic3r { namespace GUI { +#if ENABLE_SEAMS_USING_BATCHED_MODELS +size_t GLModel::InitializationData::vertices_count() const +{ + size_t ret = 0; + for (const Entity& entity : entities) { + ret += entity.positions.size(); + } + return ret; +} + +size_t GLModel::InitializationData::indices_count() const +{ + size_t ret = 0; + for (const Entity& entity : entities) { + ret += entity.indices.size(); + } + return ret; +} +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS + void GLModel::init_from(const InitializationData& data) { if (!m_render_data.empty()) // call reset() if you want to reuse this model @@ -208,6 +228,85 @@ void GLModel::render() const } } +#if ENABLE_SEAMS_USING_MODELS +void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instances_count) const +{ + if (instances_vbo == 0) + return; + + GLShaderProgram* shader = wxGetApp().get_current_shader(); + assert(shader == nullptr || boost::algorithm::iends_with(shader->get_name(), "_instanced")); + + // vertex attributes + GLint position_id = (shader != nullptr) ? shader->get_attrib_location("v_position") : -1; + GLint normal_id = (shader != nullptr) ? shader->get_attrib_location("v_normal") : -1; + assert(position_id != -1 && normal_id != -1); + + // instance attributes + GLint offset_id = (shader != nullptr) ? shader->get_attrib_location("i_offset") : -1; + GLint scales_id = (shader != nullptr) ? shader->get_attrib_location("i_scales") : -1; + assert(offset_id != -1 && scales_id != -1); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, instances_vbo)); + if (offset_id != -1) { + glsafe(::glVertexAttribPointer(offset_id, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (GLvoid*)0)); + glsafe(::glEnableVertexAttribArray(offset_id)); + glsafe(::glVertexAttribDivisor(offset_id, 1)); + } + if (scales_id != -1) { + glsafe(::glVertexAttribPointer(scales_id, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (GLvoid*)(3 * sizeof(float)))); + glsafe(::glEnableVertexAttribArray(scales_id)); + glsafe(::glVertexAttribDivisor(scales_id, 1)); + } + + for (const RenderData& data : m_render_data) { + if (data.vbo_id == 0 || data.ibo_id == 0) + continue; + + GLenum mode; + switch (data.type) + { + default: + case PrimitiveType::Triangles: { mode = GL_TRIANGLES; break; } + case PrimitiveType::Lines: { mode = GL_LINES; break; } + case PrimitiveType::LineStrip: { mode = GL_LINE_STRIP; break; } + case PrimitiveType::LineLoop: { mode = GL_LINE_LOOP; break; } + } + + if (shader != nullptr) + shader->set_uniform("uniform_color", data.color); + else + glsafe(::glColor4fv(data.color.data())); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, data.vbo_id)); + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (GLvoid*)0)); + glsafe(::glEnableVertexAttribArray(position_id)); + } + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (GLvoid*)(3 * sizeof(float)))); + glsafe(::glEnableVertexAttribArray(normal_id)); + } + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.ibo_id)); + glsafe(::glDrawElementsInstanced(mode, static_cast(data.indices_count), GL_UNSIGNED_INT, (const void*)0, instances_count)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); + } + + if (scales_id != -1) + glsafe(::glDisableVertexAttribArray(scales_id)); + if (offset_id != -1) + glsafe(::glDisableVertexAttribArray(offset_id)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} +#endif // ENABLE_SEAMS_USING_MODELS + void GLModel::send_to_gpu(RenderData& data, const std::vector& vertices, const std::vector& indices) { assert(data.vbo_id == 0); @@ -598,5 +697,53 @@ GLModel::InitializationData straight_arrow(float tip_width, float tip_height, fl return data; } +GLModel::InitializationData diamond(int resolution) +{ + resolution = std::max(4, resolution); + + GLModel::InitializationData data; + GLModel::InitializationData::Entity entity; + entity.type = GLModel::PrimitiveType::Triangles; + + const float step = 2.0f * float(PI) / float(resolution); + + // positions + for (int i = 0; i < resolution; ++i) { + float ii = float(i) * step; + entity.positions.emplace_back(0.5f * ::cos(ii), 0.5f * ::sin(ii), 0.0f); + } + entity.positions.emplace_back(0.0f, 0.0f, 0.5f); + entity.positions.emplace_back(0.0f, 0.0f, -0.5f); + + // normals + for (const Vec3f& v : entity.positions) { + entity.normals.emplace_back(v.normalized()); + } + + // triangles + // top + for (int i = 0; i < resolution; ++i) { + entity.indices.push_back(i + 0); + entity.indices.push_back(i + 1); + entity.indices.push_back(resolution); + } + entity.indices.push_back(resolution - 1); + entity.indices.push_back(0); + entity.indices.push_back(resolution); + + // bottom + for (int i = 0; i < resolution; ++i) { + entity.indices.push_back(i + 0); + entity.indices.push_back(resolution + 1); + entity.indices.push_back(i + 1); + } + entity.indices.push_back(resolution - 1); + entity.indices.push_back(resolution + 1); + entity.indices.push_back(0); + + data.entities.emplace_back(entity); + return data; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index cab2fe220f..7422466f0d 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -48,6 +48,15 @@ namespace GUI { }; std::vector entities; + +#if ENABLE_SEAMS_USING_BATCHED_MODELS + size_t vertices_count() const; + size_t vertices_size_floats() const { return vertices_count() * 6; } + size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); } + + size_t indices_count() const; + size_t indices_size_bytes() const { return indices_count() * sizeof(unsigned int); } +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS }; private: @@ -72,6 +81,9 @@ namespace GUI { void reset(); void render() const; +#if ENABLE_SEAMS_USING_MODELS + void render_instanced(unsigned int instances_vbo, unsigned int instances_count) const; +#endif // ENABLE_SEAMS_USING_MODELS bool is_initialized() const { return !m_render_data.empty(); } @@ -100,6 +112,11 @@ namespace GUI { // used to render sidebar hints for position and scale GLModel::InitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness); + // create a diamond with the given resolution + // the origin of the diamond is in its center + // the diamond is contained into a box with size [1, 1, 1] + GLModel::InitializationData diamond(int resolution); + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 33eec63e8b..5dd478b578 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -33,14 +33,31 @@ std::pair GLShadersManager::init() bool valid = true; +#if ENABLE_SEAMS_USING_BATCHED_MODELS + // used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells, options in gcode preview +#else // used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" }); // used to render printbed valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" }); // used to render options in gcode preview - valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" }); - if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20)) - valid &= append_shader("options_120", { "options_120.vs", "options_120.fs" }); +#if ENABLE_SEAMS_USING_BATCHED_MODELS + if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) + valid &= append_shader("gouraud_light_instanced", { "gouraud_light_instanced.vs", "gouraud_light_instanced.fs" }); +#else +#if ENABLE_SEAMS_USING_MODELS + if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) + valid &= append_shader("gouraud_light_instanced", { "gouraud_light_instanced.vs", "gouraud_light_instanced.fs" }); + else { +#endif // ENABLE_SEAMS_USING_MODELS + valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" }); + if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20)) + valid &= append_shader("options_120", { "options_120.vs", "options_120.fs" }); +#if ENABLE_SEAMS_USING_MODELS + } +#endif // ENABLE_SEAMS_USING_MODELS +#endif // ENABLE_SEAMS_USING_BATCHED_MODELS // used to render extrusion and travel paths as lines in gcode preview valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); // used to render objects in 3d editor diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index dc990fc667..c8d8ad7b50 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -959,6 +959,8 @@ bool GUI_App::on_init_inner() // update_mode(); // !!! do that later SetTopWindow(mainframe); + plater_->init_notification_manager(); + m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); if (is_gcode_viewer()) { @@ -2083,10 +2085,10 @@ std::vector> GUI_App::get_selected_presets( // This is called when closing the application, when loading a config file or when starting the config wizard // to notify the user whether he is aware that some preset changes will be lost. -bool GUI_App::check_and_save_current_preset_changes(const wxString& header) +bool GUI_App::check_and_save_current_preset_changes(const wxString& header, const wxString& caption) { if (/*this->plater()->model().objects.empty() && */has_current_preset_changes()) { - UnsavedChangesDialog dlg(header); + UnsavedChangesDialog dlg(header, caption); if (wxGetApp().app_config->get("default_action_on_close_application") == "none" && dlg.ShowModal() == wxID_CANCEL) return false; @@ -2279,7 +2281,7 @@ wxBookCtrlBase* GUI_App::tab_panel() const return mainframe->m_tabpanel; } -NotificationManager* GUI_App::notification_manager() +std::shared_ptr GUI_App::notification_manager() { return plater_->get_notification_manager(); } @@ -2335,10 +2337,13 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage { wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); - if (reason == ConfigWizard::RR_USER) - if (PresetUpdater::UpdateResult result = preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD); - result == PresetUpdater::R_ALL_CANCELED) + if (reason == ConfigWizard::RR_USER) { + wxString header = _L("Updates to Configuration Wizard may cause an another preset selection and lost of preset modification as a result.\n" + "So, check unsaved changes and save them if necessary.") + "\n"; + if (!check_and_save_current_preset_changes(header, _L("ConfigWizard is opening")) || + preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) return false; + } if (! m_wizard) { wxBusyCursor wait; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index e3b75e6623..c7b9081ce8 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -248,7 +248,7 @@ public: bool has_current_preset_changes() const; void update_saved_preset_from_current_preset(); std::vector> get_selected_presets() const; - bool check_and_save_current_preset_changes(const wxString& header = wxString()); + bool check_and_save_current_preset_changes(const wxString& header = wxString(), const wxString& caption = wxString()); bool check_print_host_queue(); bool checked_tab(Tab* tab); void load_current_presets(bool check_printer_presets = true); @@ -277,7 +277,7 @@ public: ObjectLayers* obj_layers(); Plater* plater(); Model& model(); - NotificationManager* notification_manager(); + std::shared_ptr notification_manager(); // Parameters extracted from the command line to be passed to GUI after initialization. GUI_InitParams* init_params { nullptr }; diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 92ba427b5b..4fec12d148 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -16,6 +16,10 @@ #include #include "slic3r/Utils/FixModelByWin10.hpp" +#ifdef __APPLE__ +#include "wx/dcclient.h" +#include "slic3r/Utils/MacDarkMode.hpp" +#endif namespace Slic3r { @@ -211,7 +215,6 @@ static int GetSelectedChoices( wxArrayInt& selections, const wxString& caption, const wxArrayString& choices) { -#ifdef _WIN32 wxMultiChoiceDialog dialog(nullptr, message, caption, choices); wxGetApp().UpdateDlgDarkUI(&dialog); @@ -219,6 +222,39 @@ static int GetSelectedChoices( wxArrayInt& selections, // deselects the first item which is selected by default dialog.SetSelections(selections); +#ifdef __APPLE__ + // Improvements for ChoiceListBox: Height of control will restect to items count + for (auto child : dialog.GetChildren()) + if (dynamic_cast(child) && !choices.IsEmpty()) { + wxClientDC dc(child); + + int height = dc.GetTextExtent(choices[0]).y; + int width = 0; + for (const auto& string : choices) + width = std::max(width, dc.GetTextExtent(string).x); + + // calculate best size of ListBox + height += 3 * mac_max_scaling_factor(); // extend height by margins + width += 3 * height; // extend width by checkbox width and margins + + // don't make the listbox too tall (limit height to around 10 items) + // but don't make it too small neither + int list_height = wxMax(height * wxMin(wxMax(choices.Count(), 3), 10), 70); + wxSize sz_best = wxSize(width, list_height); + + wxSize sz = child->GetSize(); + child->SetMinSize(sz_best); + + // extend Dialog size, if calculated best size of ListBox is bigger then its size + wxSize dlg_sz = dialog.GetSize(); + if (int delta_x = sz_best.x - sz.x; delta_x > 0) dlg_sz.x += delta_x; + if (int delta_y = sz_best.y - sz.y; delta_y > 0) dlg_sz.y += delta_y; + dialog.SetSize(dlg_sz); + + break; + } +#endif + if (dialog.ShowModal() != wxID_OK) { // NB: intentionally do not clear the selections array here, the caller @@ -229,9 +265,6 @@ static int GetSelectedChoices( wxArrayInt& selections, selections = dialog.GetSelections(); return static_cast(selections.GetCount()); -#else - return wxGetSelectedChoices(selections, message, caption, choices); -#endif } static wxMenu* create_settings_popupmenu(wxMenu* parent_menu, const bool is_object_settings, wxDataViewItem item/*, ModelConfig& config*/) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d4030d41f2..378f91cbc6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1044,13 +1044,8 @@ void ObjectList::key_event(wxKeyEvent& event) { if (event.GetKeyCode() == WXK_TAB) Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); - else if (event.GetKeyCode() == WXK_DELETE -#ifdef __WXOSX__ - || event.GetKeyCode() == WXK_BACK -#endif //__WXOSX__ - ) { - wxGetApp().plater()->remove_selected(); - } + else if (event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK ) + remove(); else if (event.GetKeyCode() == WXK_F5) wxGetApp().plater()->reload_all_from_disk(); else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/)) @@ -1702,8 +1697,7 @@ void ObjectList::load_shape_object_from_gallery(const wxArrayString& input_files snapshot_label += ", " + wxString::FromUTF8(paths[i].filename().string().c_str()); take_snapshot(snapshot_label); - std::vector res = wxGetApp().plater()->load_files(paths, true, false); - if (!res.empty()) + if (! wxGetApp().plater()->load_files(paths, true, false).empty()) wxGetApp().mainframe->update_title(); } @@ -1803,21 +1797,21 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type) cnv->get_gizmos_manager().reset_all_states(); Plater::TakeSnapshot(plater, _L("Remove paint-on supports")); for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes) - mv->supported_facets.clear(); + mv->supported_facets.reset(); break; case InfoItemType::CustomSeam: cnv->get_gizmos_manager().reset_all_states(); Plater::TakeSnapshot(plater, _L("Remove paint-on seam")); for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes) - mv->seam_facets.clear(); + mv->seam_facets.reset(); break; case InfoItemType::MmuSegmentation: cnv->get_gizmos_manager().reset_all_states(); Plater::TakeSnapshot(plater, _L("Remove Multi Material painting")); for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes) - mv->mmu_segmentation_facets.clear(); + mv->mmu_segmentation_facets.reset(); break; case InfoItemType::Sinking: @@ -1856,7 +1850,7 @@ void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item) if (is_layer_settings) layer_height = m_config->opt_float("layer_height"); - m_config->clear(); + m_config->reset(); if (extruder >= 0) m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); @@ -1932,7 +1926,7 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con const auto last_volume = object->volumes[0]; if (!last_volume->config.empty()) { object->config.apply(last_volume->config); - last_volume->config.clear(); + last_volume->config.reset(); // update extruder color in ObjectList wxDataViewItem obj_item = m_objects_model->GetItemById(obj_idx); @@ -2562,6 +2556,9 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selections/* = nullptr*/, bool added_object/* = false*/) { + if (obj_idx >= m_objects->size()) + return; + const ModelObject* model_object = (*m_objects)[obj_idx]; wxDataViewItem item_obj = m_objects_model->GetItemById(obj_idx); assert(item_obj.IsOk()); @@ -2637,7 +2634,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) model_object->config.has("extruder") ? model_object->config.extruder() : 0, get_mesh_errors_count(obj_idx) > 0); - update_info_items(obj_idx, nullptr, true); + update_info_items(obj_idx, nullptr, call_selection_changed); // add volumes to the object if (model_object->volumes.size() > 1) { @@ -3468,7 +3465,11 @@ void ObjectList::update_selections_on_canvas() else { // add - volume_idxs = selection.get_unselected_volume_idxs_from(volume_idxs); + // to avoid lost of some volumes in selection + // check non-selected volumes only if selection mode wasn't changed + // OR there is no single selection + if (selection.get_mode() == mode || !single_selection) + volume_idxs = selection.get_unselected_volume_idxs_from(volume_idxs); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Selection-Add from list"))); selection.add_volumes(mode, volume_idxs, single_selection); } @@ -3769,7 +3770,7 @@ void ObjectList::last_volume_is_deleted(const int obj_idx) auto volume = (*m_objects)[obj_idx]->volumes.front(); // clear volume's config values - volume->config.clear(); + volume->config.reset(); // set a default extruder value, since user can't add it manually volume->config.set_key_value("extruder", new ConfigOptionInt(0)); @@ -4028,17 +4029,12 @@ void ObjectList::simplify() // Do not simplify when a gizmo is open. There might be issues with updates // and what is worse, the snapshot time would refer to the internal stack. - auto current_type = gizmos_mgr.get_current_type(); - if (current_type == GLGizmosManager::Simplify) { + if (! gizmos_mgr.check_gizmos_closed_except(GLGizmosManager::EType::Simplify)) + return; + + if (gizmos_mgr.get_current_type() == GLGizmosManager::Simplify) { // close first gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); - }else if (current_type != GLGizmosManager::Undefined) { - plater->get_notification_manager()->push_notification( - NotificationType::CustomSupportsAndSeamRemovedAfterRepair, - NotificationManager::NotificationLevel::RegularNotification, - _u8L("ERROR: Please close all manipulators available from " - "the left toolbar before start simplify the mesh.")); - return; } gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 8c10fb1576..f8a10bb8e9 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -128,11 +128,6 @@ void View3D::mirror_selection(Axis axis) m_canvas->mirror_selection(axis); } -int View3D::check_volumes_outside_state() const -{ - return (m_canvas != nullptr) ? m_canvas->check_volumes_outside_state() : false; -} - bool View3D::is_layers_editing_enabled() const { return (m_canvas != nullptr) ? m_canvas->is_layers_editing_enabled() : false; @@ -739,9 +734,9 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); if( bottom_area - top_area > delta_area) { - NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager(); + std::shared_ptr notif_mngr = wxGetApp().plater()->get_notification_manager(); notif_mngr->push_notification( - NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotification, + NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", _u8L("Apply auto color change to print"), [this](wxEvtHandler*) { @@ -948,8 +943,7 @@ void Preview::load_print_as_fff(bool keep_z_range) m_canvas->set_selected_extruder(0); if (gcode_preview_data_valid) { // Load the real G-code preview. - m_canvas->load_gcode_preview(*m_gcode_result); - m_canvas->refresh_gcode_preview(*m_gcode_result, colors); + m_canvas->load_gcode_preview(*m_gcode_result, colors); m_left_sizer->Show(m_bottom_toolbar_panel); m_left_sizer->Layout(); Refresh(); diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index f8a41fd926..97ced0a1e0 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -59,8 +59,6 @@ public: void delete_selected(); void mirror_selection(Axis axis); - int check_volumes_outside_state() const; - bool is_layers_editing_enabled() const; bool is_layers_editing_allowed() const; void enable_layers_editing(bool enable); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 9cda6d75b3..64479a39e2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -191,7 +191,7 @@ void GLGizmoBase::render_grabbers(float size) const if (shader == nullptr) return; shader->start_using(); - shader->set_uniform("emission_factor", 0.1); + shader->set_uniform("emission_factor", 0.1f); for (int i = 0; i < (int)m_grabbers.size(); ++i) { if (m_grabbers[i].enabled) m_grabbers[i].render(m_hover_id == i, size); @@ -232,6 +232,20 @@ void GLGizmoBase::render_input_window(float x, float y, float bottom_limit) } } + + +std::string GLGizmoBase::get_name(bool include_shortcut) const +{ + int key = get_shortcut_key(); + assert( key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z); + std::string out = on_get_name(); + if (include_shortcut) + out += std::string(" [") + char(int('A') + key - int(WXK_CONTROL_A)) + "]"; + return out; +} + + + // Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components // were not interpolated by alpha blending or multi sampling. unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 8b033ce732..05f6adb5e8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -120,7 +120,7 @@ public: void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); } void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); } - std::string get_name() const { return on_get_name(); } + std::string get_name(bool include_shortcut = true) const; int get_group_id() const { return m_group_id; } void set_group_id(int id) { m_group_id = id; } @@ -135,6 +135,7 @@ public: bool is_activable() const { return on_is_activable(); } bool is_selectable() const { return on_is_selectable(); } CommonGizmosDataID get_requirements() const { return on_get_requirements(); } + virtual bool wants_enter_leave_snapshots() const { return false; } void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; } unsigned int get_sprite_id() const { return m_sprite_id; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index d7bdf94742..3dcb9e2b10 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -49,7 +49,7 @@ bool GLGizmoCut::on_init() std::string GLGizmoCut::on_get_name() const { - return (_L("Cut") + " [C]").ToUTF8().data(); + return _u8L("Cut"); } void GLGizmoCut::on_set_state() @@ -136,7 +136,7 @@ void GLGizmoCut::on_render() if (shader == nullptr) return; shader->start_using(); - shader->set_uniform("emission_factor", 0.1); + shader->set_uniform("emission_factor", 0.1f); m_grabbers[0].color = GrabberColor; m_grabbers[0].render(m_hover_id == 0, (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 4f3e31beaa..1ebba4d11f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -28,7 +28,7 @@ void GLGizmoFdmSupports::on_shutdown() std::string GLGizmoFdmSupports::on_get_name() const { - return (_L("Paint-on supports") + " [L]").ToUTF8().data(); + return _u8L("Paint-on supports"); } @@ -85,7 +85,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, @@ -131,6 +131,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l m_imgui->text(""); ImGui::Separator(); + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["highlight_by_angle"] + ":"); ImGui::AlignTextToFramePadding(); std::string format_str = std::string("%.f") + I18N::translate_utf8("°", diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 019ed15f40..f2c19ba5c8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -19,6 +19,9 @@ protected: wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; + std::string get_gizmo_entering_text() const override { return _u8L("Entering Paint-on supports"); } + std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Paint-on supports"); } + private: bool on_init() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 3e5c12eec4..b8c7d9f92e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -37,7 +37,7 @@ CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const std::string GLGizmoFlatten::on_get_name() const { - return (_L("Place on face") + " [F]").ToUTF8().data(); + return _u8L("Place on face"); } bool GLGizmoFlatten::on_is_activable() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 46608b4c73..f405f457d1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -505,7 +505,7 @@ RENDER_AGAIN: y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: const float settings_sliders_left = @@ -542,6 +542,7 @@ RENDER_AGAIN: m_imgui->disabled_begin(! m_enable_hollowing); float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("offset")); ImGui::SameLine(settings_sliders_left); ImGui::PushItemWidth(window_width - settings_sliders_left); @@ -558,6 +559,7 @@ RENDER_AGAIN: bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider if (current_mode >= quality_mode) { + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("quality")); ImGui::SameLine(settings_sliders_left); m_imgui->slider_float(" ", &quality, quality_min, quality_max, "%.1f"); @@ -574,6 +576,7 @@ RENDER_AGAIN: } if (current_mode >= closing_d_mode) { + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("closing_distance")); ImGui::SameLine(settings_sliders_left); m_imgui->slider_float(" ", &closing_d, closing_d_min, closing_d_max, "%.1f mm"); @@ -621,6 +624,7 @@ RENDER_AGAIN: float diameter_upper_cap = 60.; if (m_new_hole_radius * 2.f > diameter_upper_cap) m_new_hole_radius = diameter_upper_cap / 2.f; + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("hole_diameter")); ImGui::SameLine(diameter_slider_left); ImGui::PushItemWidth(window_width - diameter_slider_left); @@ -636,6 +640,7 @@ RENDER_AGAIN: bool edited = ImGui::IsItemEdited(); bool deactivated = ImGui::IsItemDeactivatedAfterEdit(); + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc["hole_depth"]); ImGui::SameLine(diameter_slider_left); m_imgui->slider_float(" ", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false); @@ -692,8 +697,10 @@ RENDER_AGAIN: // Following is rendered in both editing and non-editing mode: // m_imgui->text(""); ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) + if (m_c->object_clipper()->get_position() == 0.f) { + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("clipping_of_view")); + } else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ @@ -766,7 +773,7 @@ bool GLGizmoHollow::on_is_selectable() const std::string GLGizmoHollow::on_get_name() const { - return (_(L("Hollow and drill")) + " [H]").ToUTF8().data(); + return _u8L("Hollow and drill"); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index a69319d3b6..4bd98f8c87 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -22,7 +22,7 @@ static inline void show_notification_extruders_limit_exceeded() wxGetApp() .plater() ->get_notification_manager() - ->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::RegularNotification, + ->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::RegularNotificationLevel, GUI::format(_L("Your printer has more extruders than the multi-material painting gizmo supports. For this reason, only the " "first %1% extruders will be able to be used for painting."), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)); } @@ -42,7 +42,7 @@ void GLGizmoMmuSegmentation::on_shutdown() std::string GLGizmoMmuSegmentation::on_get_name() const { // FIXME Lukas H.: Discuss and change shortcut - return (_L("Multimaterial painting") + " [N]").ToUTF8().data(); + return _u8L("Multimaterial painting"); } bool GLGizmoMmuSegmentation::on_is_selectable() const @@ -124,13 +124,12 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["sphere"] = _L("Sphere"); m_desc["pointer"] = _L("Pointer"); - m_desc["tool_type"] = _L("Tool type"); + m_desc["tool_type"] = _L("Tool type"); m_desc["tool_brush"] = _L("Brush"); - m_desc["tool_seed_fill"] = _L("Seed fill"); + m_desc["tool_smart_fill"] = _L("Smart fill"); m_desc["tool_bucket_fill"] = _L("Bucket fill"); - m_desc["seed_fill"] = _L("Seed fill"); - m_desc["seed_fill_angle"] = _L("Seed fill angle"); + m_desc["smart_fill_angle"] = _L("Smart fill angle"); init_extruders_data(); @@ -240,16 +239,16 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float seed_fill_slider_left = m_imgui->calc_text_size(m_desc.at("seed_fill_angle")).x + m_imgui->scaled(1.f); + const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); - const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); - const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); @@ -259,9 +258,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float combo_label_width = std::max(m_imgui->calc_text_size(m_desc.at("first_color")).x, m_imgui->calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f); - const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f); - const float tool_type_radio_seed_fill = m_imgui->calc_text_size(m_desc["tool_seed_fill"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); float caption_max = 0.f; float total_text_max = 0.; @@ -272,12 +271,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott caption_max += m_imgui->scaled(1.f); total_text_max += m_imgui->scaled(1.f); - float sliders_width = std::max(seed_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left)); + float sliders_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left)); float window_width = minimal_slider_width + sliders_width; window_width = std::max(window_width, total_text_max); window_width = std::max(window_width, button_width); window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); - window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_seed_fill); + window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill); window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) { @@ -321,7 +320,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("tool_type")); - float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_seed_fill + m_imgui->scaled(2.f)) / 2.f; + float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_smart_fill + m_imgui->scaled(2.f)) / 2.f; ImGui::NewLine(); @@ -345,9 +344,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } ImGui::SameLine(tool_type_offset + tool_type_radio_brush + m_imgui->scaled(0.f)); - ImGui::PushItemWidth(tool_type_radio_seed_fill); - if (m_imgui->radio_button(m_desc["tool_seed_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::SEED_FILL)) { - m_tool_type = GLGizmoMmuSegmentation::ToolType::SEED_FILL; + ImGui::PushItemWidth(tool_type_radio_smart_fill); + if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::SMART_FILL)) { + m_tool_type = GLGizmoMmuSegmentation::ToolType::SMART_FILL; for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); @@ -362,7 +361,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGui::EndTooltip(); } - ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_seed_fill + m_imgui->scaled(0.f)); + ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill + m_imgui->scaled(0.f)); ImGui::PushItemWidth(tool_type_radio_bucket_fill); if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BUCKET_FILL)) { m_tool_type = GLGizmoMmuSegmentation::ToolType::BUCKET_FILL; @@ -458,14 +457,14 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott m_imgui->disabled_end(); ImGui::Separator(); - } else if(m_tool_type == ToolType::SEED_FILL) { - m_imgui->text(m_desc["seed_fill_angle"] + ":"); + } else if(m_tool_type == ToolType::SMART_FILL) { + m_imgui->text(m_desc["smart_fill_angle"] + ":"); ImGui::AlignTextToFramePadding(); std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo," "placed after the number with no whitespace in between."); ImGui::SameLine(sliders_width); ImGui::PushItemWidth(window_width - sliders_width); - if(m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, SeedFillAngleMin, SeedFillAngleMax, format_str.data())) + if(m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data())) for (auto &triangle_selector : m_triangle_selectors) { triangle_selector->seed_fill_unselect_all_triangles(); triangle_selector->request_update_render_data(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index afd5854a09..ab58ba1866 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -128,6 +128,9 @@ protected: wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; + std::string get_gizmo_entering_text() const override { return _u8L("Entering Multimaterial painting"); } + std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Multimaterial painting"); } + size_t m_first_selected_extruder_idx = 0; size_t m_second_selected_extruder_idx = 1; std::vector m_original_extruders_names; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 1211864ebc..e7b6a0ad6e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -52,7 +52,7 @@ bool GLGizmoMove3D::on_init() std::string GLGizmoMove3D::on_get_name() const { - return (_L("Move") + " [M]").ToUTF8().data(); + return _u8L("Move"); } bool GLGizmoMove3D::on_is_activable() const @@ -141,7 +141,7 @@ void GLGizmoMove3D::on_render() GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) { shader->start_using(); - shader->set_uniform("emission_factor", 0.1); + shader->set_uniform("emission_factor", 0.1f); // draw grabber float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); m_grabbers[m_hover_id].render(true, mean_size); @@ -208,7 +208,7 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box const_cast(&m_vbo_cone)->set_color(-1, color); if (!picking) { shader->start_using(); - shader->set_uniform("emission_factor", 0.1); + shader->set_uniform("emission_factor", 0.1f); } glsafe(::glPushMatrix()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index ac9d7adcf1..155738b327 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -47,20 +47,14 @@ void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) plater->undo_redo_topmost_string_getter(plater->can_undo(), last_snapshot_name); if (activate && !m_internal_stack_active) { - std::string str = get_painter_type() == PainterGizmoType::FDM_SUPPORTS - ? _u8L("Entering Paint-on supports") - : _u8L("Entering Seam painting"); - if (last_snapshot_name != str) + if (std::string str = this->get_gizmo_entering_text(); last_snapshot_name != str) Plater::TakeSnapshot(plater, str); plater->enter_gizmos_stack(); m_internal_stack_active = true; } if (!activate && m_internal_stack_active) { plater->leave_gizmos_stack(); - std::string str = get_painter_type() == PainterGizmoType::SEAM - ? _u8L("Leaving Seam painting") - : _u8L("Leaving Paint-on supports"); - if (last_snapshot_name != str) + if (std::string str = this->get_gizmo_leaving_text(); last_snapshot_name != str) Plater::TakeSnapshot(plater, str); m_internal_stack_active = false; } @@ -288,12 +282,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous : std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax); m_parent.set_as_dirty(); return true; - } else if (m_tool_type == ToolType::SEED_FILL) { - m_seed_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_seed_fill_angle - SeedFillAngleStep, SeedFillAngleMin) - : std::min(m_seed_fill_angle + SeedFillAngleStep, SeedFillAngleMax); + } else if (m_tool_type == ToolType::SMART_FILL) { + m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin) + : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); m_parent.set_as_dirty(); if (m_rr.mesh_id != -1) { - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle, true); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle, true); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_seed_fill_last_mesh_id = m_rr.mesh_id; } @@ -385,10 +379,10 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); assert(m_rr.mesh_id < int(m_triangle_selectors.size())); - if (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) { + if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) { m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state); - if (m_tool_type == ToolType::SEED_FILL) - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle, true); + if (m_tool_type == ToolType::SMART_FILL) + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle, true); else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false, true); else if (m_tool_type == ToolType::BUCKET_FILL) @@ -406,7 +400,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return true; } - if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) { + if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) { if (m_triangle_selectors.empty()) return false; @@ -446,8 +440,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous seed_fill_unselect_all(); assert(m_rr.mesh_id < int(m_triangle_selectors.size())); - if (m_tool_type == ToolType::SEED_FILL) - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle); + if (m_tool_type == ToolType::SMART_FILL) + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_smart_fill_angle); else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false); else if (m_tool_type == ToolType::BUCKET_FILL) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 4db17d5974..6a15ab2a52 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -109,16 +109,16 @@ protected: enum class ToolType { BRUSH, BUCKET_FILL, - SEED_FILL + SMART_FILL }; bool m_triangle_splitting_enabled = true; ToolType m_tool_type = ToolType::BRUSH; - float m_seed_fill_angle = 30.f; + float m_smart_fill_angle = 30.f; - static constexpr float SeedFillAngleMin = 0.0f; - static constexpr float SeedFillAngleMax = 90.f; - static constexpr float SeedFillAngleStep = 1.f; + static constexpr float SmartFillAngleMin = 0.0f; + static constexpr float SmartFillAngleMax = 90.f; + static constexpr float SmartFillAngleStep = 1.f; // It stores the value of the previous mesh_id to which the seed fill was applied. // It is used to detect when the mouse has moved from one volume to another one. @@ -173,6 +173,9 @@ protected: virtual wxString handle_snapshot_action_name(bool shift_down, Button button_down) const = 0; + virtual std::string get_gizmo_entering_text() const = 0; + virtual std::string get_gizmo_leaving_text() const = 0; + friend class ::Slic3r::GUI::GLGizmoMmuSegmentation; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 04e08adc15..54fcfc2c7c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -339,7 +339,7 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick const_cast(&m_cone)->set_color(-1, color); if (!picking) { shader->start_using(); - shader->set_uniform("emission_factor", 0.1); + shader->set_uniform("emission_factor", 0.1f); } glsafe(::glPushMatrix()); @@ -463,7 +463,7 @@ bool GLGizmoRotate3D::on_init() std::string GLGizmoRotate3D::on_get_name() const { - return (_L("Rotate") + " [R]").ToUTF8().data(); + return _u8L("Rotate"); } bool GLGizmoRotate3D::on_is_activable() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 8490469627..b97507166b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -76,7 +76,7 @@ bool GLGizmoScale3D::on_init() std::string GLGizmoScale3D::on_get_name() const { - return (_L("Scale") + " [S]").ToUTF8().data(); + return _u8L("Scale"); } bool GLGizmoScale3D::on_is_activable() const @@ -236,7 +236,7 @@ void GLGizmoScale3D::on_render() GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) { shader->start_using(); - shader->set_uniform("emission_factor", 0.1); + shader->set_uniform("emission_factor", 0.1f); // draw grabbers m_grabbers[0].render(true, grabber_mean_size); m_grabbers[1].render(true, grabber_mean_size); @@ -251,7 +251,7 @@ void GLGizmoScale3D::on_render() GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) { shader->start_using(); - shader->set_uniform("emission_factor", 0.1); + shader->set_uniform("emission_factor", 0.1f); // draw grabbers m_grabbers[2].render(true, grabber_mean_size); m_grabbers[3].render(true, grabber_mean_size); @@ -266,7 +266,7 @@ void GLGizmoScale3D::on_render() GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) { shader->start_using(); - shader->set_uniform("emission_factor", 0.1); + shader->set_uniform("emission_factor", 0.1f); // draw grabbers m_grabbers[4].render(true, grabber_mean_size); m_grabbers[5].render(true, grabber_mean_size); @@ -284,7 +284,7 @@ void GLGizmoScale3D::on_render() GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader != nullptr) { shader->start_using(); - shader->set_uniform("emission_factor", 0.1); + shader->set_uniform("emission_factor", 0.1f); // draw grabbers for (int i = 6; i < 10; ++i) { m_grabbers[i].render(true, grabber_mean_size); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index ff9888e199..a2ee56d9ae 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -48,7 +48,7 @@ bool GLGizmoSeam::on_init() std::string GLGizmoSeam::on_get_name() const { - return (_L("Seam painting") + " [P]").ToUTF8().data(); + return _u8L("Seam painting"); } @@ -79,7 +79,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) const float approx_height = m_imgui->scaled(14.0f); y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, @@ -138,6 +138,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("cursor_size")); ImGui::SameLine(cursor_size_slider_left); ImGui::PushItemWidth(window_width - cursor_size_slider_left); @@ -188,8 +189,10 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) + if (m_c->object_clipper()->get_position() == 0.f) { + ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("clipping_of_view")); + } else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp index 196fe5023a..7a00a9d8e6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp @@ -20,6 +20,9 @@ protected: wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; + std::string get_gizmo_entering_text() const override { return _u8L("Entering Seam painting"); } + std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Seam painting"); } + private: bool on_init() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index ef8e708f9e..03c2561a6c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -42,7 +42,7 @@ bool GLGizmoSimplify::on_init() std::string GLGizmoSimplify::on_get_name() const { - return (_L("Simplify")).ToUTF8().data(); + return _u8L("Simplify"); } void GLGizmoSimplify::on_render() {} @@ -53,6 +53,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi create_gui_cfg(); const Selection &selection = m_parent.get_selection(); int object_idx = selection.get_object_idx(); + if (!is_selected_object(&object_idx)) return; ModelObject *obj = wxGetApp().plater()->model().objects[object_idx]; ModelVolume *act_volume = obj->volumes.front(); @@ -94,7 +95,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; - m_imgui->begin(on_get_name(), flag); + m_imgui->begin(get_name(), flag); size_t triangle_count = m_volume->mesh().its.indices.size(); // already reduced mesh @@ -281,11 +282,11 @@ void GLGizmoSimplify::process() } }; - std::function statusfn = [this](int percent) { + int64_t last = 0; + std::function statusfn = [this, &last](int percent) { m_progress = percent; // check max 4fps - static int64_t last = 0; int64_t now = m_parent.timestamp_now(); if ((now - last) < 250) return; last = now; @@ -327,14 +328,14 @@ void GLGizmoSimplify::on_set_state() { // Closing gizmo. e.g. selecting another one if (GLGizmoBase::m_state == GLGizmoBase::Off) { - // refuse outgoing during simlification - if (m_state != State::settings) { + // object is not selected when it is deleted(cancel and close gizmo) + if (m_state != State::settings && is_selected_object()) { GLGizmoBase::m_state = GLGizmoBase::On; auto notification_manager = wxGetApp().plater()->get_notification_manager(); notification_manager->push_notification( NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotification, + NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("ERROR: Wait until Simplification ends or Cancel process.")); return; } @@ -381,6 +382,22 @@ void GLGizmoSimplify::request_rerender() { }); } +bool GLGizmoSimplify::is_selected_object(int *object_idx) +{ + int index = (object_idx != nullptr) ? *object_idx : + m_parent.get_selection().get_object_idx(); + // no selected object --> can appear after delete model + if (index < 0) { + switch (m_state) { + case State::settings: close(); break; + case State::canceling: break; + default: m_state = State::canceling; + } + return false; + } + return true; +} + // any existing icon filename to not influence GUI const std::string GLGizmoSimplify::M_ICON_FILENAME = "cut.svg"; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 9754e07c13..8d0e5e5626 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -38,6 +38,7 @@ private: void set_its(indexed_triangle_set &its); void create_gui_cfg(); void request_rerender(); + bool is_selected_object(int *object_idx = nullptr); std::atomic_bool m_is_valid_result; // differ what to do in apply std::atomic_bool m_exist_preview; // set when process end diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 6d8ae36ab2..11446d6d78 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -169,7 +169,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const_cast(&m_cone)->set_color(-1, render_color); const_cast(&m_sphere)->set_color(-1, render_color); if (shader && !picking) - shader->set_uniform("emission_factor", 0.5); + shader->set_uniform("emission_factor", 0.5f); // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. glsafe(::glPushMatrix()); @@ -224,7 +224,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) render_color[3] = 0.7f; const_cast(&m_cylinder)->set_color(-1, render_color); if (shader) - shader->set_uniform("emission_factor", 0.5); + shader->set_uniform("emission_factor", 0.5f); for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { if (is_mesh_point_clipped(drain_hole.pos.cast())) continue; @@ -625,7 +625,7 @@ RENDER_AGAIN: //ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); //ImGui::SetNextWindowSize(ImVec2(window_size)); - m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // adjust window position to avoid overlap the view toolbar float win_h = ImGui::GetWindowHeight(); @@ -786,8 +786,7 @@ RENDER_AGAIN: // Following is rendered in both editing and non-editing mode: ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) - { + if (m_c->object_clipper()->get_position() == 0.f) { ImGui::AlignTextToFramePadding(); m_imgui->text(m_desc.at("clipping_of_view")); } @@ -864,7 +863,7 @@ bool GLGizmoSlaSupports::on_is_selectable() const std::string GLGizmoSlaSupports::on_get_name() const { - return (_L("SLA Support Points") + " [L]").ToUTF8().data(); + return _u8L("SLA Support Points"); } CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const @@ -902,15 +901,6 @@ void GLGizmoSlaSupports::on_set_state() return; if (m_state == On && m_old_state != On) { // the gizmo was just turned on - if (! m_parent.get_gizmos_manager().is_serializing()) { - // Only take the snapshot when the USER opens the gizmo. Common gizmos - // data are not yet available, the CallAfter will postpone taking the - // snapshot until they are. No, it does not feel right. - wxGetApp().CallAfter([]() { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Entering SLA gizmo")); - }); - } - // 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; @@ -926,8 +916,6 @@ void GLGizmoSlaSupports::on_set_state() else { // we are actually shutting down disable_editing_mode(); // so it is not active next time the gizmo opens - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Leaving SLA gizmo")); - m_normal_cache.clear(); m_old_mo_id = -1; } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index cb60c0e258..8e30aa6b85 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -67,6 +67,8 @@ public: bool has_backend_supports() const; void reslice_SLA_supports(bool postpone_error_messages = false) const; + bool wants_enter_leave_snapshots() const override { return true; } + private: bool on_init() override; void on_update(const UpdateData& data) override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 18ce9d73c4..b22e72be9e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -152,7 +152,7 @@ void InstancesHider::on_update() canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst); canvas->set_use_clipping_planes(true); // Some objects may be sinking, do not show whatever is below the bed. - canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), 0.)); + canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits::max())); @@ -164,12 +164,7 @@ void InstancesHider::on_update() m_clippers.clear(); for (const TriangleMesh* mesh : meshes) { m_clippers.emplace_back(new MeshClipper); - if (mo->get_instance_min_z(active_inst) < SINKING_Z_THRESHOLD) - m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), 0.)); - else { - m_clippers.back()->set_plane(ClippingPlane::ClipsNothing()); - m_clippers.back()->set_limiting_plane(ClippingPlane::ClipsNothing()); - } + m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); m_clippers.back()->set_mesh(*mesh); } m_old_meshes = meshes; @@ -218,8 +213,16 @@ void InstancesHider::render_cut() const clipper->set_limiting_plane(ClippingPlane::ClipsNothing()); glsafe(::glPushMatrix()); - glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); + if (mv->is_model_part()) + glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); + else { + const std::array& c = color_from_model_volume(*mv); + glsafe(::glColor4f(c[0], c[1], c[2], c[3])); + } + glsafe(::glPushAttrib(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_DEPTH_TEST)); clipper->render_cut(); + glsafe(::glPopAttrib()); glsafe(::glPopMatrix()); ++clipper_id; @@ -385,8 +388,6 @@ void ObjectClipper::on_update() m_active_inst_bb_radius = mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); - //if (has_hollowed && m_clp_ratio != 0.) - // m_clp_ratio = 0.25; } } @@ -407,7 +408,6 @@ void ObjectClipper::render_cut() const const SelectionInfo* sel_info = get_pool()->selection_info(); const ModelObject* mo = sel_info->model_object(); Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); - const bool sinking = mo->bounding_box().min.z() < SINKING_Z_THRESHOLD; size_t clipper_id = 0; for (const ModelVolume* mv : mo->volumes) { @@ -418,9 +418,7 @@ void ObjectClipper::render_cut() const auto& clipper = m_clippers[clipper_id]; clipper->set_plane(*m_clp); clipper->set_transformation(trafo); - clipper->set_limiting_plane(sinking ? - ClippingPlane(Vec3d::UnitZ(), 0.) - : ClippingPlane::ClipsNothing()); + clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); glsafe(::glPushMatrix()); glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); clipper->render_cut(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index ec111236ee..2d1184f2e7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -23,6 +23,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp" #include "slic3r/GUI/Gizmos/GLGizmoEmboss.hpp" +#include "libslic3r/format.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" @@ -165,10 +166,8 @@ void GLGizmosManager::refresh_on_off_state() return; if (m_current != Undefined - && ! m_gizmos[m_current]->is_activable()) { - activate_gizmo(Undefined); + && ! m_gizmos[m_current]->is_activable() && activate_gizmo(Undefined)) update_data(); - } } void GLGizmosManager::reset_all_states() @@ -183,14 +182,28 @@ void GLGizmosManager::reset_all_states() bool GLGizmosManager::open_gizmo(EType type) { int idx = int(type); - if (m_gizmos[idx]->is_activable()) { - activate_gizmo(m_current == idx ? Undefined : (EType)idx); + if (m_gizmos[idx]->is_activable() + && activate_gizmo(m_current == idx ? Undefined : (EType)idx)) { update_data(); return true; } return false; } + +bool GLGizmosManager::check_gizmos_closed_except(EType type) const +{ + if (get_current_type() != type && get_current_type() != Undefined) { + wxGetApp().plater()->get_notification_manager()->push_notification( + NotificationType::CustomSupportsAndSeamRemovedAfterRepair, + NotificationManager::NotificationLevel::RegularNotificationLevel, + _u8L("ERROR: Please close all manipulators available from " + "the left toolbar first")); + return false; + } + return true; +} + void GLGizmosManager::set_hover_id(int id) { if (!m_enabled || m_current == Undefined) @@ -1196,31 +1209,35 @@ std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos) return name; } -void GLGizmosManager::activate_gizmo(EType type) +bool GLGizmosManager::activate_gizmo(EType type) { if (m_gizmos.empty() || m_current == type) - return; + return true; - if (m_current != Undefined) { - m_gizmos[m_current]->set_state(GLGizmoBase::Off); - if (m_gizmos[m_current]->get_state() != GLGizmoBase::Off) - return; // gizmo refused to be turned off, do nothing. + GLGizmoBase* old_gizmo = m_current == Undefined ? nullptr : m_gizmos[m_current].get(); + GLGizmoBase* new_gizmo = type == Undefined ? nullptr : m_gizmos[type].get(); + + if (old_gizmo) { + old_gizmo->set_state(GLGizmoBase::Off); + if (old_gizmo->get_state() != GLGizmoBase::Off) + return false; // gizmo refused to be turned off, do nothing. + + if (! m_parent.get_gizmos_manager().is_serializing() + && old_gizmo->wants_enter_leave_snapshots()) + Plater::TakeSnapshot snapshot(wxGetApp().plater(), + Slic3r::format(_utf8("Leaving %1%"), old_gizmo->get_name(false))); } + if (new_gizmo && ! m_parent.get_gizmos_manager().is_serializing() + && new_gizmo->wants_enter_leave_snapshots()) + Plater::TakeSnapshot snapshot(wxGetApp().plater(), + Slic3r::format(_utf8("Entering %1%"), new_gizmo->get_name(false))); + m_current = type; - // Updating common data should be left to the update_data function, which - // is always called after this one. activate_gizmo can be called by undo/redo, - // when selection is not yet deserialized, so the common data would update - // incorrectly (or crash if relying on unempty selection). Undo/redo stack - // will also call update_data, after selection is restored. - - //m_common_gizmos_data->update(get_current() - // ? get_current()->get_requirements() - // : CommonGizmosDataID(0)); - - if (type != Undefined) - m_gizmos[type]->set_state(GLGizmoBase::On); + if (new_gizmo) + new_gizmo->set_state(GLGizmoBase::On); + return true; } @@ -1242,7 +1259,7 @@ bool GLGizmosManager::is_in_editing_mode(bool error_notification) const if (error_notification) wxGetApp().plater()->get_notification_manager()->push_notification( NotificationType::QuitSLAManualMode, - NotificationManager::NotificationLevel::ErrorNotification, + NotificationManager::NotificationLevel::ErrorNotificationLevel, _u8L("You are currently editing SLA support points. Please, " "apply or discard your changes first.")); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index e84c149369..8111eb4e6c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -105,7 +105,7 @@ private: std::vector get_selectable_idxs() const; size_t get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const; - void activate_gizmo(EType type); + bool activate_gizmo(EType type); struct MouseCapture { @@ -177,6 +177,7 @@ public: void reset_all_states(); bool is_serializing() const { return m_serializing; } bool open_gizmo(EType type); + bool check_gizmos_closed_except(EType) const; void set_hover_id(int id); void enable_grabber(EType type, unsigned int id, bool enable); diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index ead7a8f29a..53435e318c 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -4,6 +4,7 @@ #include "I18N.hpp" #include "GUI_ObjectList.hpp" #include "GLCanvas3D.hpp" +#include "MainFrame.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Config.hpp" @@ -57,10 +58,6 @@ inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, f ImGui::PushStyleColor(idx, col); } - - - - void write_used_binary(const std::vector& ids) { boost::filesystem::ofstream file((boost::filesystem::path(data_dir()) / "cache" / "hints.cereal"), std::ios::binary); @@ -77,7 +74,12 @@ void write_used_binary(const std::vector& ids) } void read_used_binary(std::vector& ids) { - boost::filesystem::ifstream file((boost::filesystem::path(data_dir()) / "cache" / "hints.cereal")); + boost::filesystem::path path(boost::filesystem::path(data_dir()) / "cache" / "hints.cereal"); + if (!boost::filesystem::exists(path)) { + BOOST_LOG_TRIVIAL(warning) << "Failed to load to hints.cereal. File does not exists. " << path.string(); + return; + } + boost::filesystem::ifstream file(path); cereal::BinaryInputArchive archive(file); HintsCerealData cd; try @@ -245,6 +247,13 @@ HintDatabase::~HintDatabase() write_used_binary(m_used_ids); } } +void HintDatabase::uninit() +{ + if (m_initialized) { + write_used_binary(m_used_ids); + } + m_initialized = false; +} void HintDatabase::init() { load_hints_from_file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.ini")); @@ -379,7 +388,13 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, []() { // Deselect all objects, otherwise gallery wont show. wxGetApp().plater()->canvas3D()->deselect_all(); - wxGetApp().obj_list()->load_shape_object_from_gallery(); } }; + wxGetApp().obj_list()->load_shape_object_from_gallery(); } + }; + m_loaded_hints.emplace_back(hint_data); + } else if (dict["hypertext_type"] == "menubar") { + wxString menu(_L("&" + dict["hypertext_menubar_menu_name"])); + wxString item(_L(dict["hypertext_menubar_item_name"])); + HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [menu, item]() { wxGetApp().mainframe->open_menubar_item(menu, item); } }; m_loaded_hints.emplace_back(hint_data); } } else { @@ -997,7 +1012,7 @@ void NotificationManager::HintNotification::retrieve_data(bool new_hint/* = true if(hint_data != nullptr) { NotificationData nd { NotificationType::DidYouKnowHint, - NotificationLevel::RegularNotification, + NotificationLevel::RegularNotificationLevel, 0, hint_data->text, hint_data->hypertext, nullptr, diff --git a/src/slic3r/GUI/HintNotification.hpp b/src/slic3r/GUI/HintNotification.hpp index c1e7c0ed3c..e5dd90bdbd 100644 --- a/src/slic3r/GUI/HintNotification.hpp +++ b/src/slic3r/GUI/HintNotification.hpp @@ -47,6 +47,9 @@ public: return 0; return m_loaded_hints.size(); } + // resets m_initiailized to false and writes used if was initialized + // used when reloading in runtime - like change language + void uninit(); private: void init(); void load_hints_from_file(const boost::filesystem::path& path); diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index 2744920da8..2ccb7a04f1 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -9,6 +9,7 @@ namespace Slic3r { class ModelInstance; namespace GUI { +class NotificationManager; class ArrangeJob : public PlaterJob { @@ -39,8 +40,8 @@ protected: void process() override; public: - ArrangeJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} + ArrangeJob(std::shared_ptr nm, Plater *plater) + : PlaterJob{nm, plater} {} int status_range() const override diff --git a/src/slic3r/GUI/Jobs/FillBedJob.hpp b/src/slic3r/GUI/Jobs/FillBedJob.hpp index bf407656d1..548974fa6b 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.hpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.hpp @@ -6,6 +6,7 @@ namespace Slic3r { namespace GUI { class Plater; +class NotificationManager; class FillBedJob : public PlaterJob { @@ -27,8 +28,8 @@ protected: void process() override; public: - FillBedJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater} + FillBedJob(std::shared_ptr nm, Plater *plater) + : PlaterJob{nm, plater} {} int status_range() const override diff --git a/src/slic3r/GUI/Jobs/Job.cpp b/src/slic3r/GUI/Jobs/Job.cpp index 6346227aab..c198de40ca 100644 --- a/src/slic3r/GUI/Jobs/Job.cpp +++ b/src/slic3r/GUI/Jobs/Job.cpp @@ -2,9 +2,11 @@ #include #include "Job.hpp" +#include "../NotificationManager.hpp" #include #include + namespace Slic3r { void GUI::Job::run(std::exception_ptr &eptr) @@ -30,8 +32,8 @@ void GUI::Job::update_status(int st, const wxString &msg) wxQueueEvent(this, evt); } -GUI::Job::Job(std::shared_ptr pri) - : m_progress(std::move(pri)) +GUI::Job::Job(std::shared_ptr nm) + : m_notifications(nm) { m_thread_evt_id = wxNewId(); @@ -40,21 +42,21 @@ GUI::Job::Job(std::shared_ptr pri) auto msg = evt.GetString(); if (!msg.empty() && !m_worker_error) - m_progress->set_status_text(msg.ToUTF8().data()); + m_notifications->progress_indicator_set_status_text(msg.ToUTF8().data()); if (m_finalized) return; - m_progress->set_progress(evt.GetInt()); + m_notifications->progress_indicator_set_progress(evt.GetInt()); if (evt.GetInt() == status_range() || m_worker_error) { // set back the original range and cancel callback - m_progress->set_range(m_range); - m_progress->set_cancel_callback(); + m_notifications->progress_indicator_set_range(m_range); + m_notifications->progress_indicator_set_cancel_callback(); wxEndBusyCursor(); if (m_worker_error) { m_finalized = true; - m_progress->set_status_text(""); - m_progress->set_progress(m_range); + m_notifications->progress_indicator_set_status_text(""); + m_notifications->progress_indicator_set_progress(m_range); on_exception(m_worker_error); } else { @@ -86,12 +88,12 @@ void GUI::Job::start() prepare(); // Save the current status indicatior range and push the new one - m_range = m_progress->get_range(); - m_progress->set_range(status_range()); + m_range = m_notifications->progress_indicator_get_range(); + m_notifications->progress_indicator_set_range(status_range()); // init cancellation flag and set the cancel callback m_canceled.store(false); - m_progress->set_cancel_callback( + m_notifications->progress_indicator_set_cancel_callback( [this]() { m_canceled.store(true); }); m_finalized = false; diff --git a/src/slic3r/GUI/Jobs/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp index 8243ce9430..ed80d8b5fc 100644 --- a/src/slic3r/GUI/Jobs/Job.hpp +++ b/src/slic3r/GUI/Jobs/Job.hpp @@ -8,14 +8,13 @@ #include -#include "ProgressIndicator.hpp" - #include #include namespace Slic3r { namespace GUI { +class NotificationManager; // A class to handle UI jobs like arranging and optimizing rotation. // These are not instant jobs, the user has to be informed about their // state in the status progress indicator. On the other hand they are @@ -33,7 +32,7 @@ class Job : public wxEvtHandler boost::thread m_thread; std::atomic m_running{false}, m_canceled{false}; bool m_finalized = false, m_finalizing = false; - std::shared_ptr m_progress; + std::shared_ptr m_notifications; std::exception_ptr m_worker_error = nullptr; void run(std::exception_ptr &); @@ -65,7 +64,7 @@ protected: } public: - Job(std::shared_ptr pri); + Job(std::shared_ptr nm); bool is_finalized() const { return m_finalized; } diff --git a/src/slic3r/GUI/Jobs/PlaterJob.hpp b/src/slic3r/GUI/Jobs/PlaterJob.hpp index fcf0a54b80..52e5741aa4 100644 --- a/src/slic3r/GUI/Jobs/PlaterJob.hpp +++ b/src/slic3r/GUI/Jobs/PlaterJob.hpp @@ -6,6 +6,7 @@ namespace Slic3r { namespace GUI { class Plater; +class NotificationManager; class PlaterJob : public Job { protected: @@ -15,8 +16,8 @@ protected: public: - PlaterJob(std::shared_ptr pri, Plater *plater): - Job{std::move(pri)}, m_plater{plater} {} + PlaterJob(std::shared_ptr nm, Plater *plater): + Job{nm}, m_plater{plater} {} }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index cdb367f23a..f967bb7a37 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -10,6 +10,8 @@ namespace Slic3r { namespace GUI { +class NotificationManager; + class RotoptimizeJob : public PlaterJob { using FindFn = std::function pri, Plater *plater) - : PlaterJob{std::move(pri), plater} + RotoptimizeJob(std::shared_ptr nm, Plater *plater) + : PlaterJob{nm, plater} {} void finalize() override; diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 027bf68492..9b9151f773 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/NotificationManager.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" @@ -124,8 +125,8 @@ public: priv(Plater *plt) : plater{plt} {} }; -SLAImportJob::SLAImportJob(std::shared_ptr pri, Plater *plater) - : PlaterJob{std::move(pri), plater}, p{std::make_unique(plater)} +SLAImportJob::SLAImportJob(std::shared_ptr nm, Plater *plater) + : PlaterJob{nm, plater}, p{std::make_unique(plater)} {} SLAImportJob::~SLAImportJob() = default; diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.hpp b/src/slic3r/GUI/Jobs/SLAImportJob.hpp index 583c986559..7bc4d5e7e3 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.hpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.hpp @@ -5,6 +5,8 @@ namespace Slic3r { namespace GUI { +class NotificationManager; + class SLAImportJob : public PlaterJob { class priv; @@ -16,7 +18,7 @@ protected: void finalize() override; public: - SLAImportJob(std::shared_ptr pri, Plater *plater); + SLAImportJob(std::shared_ptr nm, Plater *plater); ~SLAImportJob(); void reset(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 329a9a62a7..6d5fbf11c7 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -14,6 +14,7 @@ #include #include +#include #include "libslic3r/Print.hpp" #include "libslic3r/Polygon.hpp" @@ -141,11 +142,11 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S default: case GUI_App::EAppMode::Editor: m_taskbar_icon = std::make_unique(wxTBI_DOCK); - m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer"); + m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer-mac_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer"); break; case GUI_App::EAppMode::GCodeViewer: m_taskbar_icon = std::make_unique(wxTBI_DOCK); - m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer-gcodeviewer_128px.png"), wxBITMAP_TYPE_PNG), "G-code Viewer"); + m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer-gcodeviewer-mac_128px.png"), wxBITMAP_TYPE_PNG), "G-code Viewer"); break; } #endif // __APPLE__ @@ -154,13 +155,13 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S SetIcon(main_frame_icon(wxGetApp().get_app_mode())); // initialize status bar - m_statusbar = std::make_shared(this); - m_statusbar->set_font(GUI::wxGetApp().normal_font()); - if (wxGetApp().is_editor()) - m_statusbar->embed(this); - m_statusbar->set_status_text(_L("Version") + " " + - SLIC3R_VERSION + " - " + - _L("Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases")); +// m_statusbar = std::make_shared(this); +// m_statusbar->set_font(GUI::wxGetApp().normal_font()); +// if (wxGetApp().is_editor()) +// m_statusbar->embed(this); +// m_statusbar->set_status_text(_L("Version") + " " + +// SLIC3R_VERSION + " - " + +// _L("Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases")); // initialize tabpanel and menubar init_tabpanel(); @@ -620,7 +621,7 @@ void MainFrame::update_title() wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : ""; if (!dirty_marker.empty() || !project.empty()) { if (!dirty_marker.empty() && project.empty()) - project = _("Untitled"); + project = _L("Untitled"); title = dirty_marker + project + " - "; } } @@ -819,20 +820,31 @@ bool MainFrame::is_active_and_shown_tab(Tab* tab) bool MainFrame::can_start_new_project() const { - return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || !m_plater->model().objects.empty()); + return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || GetTitle().StartsWith('*') || !m_plater->model().objects.empty()); } bool MainFrame::can_save() const { +#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED + return (m_plater != nullptr) && + !m_plater->canvas3D()->get_gizmos_manager().is_in_editing_mode(false) && + !m_plater->get_project_filename().empty() && m_plater->is_project_dirty(); +#else return (m_plater != nullptr) && !m_plater->model().objects.empty() && !m_plater->canvas3D()->get_gizmos_manager().is_in_editing_mode(false) && !m_plater->get_project_filename().empty() && m_plater->is_project_dirty(); +#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED } bool MainFrame::can_save_as() const { +#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED + return (m_plater != nullptr) && + !m_plater->canvas3D()->get_gizmos_manager().is_in_editing_mode(false); +#else return (m_plater != nullptr) && !m_plater->model().objects.empty() && !m_plater->canvas3D()->get_gizmos_manager().is_in_editing_mode(false); +#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED } void MainFrame::save_project() @@ -1021,7 +1033,7 @@ void MainFrame::on_sys_color_changed() wxGetApp().init_label_colours(); #ifdef __WXMSW__ wxGetApp().UpdateDarkUI(m_tabpanel); - m_statusbar->update_dark_ui(); + // m_statusbar->update_dark_ui(); #ifdef _MSW_DARK_MODE // update common mode sizer if (!wxGetApp().tabs_as_menu()) @@ -1227,9 +1239,10 @@ void MainFrame::init_menubar_as_editor() append_menu_item(export_menu, wxID_ANY, _L("Export plate as STL &including supports") + dots, _L("Export current plate as STL including supports"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(true); }, "export_plater", nullptr, [this](){return can_export_supports(); }, this); - append_menu_item(export_menu, wxID_ANY, _L("Export plate as &AMF") + dots, _L("Export current plate as AMF"), - [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "export_plater", nullptr, - [this](){return can_export_model(); }, this); +// Deprecating AMF export. Let's wait for user feedback. +// append_menu_item(export_menu, wxID_ANY, _L("Export plate as &AMF") + dots, _L("Export current plate as AMF"), +// [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "export_plater", nullptr, +// [this](){return can_export_model(); }, this); export_menu->AppendSeparator(); append_menu_item(export_menu, wxID_ANY, _L("Export &toolpaths as OBJ") + dots, _L("Export toolpaths as OBJ"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr, @@ -1468,6 +1481,33 @@ void MainFrame::init_menubar_as_editor() update_menubar(); } +void MainFrame::open_menubar_item(const wxString& menu_name,const wxString& item_name) +{ + if (m_menubar == nullptr) + return; + // Get menu object from menubar + int menu_index = m_menubar->FindMenu(menu_name); + wxMenu* menu = m_menubar->GetMenu(menu_index); + if (menu == nullptr) { + BOOST_LOG_TRIVIAL(error) << "Mainframe open_menubar_item function couldn't find menu: " << menu_name; + return; + } + // Get item id from menu + int item_id = menu->FindItem(item_name); + if (item_id == wxNOT_FOUND) + { + // try adding three dots char + item_id = menu->FindItem(item_name + dots); + } + if (item_id == wxNOT_FOUND) + { + BOOST_LOG_TRIVIAL(error) << "Mainframe open_menubar_item function couldn't find item: " << item_name; + return; + } + // wxEVT_MENU will trigger item + wxPostEvent((wxEvtHandler*)menu, wxCommandEvent(wxEVT_MENU, item_id)); +} + void MainFrame::init_menubar_as_gcodeviewer() { wxMenu* fileMenu = new wxMenu; diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index aced5efbe4..487d002af2 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -158,6 +158,8 @@ public: void init_menubar_as_editor(); void init_menubar_as_gcodeviewer(); void update_menubar(); + // Open item in menu by menu and item name (in actual language) + void open_menubar_item(const wxString& menu_name,const wxString& item_name); #ifdef _WIN32 void show_tabs_menu(bool show); #endif @@ -202,7 +204,7 @@ public: wxWindow* m_plater_page{ nullptr }; wxProgressDialog* m_progress_dialog { nullptr }; PrintHostQueueDialog* m_printhost_queue_dlg; - std::shared_ptr m_statusbar; +// std::shared_ptr m_statusbar; #ifdef __APPLE__ std::unique_ptr m_taskbar_icon; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index ba83bdbff7..75232c9301 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -106,7 +106,6 @@ void MeshClipper::recalculate_triangles() Transform3d tr = Transform3d::Identity(); tr.rotate(q); tr = m_trafo.get_matrix() * tr; - height_mesh += 0.001f; // to avoid z-fighting if (m_limiting_plane != ClippingPlane::ClipsNothing()) { @@ -165,6 +164,8 @@ void MeshClipper::recalculate_triangles() m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.); + tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting + m_vertex_array.release_geometry(); for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) { m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index ec6c337c06..65c3261168 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -112,7 +112,7 @@ public: // The class references extern TriangleMesh, which must stay alive // during MeshRaycaster existence. MeshRaycaster(const TriangleMesh& mesh) - : m_emesh(mesh) + : m_emesh(mesh, true) // calculate epsilon for triangle-ray intersection from an average edge length { m_normals.reserve(mesh.stl.facet_start.size()); for (const stl_facet& facet : mesh.stl.facet_start) diff --git a/src/slic3r/GUI/Notebook.cpp b/src/slic3r/GUI/Notebook.cpp index b328fc583f..9c5ccb834e 100644 --- a/src/slic3r/GUI/Notebook.cpp +++ b/src/slic3r/GUI/Notebook.cpp @@ -45,11 +45,12 @@ void ButtonsListCtrl::OnPaint(wxPaintEvent&) if (m_selection < 0 || m_selection >= (int)m_pageButtons.size()) return; - // highlight selected button - const wxColour& selected_btn_bg = Slic3r::GUI::wxGetApp().get_color_selected_btn_bg(); const wxColour& default_btn_bg = Slic3r::GUI::wxGetApp().get_highlight_default_clr(); const wxColour& btn_marker_color = Slic3r::GUI::wxGetApp().get_color_hovered_btn_label(); + + // highlight selected notebook button + for (int idx = 0; idx < int(m_pageButtons.size()); idx++) { wxButton* btn = m_pageButtons[idx]; @@ -63,6 +64,25 @@ void ButtonsListCtrl::OnPaint(wxPaintEvent&) dc.DrawRectangle(pos.x, pos.y + size.y, size.x, sz.y - size.y); } + // highlight selected mode button + + if (m_mode_sizer) { + const std::vector& mode_btns = m_mode_sizer->get_btns(); + for (int idx = 0; idx < int(mode_btns.size()); idx++) { + ModeButton* btn = mode_btns[idx]; + btn->SetBackgroundColour(btn->is_selected() ? selected_btn_bg : default_btn_bg); + + //wxPoint pos = btn->GetPosition(); + //wxSize size = btn->GetSize(); + //const wxColour& clr = btn->is_selected() ? btn_marker_color : default_btn_bg; + //dc.SetPen(clr); + //dc.SetBrush(clr); + //dc.DrawRectangle(pos.x, pos.y + size.y, size.x, sz.y - size.y); + } + } + + // Draw orange bottom line + dc.SetPen(btn_marker_color); dc.SetBrush(btn_marker_color); dc.DrawRectangle(1, sz.y - m_line_margin, sz.x, m_line_margin); diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 3278a5a6e3..0841efcb06 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -33,32 +33,32 @@ wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClic wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent); const NotificationManager::NotificationData NotificationManager::basic_notifications[] = { - {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, - {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), + {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotificationLevel, 10, _u8L("3D Mouse disconnected.") }, + {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("Configuration update is available."), _u8L("See more."), [](wxEvtHandler* evnthndlr) { if (evnthndlr != nullptr) wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); return true; } }, - {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { + {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, - {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10, + {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotificationLevel, 10, _u8L("You have just added a G-code for color change, but its value is empty.\n" "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, - {NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotification, 10, + {NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotificationLevel, 10, _u8L("No color change event was added to the print. The print does not look like a sign.") }, - {NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, + {NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10, _u8L("Desktop integration was successful.") }, - {NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotification, 10, + {NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10, _u8L("Desktop integration failed.") }, - {NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, + {NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10, _u8L("Undo desktop integration was successful.") }, - {NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotification, 10, + {NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10, _u8L("Undo desktop integration failed.") }, - //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, - //{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") }, - //{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification + //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, + //{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20, _u8L("Loading of model has Failed") }, + //{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification }; namespace { @@ -255,13 +255,13 @@ bool NotificationManager::PopNotification::push_background_color() push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); return true; } - if (m_data.level == NotificationLevel::ErrorNotification) { + if (m_data.level == NotificationLevel::ErrorNotificationLevel) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); return true; } - if (m_data.level == NotificationLevel::WarningNotification) { + if (m_data.level == NotificationLevel::WarningNotificationLevel) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; backcolor.y += 0.15f; @@ -276,9 +276,9 @@ void NotificationManager::PopNotification::count_spaces() m_line_height = ImGui::CalcTextSize("A").y; m_left_indentation = m_line_height; - if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { + if (m_data.level == NotificationLevel::ErrorNotificationLevel || m_data.level == NotificationLevel::WarningNotificationLevel) { std::string text; - text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); float picture_width = ImGui::CalcTextSize(text.c_str()).x; m_left_indentation = picture_width + m_line_height / 2; } @@ -505,9 +505,9 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui) { - if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { + if (m_data.level == NotificationLevel::ErrorNotificationLevel || m_data.level == NotificationLevel::WarningNotificationLevel) { std::string text; - text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); ImGui::SetCursorPosX(m_line_height / 3); ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); imgui.text(text.c_str()); @@ -583,19 +583,23 @@ bool NotificationManager::PopNotification::update_state(bool paused, const int64 int64_t now = GLCanvas3D::timestamp_now(); + // reset fade opacity for non-closing notifications or hover during fading + if (m_state != EState::FadingOut && m_state != EState::ClosePending && m_state != EState::Finished) { + m_current_fade_opacity = 1.0f; + } + // reset timers - hovered state is set in render if (m_state == EState::Hovered) { - m_current_fade_opacity = 1.0f; m_state = EState::Unknown; init(); // Timers when not fading - } else if (m_state != EState::NotFading && m_state != EState::FadingOut && m_data.duration != 0 && !paused) { + } else if (m_state != EState::NotFading && m_state != EState::FadingOut && get_duration() != 0 && !paused) { int64_t up_time = now - m_notification_start; - if (up_time >= m_data.duration * 1000) { + if (up_time >= get_duration() * 1000) { m_state = EState::FadingOut; m_fading_start = now; } else { - m_next_render = m_data.duration * 1000 - up_time; + m_next_render = get_duration() * 1000 - up_time; } } // Timers when fading @@ -626,53 +630,6 @@ bool NotificationManager::PopNotification::update_state(bool paused, const int64 return false; } -NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool large) : - NotificationManager::PopNotification(n, id_provider, evt_handler) -{ - set_large(large); -} -void NotificationManager::SlicingCompleteLargeNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) -{ - if (!m_is_large) - PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); - else { - ImVec2 win_size(win_size_x, win_size_y); - ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str()); - float x_offset = m_left_indentation; - std::string fulltext = m_text1 + m_hypertext + m_text2; - ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str()); - float cursor_y = win_size.y / 2 - text_size.y / 2; - if (m_has_print_info) { - x_offset = 20; - cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2; - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(cursor_y); - imgui.text(m_print_info.c_str()); - cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2; - } - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(cursor_y); - imgui.text(m_text1.c_str()); - - render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext); - } -} -void NotificationManager::SlicingCompleteLargeNotification::set_print_info(const std::string &info) -{ - m_print_info = info; - m_has_print_info = true; - if (m_is_large) - m_lines_count = 2; -} -void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l) -{ - m_is_large = l; - //FIXME this information should not be lost (change m_data?) -// m_counting_down = !l; - m_hypertext = l ? _u8L("Export G-Code.") : std::string(); - m_state = l ? EState::Shown : EState::Hidden; - init(); -} //---------------ExportFinishedNotification----------- void NotificationManager::ExportFinishedNotification::count_spaces() { @@ -680,9 +637,9 @@ void NotificationManager::ExportFinishedNotification::count_spaces() m_line_height = ImGui::CalcTextSize("A").y; m_left_indentation = m_line_height; - if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { + if (m_data.level == NotificationLevel::ErrorNotificationLevel || m_data.level == NotificationLevel::WarningNotificationLevel) { std::string text; - text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); float picture_width = ImGui::CalcTextSize(text.c_str()).x; m_left_indentation = picture_width + m_line_height / 2; } @@ -793,6 +750,9 @@ void NotificationManager::ProgressBarNotification::init() { PopNotification::init(); //m_lines_count++; + if (m_endlines.empty()) { + m_endlines.push_back(0); + } if(m_lines_count >= 2) { m_lines_count = 3; m_multiline = true; @@ -897,8 +857,17 @@ void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgu ImVec2 lineEnd = ImVec2(win_pos_x - m_window_width_offset, win_pos_y + win_size_y / 2 + (m_multiline ? m_line_height / 2 : 0)); ImVec2 lineStart = ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y / 2 + (m_multiline ? m_line_height / 2 : 0)); ImVec2 midPoint = ImVec2(lineStart.x + (lineEnd.x - lineStart.x) * m_percentage, lineStart.y); - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); - ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (m_current_fade_opacity * 255.f)), m_line_height * 0.2f); + ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (m_current_fade_opacity * 255.f)), m_line_height * 0.2f); + if (m_render_percentage) { + std::string text; + std::stringstream stream; + stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "%"; + text = stream.str(); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); + imgui.text(text.c_str()); + } } //------PrintHostUploadNotification---------------- void NotificationManager::PrintHostUploadNotification::init() @@ -915,7 +884,7 @@ void NotificationManager::PrintHostUploadNotification::count_spaces() m_left_indentation = m_line_height; if (m_uj_state == UploadJobState::PB_ERROR) { std::string text; - text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); + text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); float picture_width = ImGui::CalcTextSize(text.c_str()).x; m_left_indentation = picture_width + m_line_height / 2; } @@ -1102,11 +1071,382 @@ void NotificationManager::UpdatedItemsInfoNotification::render_left_sign(ImGuiWr imgui.text(text.c_str()); } +//------SlicingProgressNotificastion +void NotificationManager::SlicingProgressNotification::init() +{ + if (m_sp_state == SlicingProgressState::SP_PROGRESS) { + ProgressBarNotification::init(); + //if (m_state == EState::NotFading && m_percentage >= 1.0f) + // m_state = EState::Shown; + } + else { + PopNotification::init(); + } + +} +void NotificationManager::SlicingProgressNotification::set_progress_state(float percent) +{ + if (percent < 0.f) + set_progress_state(SlicingProgressState::SP_CANCELLED); + else if (percent >= 1.f) + set_progress_state(SlicingProgressState::SP_COMPLETED); + else + set_progress_state(SlicingProgressState::SP_PROGRESS, percent); +} +void NotificationManager::SlicingProgressNotification::set_progress_state(NotificationManager::SlicingProgressNotification::SlicingProgressState state, float percent/* = 0.f*/) +{ + switch (state) + { + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_NO_SLICING: + m_state = EState::Hidden; + set_percentage(-1); + m_has_print_info = false; + set_export_possible(false); + break; + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_PROGRESS: + set_percentage(percent); + m_has_cancel_button = true; + break; + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_CANCELLED: + set_percentage(-1); + m_has_cancel_button = false; + m_has_print_info = false; + set_export_possible(false); + break; + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_COMPLETED: + set_percentage(1); + m_has_cancel_button = false; + m_has_print_info = false; + // m_export_possible is important only for PROGRESS state, thus we can reset it here + set_export_possible(false); + break; + default: + break; + } + m_sp_state = state; +} +void NotificationManager::SlicingProgressNotification::set_status_text(const std::string& text) +{ + switch (m_sp_state) + { + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_NO_SLICING: + m_state = EState::Hidden; + break; + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_PROGRESS: + { + NotificationData data{ NotificationType::SlicingProgress, NotificationLevel::ProgressBarNotificationLevel, 0, text + ".", m_is_fff ? _u8L("Export G-Code.") : _u8L("Export.") }; + update(data); + m_state = EState::NotFading; + } + break; + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_CANCELLED: + { + NotificationData data{ NotificationType::SlicingProgress, NotificationLevel::ProgressBarNotificationLevel, 0, text }; + update(data); + m_state = EState::Shown; + } + break; + case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_COMPLETED: + { + NotificationData data{ NotificationType::SlicingProgress, NotificationLevel::ProgressBarNotificationLevel, 0, _u8L("Slicing finished."), m_is_fff ? _u8L("Export G-Code.") : _u8L("Export.") }; + update(data); + m_state = EState::Shown; + } + break; + default: + break; + } +} +void NotificationManager::SlicingProgressNotification::set_print_info(const std::string& info) +{ + if (m_sp_state != SlicingProgressState::SP_COMPLETED) { + set_progress_state (SlicingProgressState::SP_COMPLETED); + } else { + m_has_print_info = true; + m_print_info = info; + } +} +void NotificationManager::SlicingProgressNotification::set_sidebar_collapsed(bool collapsed) +{ + m_sidebar_collapsed = collapsed; + if (m_sp_state == SlicingProgressState::SP_COMPLETED) + m_state = EState::Shown; +} + +void NotificationManager::SlicingProgressNotification::on_cancel_button() +{ + if (m_cancel_callback){ + m_cancel_callback(); + } +} +int NotificationManager::SlicingProgressNotification::get_duration() +{ + if (m_sp_state == SlicingProgressState::SP_CANCELLED) + return 10; + else if (m_sp_state == SlicingProgressState::SP_COMPLETED && !m_sidebar_collapsed) + return 5; + else + return 0; +} +bool NotificationManager::SlicingProgressNotification::update_state(bool paused, const int64_t delta) +{ + bool ret = ProgressBarNotification::update_state(paused, delta); + // sets Estate to hidden + if (get_state() == PopNotification::EState::ClosePending || get_state() == PopNotification::EState::Finished) + set_progress_state(SlicingProgressState::SP_NO_SLICING); + return ret; +} +void NotificationManager::SlicingProgressNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + if (m_sp_state == SlicingProgressState::SP_PROGRESS || (m_sp_state == SlicingProgressState::SP_COMPLETED && !m_sidebar_collapsed)) { + ProgressBarNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + /* // enable for hypertext during slicing (correct call of export_enabled needed) + if (m_multiline) { + // two lines text, one line bar + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height / 4); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height + m_line_height / 4); + std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); + imgui.text(line.c_str()); + if (m_sidebar_collapsed && m_sp_state == SlicingProgressState::SP_PROGRESS && m_export_possible) { + ImVec2 text_size = ImGui::CalcTextSize(line.c_str()); + render_hypertext(imgui, m_left_indentation + text_size.x + 4, m_line_height + m_line_height / 4, m_hypertext); + } + if (m_has_cancel_button) + render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } + else { + //one line text, one line bar + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height / 4); + std::string line = m_text1.substr(0, m_endlines[0]); + imgui.text(line.c_str()); + if (m_sidebar_collapsed && m_sp_state == SlicingProgressState::SP_PROGRESS && m_export_possible) { + ImVec2 text_size = ImGui::CalcTextSize(line.c_str()); + render_hypertext(imgui, m_left_indentation + text_size.x + 4, m_line_height / 4, m_hypertext); + } + if (m_has_cancel_button) + render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } + */ + } else if (m_sp_state == SlicingProgressState::SP_COMPLETED) { + // "Slicing Finished" on line 1 + hypertext, print info on line + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str()); + float x_offset = m_left_indentation; + std::string fulltext = m_text1 + m_hypertext + m_text2; + ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str()); + float cursor_y = win_size.y / 2 - text_size.y / 2; + if (m_sidebar_collapsed && m_has_print_info) { + x_offset = 20; + cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2; + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(m_print_info.c_str()); + cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2; + } + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(cursor_y); + imgui.text(m_text1.c_str()); + if (m_sidebar_collapsed) + render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext); + } else { + PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } +} +void NotificationManager::SlicingProgressNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + if (!(m_sp_state == SlicingProgressState::SP_PROGRESS || (m_sp_state == SlicingProgressState::SP_COMPLETED && !m_sidebar_collapsed))) { + return; + } + //std::string text; + ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + /* + std::stringstream stream; + stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "%"; + text = stream.str(); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); + imgui.text(text.c_str()); + */ +} +void NotificationManager::SlicingProgressNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + + std::string button_text; + button_text = ImGui::CancelButton; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), + ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)), + true)) + { + button_text = ImGui::CancelHoverButton; + } + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + on_cancel_button(); + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + { + on_cancel_button(); + } + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); +} + +void NotificationManager::SlicingProgressNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + // Do not render close button while showing progress - cancel button is rendered instead + if (m_sp_state != SlicingProgressState::SP_PROGRESS) { + ProgressBarNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } +} +//------ProgressIndicatorNotification------- +void NotificationManager::ProgressIndicatorNotification::set_status_text(const char* text) +{ + NotificationData data{ NotificationType::ProgressIndicator, NotificationLevel::ProgressBarNotificationLevel, 0, text }; + update(data); +} + +void NotificationManager::ProgressIndicatorNotification::init() +{ + // skip ProgressBarNotification::init (same code here) + PopNotification::init(); + if (m_endlines.empty()) { + m_endlines.push_back(0); + } + if (m_lines_count >= 2) { + m_lines_count = 3; + m_multiline = true; + while (m_endlines.size() < 3) + m_endlines.push_back(m_endlines.back()); + } + else { + m_lines_count = 2; + m_endlines.push_back(m_endlines.back()); + } + switch (m_progress_state) + { + case Slic3r::GUI::NotificationManager::ProgressIndicatorNotification::ProgressIndicatorState::PIS_HIDDEN: + m_state = EState::Hidden; + break; + case Slic3r::GUI::NotificationManager::ProgressIndicatorNotification::ProgressIndicatorState::PIS_PROGRESS_REQUEST: + case Slic3r::GUI::NotificationManager::ProgressIndicatorNotification::ProgressIndicatorState::PIS_PROGRESS_UPDATED: + m_state = EState::NotFading; + break; + case Slic3r::GUI::NotificationManager::ProgressIndicatorNotification::ProgressIndicatorState::PIS_COMPLETED: + m_state = EState::Shown; + break; + default: + break; + } +} +void NotificationManager::ProgressIndicatorNotification::set_percentage(float percent) +{ + ProgressBarNotification::set_percentage(percent); + if (percent >= 0.0f && percent < 1.0f) { + m_state = EState::NotFading; + m_has_cancel_button = true; + m_progress_state = ProgressIndicatorState::PIS_PROGRESS_REQUEST; + } else if (percent >= 1.0f) { + m_state = EState::Shown; + m_progress_state = ProgressIndicatorState::PIS_COMPLETED; + m_has_cancel_button = false; + } else { + m_progress_state = ProgressIndicatorState::PIS_HIDDEN; + m_state = EState::Hidden; + } +} +bool NotificationManager::ProgressIndicatorNotification::update_state(bool paused, const int64_t delta) +{ + if (m_progress_state == ProgressIndicatorState::PIS_PROGRESS_REQUEST) { + // percentage was changed (and it called schedule_extra_frame), now update must know this needs render + m_next_render = 0; + m_progress_state = ProgressIndicatorState::PIS_PROGRESS_UPDATED; + return true; + } + bool ret = ProgressBarNotification::update_state(paused, delta); + if (get_state() == PopNotification::EState::ClosePending || get_state() == PopNotification::EState::Finished) + // go to PIS_HIDDEN state + set_percentage(-1.0f); + return ret; +} + +void NotificationManager::ProgressIndicatorNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + + std::string button_text; + button_text = ImGui::CancelButton; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), + ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)), + true)) + { + button_text = ImGui::CancelHoverButton; + } + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + on_cancel_button(); + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + { + on_cancel_button(); + } + ImGui::PopStyleColor(5); +} +void NotificationManager::ProgressIndicatorNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + // Do not render close button while showing progress - cancel button is rendered instead + if (m_percentage >= 1.0f) + { + ProgressBarNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } +} //------NotificationManager-------- NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : m_evt_handler(evt_handler) { } + void NotificationManager::push_notification(const NotificationType type, int timestamp) { auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications), @@ -1117,7 +1457,7 @@ void NotificationManager::push_notification(const NotificationType type, int tim } void NotificationManager::push_notification(const std::string& text, int timestamp) { - push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, timestamp); + push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotificationLevel, 10, text }, timestamp); } void NotificationManager::push_notification(NotificationType type, @@ -1129,11 +1469,11 @@ void NotificationManager::push_notification(NotificationType type, { int duration = 0; switch (level) { - case NotificationLevel::RegularNotification: duration = 10; break; - case NotificationLevel::ErrorNotification: break; - case NotificationLevel::WarningNotification: break; - case NotificationLevel::ImportantNotification: break; - case NotificationLevel::ProgressBarNotification: break; + case NotificationLevel::RegularNotificationLevel: duration = 10; break; + case NotificationLevel::ErrorNotificationLevel: break; + case NotificationLevel::WarningNotificationLevel: break; + case NotificationLevel::ImportantNotificationLevel: break; + case NotificationLevel::ProgressBarNotificationLevel: break; default: assert(false); return; @@ -1142,18 +1482,19 @@ void NotificationManager::push_notification(NotificationType type, } void NotificationManager::push_validate_error_notification(const std::string& text) { - push_notification_data({ NotificationType::ValidateError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, 0); + push_notification_data({ NotificationType::ValidateError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("ERROR:") + "\n" + text }, 0); + set_slicing_progress_hidden(); } void NotificationManager::push_slicing_error_notification(const std::string& text) { set_all_slicing_errors_gray(false); - push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, 0); - close_notification_of_type(NotificationType::SlicingComplete); + push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("ERROR:") + "\n" + text }, 0); + set_slicing_progress_hidden(); } void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, ObjectID oid, int warning_step) { - NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }; + NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotificationLevel, 0, _u8L("WARNING:") + "\n" + text }; auto notification = std::make_unique(data, m_id_provider, m_evt_handler); notification->object_id = oid; @@ -1164,7 +1505,7 @@ void NotificationManager::push_slicing_warning_notification(const std::string& t } void NotificationManager::push_plater_error_notification(const std::string& text) { - push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, 0); + push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("ERROR:") + "\n" + text }, 0); } void NotificationManager::close_plater_error_notification(const std::string& text) @@ -1188,7 +1529,7 @@ void NotificationManager::push_plater_warning_notification(const std::string& te } } - NotificationData data{ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }; + NotificationData data{ NotificationType::PlaterWarning, NotificationLevel::WarningNotificationLevel, 0, _u8L("WARNING:") + "\n" + text }; auto notification = std::make_unique(data, m_id_provider, m_evt_handler); push_notification_data(std::move(notification), 0); @@ -1246,42 +1587,13 @@ void NotificationManager::close_slicing_error_notification(const std::string& te } } } -void NotificationManager::push_slicing_complete_notification(int timestamp, bool large) +void NotificationManager::push_object_warning_notification(const std::string& text, ObjectID object_id, const std::string& hypertext/* = ""*/, std::function callback/* = std::function()*/) { - std::string hypertext; - int time = 10; - if (has_slicing_error_notification()) - return; - if (large) { - hypertext = _u8L("Export G-Code."); - time = 0; - } - NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time, _u8L("Slicing finished."), hypertext, - [](wxEvtHandler* evnthndlr){ - if (evnthndlr != nullptr) - wxPostEvent(evnthndlr, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED)); - return true; - } - }; - push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, large), timestamp); -} -void NotificationManager::set_slicing_complete_print_time(const std::string &info) -{ - for (std::unique_ptr ¬ification : m_pop_notifications) { - if (notification->get_type() == NotificationType::SlicingComplete) { - dynamic_cast(notification.get())->set_print_info(info); - break; - } - } -} -void NotificationManager::set_slicing_complete_large(bool large) -{ - for (std::unique_ptr ¬ification : m_pop_notifications) { - if (notification->get_type() == NotificationType::SlicingComplete) { - dynamic_cast(notification.get())->set_large(large); - break; - } - } + NotificationData data{ NotificationType::ObjectWarning, NotificationLevel::WarningNotificationLevel, 0, text, hypertext, callback }; + auto notification = std::make_unique(data, m_id_provider, m_evt_handler); + notification->object_id = object_id; + notification->warning_step = 0; + push_notification_data(std::move(notification), 0); } void NotificationManager::close_notification_of_type(const NotificationType type) { @@ -1300,10 +1612,19 @@ void NotificationManager::remove_slicing_warnings_of_released_objects(const std: notification->close(); } } +void NotificationManager::remove_object_warnings_of_released_objects(const std::vector& living_oids) +{ + for (std::unique_ptr& notification : m_pop_notifications) + if (notification->get_type() == NotificationType::ObjectWarning) { + if (!std::binary_search(living_oids.begin(), living_oids.end(), + static_cast(notification.get())->object_id)) + notification->close(); + } +} void NotificationManager::push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable) { close_notification_of_type(NotificationType::ExportFinished); - NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotification, on_removable ? 0 : 20, _u8L("Exporting finished.") + "\n" + path }; + NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotificationLevel, on_removable ? 0 : 20, _u8L("Exporting finished.") + "\n" + path }; push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), 0); } @@ -1317,7 +1638,7 @@ void NotificationManager::push_upload_job_notification(int id, float filesize, } } std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); - NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotification, 10, text }; + NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotificationLevel, 10, text }; push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0, id, filesize), 0); } void NotificationManager::set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage) @@ -1359,6 +1680,153 @@ void NotificationManager::upload_job_notification_show_error(int id, const std:: } } } + +void NotificationManager::init_slicing_progress_notification(std::function cancel_callback) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + dynamic_cast(notification.get())->set_cancel_callback(cancel_callback); + return; + } + } + NotificationData data{ NotificationType::SlicingProgress, NotificationLevel::ProgressBarNotificationLevel, 0, std::string(),std::string(), + [](wxEvtHandler* evnthndlr) { + if (evnthndlr != nullptr) + wxPostEvent(evnthndlr, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED)); + return true; + } + }; + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, cancel_callback), 0); +} +void NotificationManager::set_slicing_progress_percentage(const std::string& text, float percentage) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + SlicingProgressNotification* spn = dynamic_cast(notification.get()); + spn->set_progress_state(percentage); + spn->set_status_text(text); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + return; + } + } + // Slicing progress notification was not found - init it thru plater so correct cancel callback function is appended + wxGetApp().plater()->init_notification_manager(); +} + +void NotificationManager::set_slicing_progress_hidden() +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + SlicingProgressNotification* notif = dynamic_cast(notification.get()); + notif->set_progress_state(SlicingProgressNotification::SlicingProgressState::SP_NO_SLICING); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + return; + } + } + // Slicing progress notification was not found - init it thru plater so correct cancel callback function is appended + wxGetApp().plater()->init_notification_manager(); +} +void NotificationManager::set_slicing_complete_print_time(const std::string& info, bool sidebar_colapsed) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + dynamic_cast(notification.get())->set_sidebar_collapsed(sidebar_colapsed); + dynamic_cast(notification.get())->set_print_info(info); + break; + } + } +} +void NotificationManager::set_sidebar_collapsed(bool collapsed) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + dynamic_cast(notification.get())->set_sidebar_collapsed(collapsed); + break; + } + } +} +void NotificationManager::set_fff(bool fff) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + dynamic_cast(notification.get())->set_fff(fff); + break; + } + } +} +void NotificationManager::set_slicing_progress_export_possible() +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingProgress) { + dynamic_cast(notification.get())->set_export_possible(true); + break; + } + } +} +void NotificationManager::init_progress_indicator() +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressIndicator) { + return; + } + } + NotificationData data{ NotificationType::ProgressIndicator, NotificationLevel::ProgressBarNotificationLevel, 2}; + auto notification = std::make_unique(data, m_id_provider, m_evt_handler); + push_notification_data(std::move(notification), 0); +} + +void NotificationManager::progress_indicator_set_range(int range) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressIndicator) { + dynamic_cast(notification.get())->set_range(range); + return; + } + } + init_progress_indicator(); +} +void NotificationManager::progress_indicator_set_cancel_callback(CancelFn callback/* = CancelFn()*/) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressIndicator) { + dynamic_cast(notification.get())->set_cancel_callback(callback); + return; + } + } + init_progress_indicator(); +} +void NotificationManager::progress_indicator_set_progress(int pr) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressIndicator) { + dynamic_cast(notification.get())->set_progress(pr); + // Ask for rendering - needs to be done on every progress. Calls to here doesnt trigger IDLE event or rendering. + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(100); + return; + } + } + init_progress_indicator(); +} +void NotificationManager::progress_indicator_set_status_text(const char* text) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressIndicator) { + dynamic_cast(notification.get())->set_status_text(text); + return; + } + } + init_progress_indicator(); +} +int NotificationManager::progress_indicator_get_range() const +{ + for (const std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::ProgressIndicator) { + return dynamic_cast(notification.get())->get_range(); + } + } + return 0; +} + void NotificationManager::push_hint_notification(bool open_next) { for (std::unique_ptr& notification : m_pop_notifications) { @@ -1368,8 +1836,19 @@ void NotificationManager::push_hint_notification(bool open_next) } } - NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::RegularNotification, 300, "" }; - push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, open_next), 0); + NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::HintNotificationLevel, 300, "" }; + // from user - open now + if (!open_next) { + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, open_next), 0); + stop_delayed_notifications_of_type(NotificationType::DidYouKnowHint); + // at startup - delay for half a second to let other notification pop up, than try every 30 seconds + // show only if no notifications are shown + } else { + auto condition = [&self = std::as_const(*this)]() { + return self.get_notification_count() == 0; + }; + push_delayed_notification(std::make_unique(data, m_id_provider, m_evt_handler, open_next), condition, 500, 30000); + } } bool NotificationManager::is_hint_notification_open() @@ -1380,7 +1859,10 @@ bool NotificationManager::is_hint_notification_open() } return false; } - +void NotificationManager::deactivate_loaded_hints() +{ + HintDatabase::get_instance().uninit(); +} void NotificationManager::push_updated_item_info_notification(InfoItemType type) { for (std::unique_ptr& notification : m_pop_notifications) { @@ -1390,7 +1872,7 @@ void NotificationManager::push_updated_item_info_notification(InfoItemType type) } } - NotificationData data{ NotificationType::UpdatedItemsInfo, NotificationLevel::RegularNotification, 5, "" }; + NotificationData data{ NotificationType::UpdatedItemsInfo, NotificationLevel::RegularNotificationLevel, 5, "" }; auto notification = std::make_unique(data, m_id_provider, m_evt_handler, type); if (push_notification_data(std::move(notification), 0)) { (dynamic_cast(m_pop_notifications.back().get()))->add_type(type); @@ -1412,16 +1894,41 @@ bool NotificationManager::push_notification_data(std::unique_ptrget_current_canvas3D(); - + bool retval = false; if (this->activate_existing(notification.get())) { - m_pop_notifications.back()->update(notification->get_data()); - canvas.schedule_extra_frame(0); - return false; + if (m_initialized) { // ignore update action - it cant be initialized if canvas and imgui context is not ready + m_pop_notifications.back()->update(notification->get_data()); + } } else { m_pop_notifications.emplace_back(std::move(notification)); - canvas.schedule_extra_frame(0); - return true; + retval = true; + } + if (!m_initialized) + return retval; + GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D(); + canvas.schedule_extra_frame(0); + return retval; +} + +void NotificationManager::push_delayed_notification(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval) +{ + if (initial_delay == 0 && condition_callback()) { + if( push_notification_data(std::move(notification), 0)) + return; + } + m_waiting_notifications.emplace_back(std::move(notification), condition_callback, initial_delay == 0 ? delay_interval : initial_delay, delay_interval); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(initial_delay == 0 ? delay_interval : initial_delay); +} + +void NotificationManager::stop_delayed_notifications_of_type(const NotificationType type) +{ + for (auto it = m_waiting_notifications.begin(); it != m_waiting_notifications.end();) { + if ((*it).notification->get_type() == type) { + it = m_waiting_notifications.erase(it); + } + else { + ++it; + } } } @@ -1477,6 +1984,26 @@ bool NotificationManager::update_notifications(GLCanvas3D& canvas) ++it; } + // delayed notifications + for (auto it = m_waiting_notifications.begin(); it != m_waiting_notifications.end();) { + // substract time + if ((*it).remaining_time > 0) + (*it).remaining_time -= time_since_render; + if ((*it).remaining_time <= 0) { + if ((*it).condition_callback()) { // push notification, erase it from waiting list (frame is scheduled by push) + (*it).notification->reset_timer(); + if (push_notification_data(std::move((*it).notification), 0)) { + it = m_waiting_notifications.erase(it); + continue; + } + } + // not possible to push, delay for delay_interval + (*it).remaining_time = (*it).delay_interval; + } + next_render = std::min(next_render, (*it).remaining_time); + ++it; + } + // request next frame in future if (next_render < max) canvas.schedule_extra_frame(int(next_render)); @@ -1534,6 +2061,8 @@ void NotificationManager::set_in_preview(bool preview) notification->hide(preview); if (notification->get_type() == NotificationType::SignDetected) notification->hide(!preview); + if (m_in_preview && notification->get_type() == NotificationType::DidYouKnowHint) + notification->close(); } } @@ -1569,6 +2098,15 @@ void NotificationManager::device_ejected() notification->close(); } } +size_t NotificationManager::get_notification_count() const +{ + size_t ret = 0; + for (const std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_state() != PopNotification::EState::Hidden) + ret++; + } + return ret; +} }//namespace GUI }//namespace Slic3r diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 038290f823..76dc9a6887 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -6,6 +6,7 @@ #include "GLCanvas3D.hpp" #include "Event.hpp" #include "I18N.hpp" +#include "Jobs/ProgressIndicator.hpp" #include #include @@ -27,6 +28,8 @@ wxDECLARE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationCli using PresetUpdateAvailableClickedEvent = SimpleEvent; wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent); +using CancelFn = std::function; + class GLCanvas3D; class ImGuiWrapper; enum class InfoItemType; @@ -34,9 +37,6 @@ enum class InfoItemType; enum class NotificationType { CustomNotification, - // Notification on end of slicing and G-code processing (the full G-code preview is available). - // Contains a hyperlink to export the G-code to a removable media. - SlicingComplete, // SlicingNotPossible, // Notification on end of export to a removable media, with hyperling to eject the external media. // Obsolete by ExportFinished @@ -71,10 +71,17 @@ enum class NotificationType PlaterError, // Object fully outside the print volume, or extrusion outside the print volume. Slicing is not disabled. PlaterWarning, + // Warning connected to single object id, appears at loading object, disapears at deletition. + // Example: advice to simplify object with big amount of triangles. + ObjectWarning, // Progress bar instead of text. ProgressBar, // Progress bar with info from Print Host Upload Queue dialog. PrintHostUpload, + // Progress bar with cancel button, cannot be closed + // On end of slicing and G-code processing (the full G-code preview is available), + // contains a hyperlink to export the G-code to a removable media or hdd. + SlicingProgress, // Notification, when Color Change G-code is empty and user try to add color change on DoubleSlider. EmptyColorChangeCode, // Notification that custom supports/seams were deleted after mesh repair. @@ -97,8 +104,8 @@ enum class NotificationType // Shows when ObjectList::update_info_items finds information that should be stressed to the user // Might contain logo taken from gizmos UpdatedItemsInfo, - // Give user advice to simplify object with big amount of triangles - SimplifySuggestion + // Progress bar notification with methods to replace ProgressIndicator class. + ProgressIndicator }; class NotificationManager @@ -108,26 +115,31 @@ public: { // The notifications will be presented in the order of importance, thus these enum values // are sorted by the importance. - // "Good to know" notification, usually but not always with a quick fade-out. - RegularNotification = 1, + // Important notification with progress bar, no fade-out, might appear again after closing. Position at the bottom. + ProgressBarNotificationLevel = 1, + // "Did you know" notification with special icon and buttons, Position close to bottom. + HintNotificationLevel, + // "Good to know" notification, usually but not always with a quick fade-out. + RegularNotificationLevel, // Information notification without a fade-out or with a longer fade-out. - ImportantNotification, - // Important notification with progress bar, no fade-out, might appear again after closing. - ProgressBarNotification, + ImportantNotificationLevel, // Warning, no fade-out. - WarningNotification, - // Error, no fade-out. - ErrorNotification, + WarningNotificationLevel, + // Error, no fade-out. Top most position. + ErrorNotificationLevel, }; NotificationManager(wxEvtHandler* evt_handler); + ~NotificationManager(){} + // init is called after canvas3d is created. Notifications added before init are not showed or updated + void init() { m_initialized = true; } // Push a prefabricated notification from basic_notifications (see the table at the end of this file). void push_notification(const NotificationType type, int timestamp = 0); - // Push a NotificationType::CustomNotification with NotificationLevel::RegularNotification and 10s fade out interval. + // Push a NotificationType::CustomNotification with NotificationLevel::RegularNotificationLevel and 10s fade out interval. void push_notification(const std::string& text, int timestamp = 0); - // Push a NotificationType::CustomNotification with provided notification level and 10s for RegularNotification. - // ErrorNotification and ImportantNotification are never faded out. + // Push a NotificationType::CustomNotification with provided notification level and 10s for RegularNotificationLevel. + // ErrorNotificationLevel and ImportantNotificationLevel are never faded out. void push_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext = "", std::function callback = std::function(), int timestamp = 0); // Creates Validate Error notification with a custom text and no fade out. @@ -154,25 +166,50 @@ public: // Closes error or warning of the same text void close_plater_error_notification(const std::string& text); void close_plater_warning_notification(const std::string& text); - // Creates special notification slicing complete. - // If large = true (Plater side bar is closed), then printing time and export button is shown - // at the notification and fade-out is disabled. Otherwise the fade out time is set to 10s. - void push_slicing_complete_notification(int timestamp, bool large); - // Add a print time estimate to an existing SlicingComplete notification. - void set_slicing_complete_print_time(const std::string &info); + // Object warning with ObjectID, closes when object is deleted. ID used is of object not print like in slicing warning. + void push_object_warning_notification(const std::string& text, ObjectID object_id, const std::string& hypertext = "", + std::function callback = std::function()); + // Close object warnings, whose ObjectID is not in the list. + // living_oids is expected to be sorted. + void remove_object_warnings_of_released_objects(const std::vector& living_oids); // Called when the side bar changes its visibility, as the "slicing complete" notification supplements // the "slicing info" normally shown at the side bar. - void set_slicing_complete_large(bool large); + void set_sidebar_collapsed(bool collapsed); + // Set technology for correct text in SlicingProgress. + void set_fff(bool b); + void set_fdm(bool b) { set_fff(b); } + void set_sla(bool b) { set_fff(!b); } // Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable); - // notification with progress bar + // notifications with progress bar + // print host upload void push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage); void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host); void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host); + // slicing progress + void init_slicing_progress_notification(std::function cancel_callback); + // percentage negative = canceled, <0-1) = progress, 1 = completed + void set_slicing_progress_percentage(const std::string& text, float percentage); + // hides slicing progress notification imidietly + void set_slicing_progress_hidden(); + // Add a print time estimate to an existing SlicingProgress notification. Set said notification to SP_COMPLETED state. + void set_slicing_complete_print_time(const std::string& info, bool sidebar_colapsed); + void set_slicing_progress_export_possible(); + // ProgressIndicator notification + // init adds hidden instance of progress indi notif that should always live (goes to hidden instead of erasing) + void init_progress_indicator(); + // functions equal to ProgressIndicator class + void progress_indicator_set_range(int range); + void progress_indicator_set_cancel_callback(CancelFn callback = CancelFn()); + void progress_indicator_set_progress(int pr); + void progress_indicator_set_status_text(const char*); // utf8 char array + int progress_indicator_get_range() const; // Hint (did you know) notification void push_hint_notification(bool open_next); bool is_hint_notification_open(); + // Forces Hints to reload its content when next hint should be showed + void deactivate_loaded_hints(); void push_updated_item_info_notification(InfoItemType type); // Close old notification ExportFinished. void new_export_began(bool on_removable); @@ -190,6 +227,8 @@ public: void set_move_from_overlay(bool move) { m_move_from_overlay = move; } // perform update_state on each notification and ask for more frames if needed, return true for render needed bool update_notifications(GLCanvas3D& canvas); + // returns number of all notifications shown + size_t get_notification_count() const; private: // duration 0 means not disapearing struct NotificationData { @@ -257,11 +296,13 @@ private: virtual bool compare_text(const std::string& text) const; void hide(bool h) { if (is_finished()) return; m_state = h ? EState::Hidden : EState::Unknown; } // sets m_next_render with time of next mandatory rendering. Delta is time since last render. - bool update_state(bool paused, const int64_t delta); + virtual bool update_state(bool paused, const int64_t delta); int64_t next_render() const { return is_finished() ? 0 : m_next_render; } EState get_state() const { return m_state; } bool is_hovered() const { return m_state == EState::Hovered; } void set_hovered() { if (m_state != EState::Finished && m_state != EState::ClosePending && m_state != EState::Hidden && m_state != EState::Unknown) m_state = EState::Hovered; } + // set start of notification to now. Used by delayed notifications + void reset_timer() { m_notification_start = GLCanvas3D::timestamp_now(); m_state = EState::Shown; } protected: // Call after every size change virtual void init(); @@ -291,6 +332,8 @@ private: virtual void count_lines(); // returns true if PopStyleColor should be called later to pop this push virtual bool push_background_color(); + // used this function instead of reading directly m_data.duration. Some notifications might need to return changing value. + virtual int get_duration() { return m_data.duration; } const NotificationData m_data; // For reusing ImGUI windows. @@ -347,29 +390,7 @@ private: wxEvtHandler* m_evt_handler; }; - class SlicingCompleteLargeNotification : public PopNotification - { - public: - SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool largeds); - void set_large(bool l); - bool get_large() { return m_is_large; } - void set_print_info(const std::string &info); - void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) override - { - // This notification is always hidden if !large (means side bar is collapsed) - if (!get_large() && !is_finished()) - m_state = EState::Hidden; - PopNotification::render(canvas, initial_y, move_from_overlay, overlay_width); - } - protected: - void render_text(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y) - override; - bool m_is_large; - bool m_has_print_info { false }; - std::string m_print_info; - }; + class SlicingWarningNotification : public PopNotification { @@ -393,7 +414,7 @@ private: { public: - ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { } + ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) { } virtual void set_percentage(float percent) { m_percentage = percent; } protected: virtual void init() override; @@ -411,9 +432,10 @@ private: {} void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override {} - float m_percentage; + float m_percentage {0.0f}; bool m_has_cancel_button {false}; + bool m_render_percentage {false}; // local time of last hover for showing tooltip }; @@ -431,7 +453,7 @@ private: PB_COMPLETED }; PrintHostUploadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage, int job_id, float filesize) - :ProgressBarNotification(n, id_provider, evt_handler, percentage) + :ProgressBarNotification(n, id_provider, evt_handler) , m_job_id(job_id) , m_file_size(filesize) { @@ -460,7 +482,117 @@ private: // Size of uploaded size to be displayed in MB float m_file_size; long m_hover_time{ 0 }; - UploadJobState m_uj_state{ UploadJobState::PB_PROGRESS }; + UploadJobState m_uj_state{ UploadJobState::PB_PROGRESS }; + }; + + class SlicingProgressNotification : public ProgressBarNotification + { + public: + // Inner state of notification, Each state changes bahaviour of the notification + enum class SlicingProgressState + { + SP_NO_SLICING, // hidden + SP_PROGRESS, // never fades outs, no close button, has cancel button + SP_CANCELLED, // fades after 10 seconds, simple message + SP_COMPLETED // Has export hyperlink and print info, fades after 20 sec if sidebar is shown, otherwise no fade out + }; + SlicingProgressNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, std::function callback) + : ProgressBarNotification(n, id_provider, evt_handler) + , m_cancel_callback(callback) + { + set_progress_state(SlicingProgressState::SP_NO_SLICING); + m_has_cancel_button = false; + m_render_percentage = true; + } + // sets text of notification - call after setting progress state + void set_status_text(const std::string& text); + // sets cancel button callback + void set_cancel_callback(std::function callback) { m_cancel_callback = callback; } + bool has_cancel_callback() const { return m_cancel_callback != nullptr; } + // sets SlicingProgressState, negative percent means canceled + void set_progress_state(float percent); + // sets SlicingProgressState, percent is used only at progress state. + void set_progress_state(SlicingProgressState state,float percent = 0.f); + // sets additional string of print info and puts notification into Completed state. + void set_print_info(const std::string& info); + // sets fading if in Completed state. + void set_sidebar_collapsed(bool collapsed); + // Calls inherited update_state and ensures Estate goes to hidden not closing. + bool update_state(bool paused, const int64_t delta) override; + // Switch between technology to provide correct text. + void set_fff(bool b) { m_is_fff = b; } + void set_fdm(bool b) { m_is_fff = b; } + void set_sla(bool b) { m_is_fff = !b; } + void set_export_possible(bool b) { m_export_possible = b; } + protected: + void init() override; + void count_lines() override + { + if (m_sp_state == SlicingProgressState::SP_PROGRESS) + ProgressBarNotification::count_lines(); + else + PopNotification::count_lines(); + } + void render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; + void render_bar(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void render_cancel_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void render_close_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void on_cancel_button(); + int get_duration() override; + std::function m_cancel_callback; + SlicingProgressState m_sp_state { SlicingProgressState::SP_PROGRESS }; + bool m_has_print_info { false }; + std::string m_print_info; + bool m_sidebar_collapsed { false }; + bool m_is_fff { true }; + // if true, it is possible show export hyperlink in state SP_PROGRESS + bool m_export_possible { false }; + }; + + class ProgressIndicatorNotification : public ProgressBarNotification + { + public: + enum class ProgressIndicatorState + { + PIS_HIDDEN, // hidden + PIS_PROGRESS_REQUEST, // progress was updated, request render on next update_state() call + PIS_PROGRESS_UPDATED, // render was requested + PIS_COMPLETED // both completed and canceled state. fades out into PIS_NO_SLICING + }; + ProgressIndicatorNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) + : ProgressBarNotification(n, id_provider, evt_handler) + { + m_render_percentage = true; + } + // ProgressIndicator + void set_range(int range) { m_range = range; } + void set_cancel_callback(CancelFn callback) { m_cancel_callback = callback; } + void set_progress(int pr) { set_percentage((float)pr / (float)m_range); } + void set_status_text(const char*); // utf8 char array + int get_range() const { return m_range; } + // ProgressBarNotification + void init() override; + void set_percentage(float percent) override; + bool update_state(bool paused, const int64_t delta) override; + // Own + protected: + int m_range { 100 }; + CancelFn m_cancel_callback { nullptr }; + ProgressIndicatorState m_progress_state { ProgressIndicatorState::PIS_HIDDEN }; + + void render_close_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void render_cancel_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void on_cancel_button() { if (m_cancel_callback) m_cancel_callback(); } }; class ExportFinishedNotification : public PopNotification @@ -487,7 +619,7 @@ private: void render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; - void render_eject_button(ImGuiWrapper& imgui, + void render_eject_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override @@ -515,10 +647,34 @@ private: // in HintNotification.hpp class HintNotification; + // Data of waiting notifications + struct DelayedNotification + { + std::unique_ptr notification; + std::function condition_callback; + int64_t remaining_time; + int64_t delay_interval; + + DelayedNotification(std::unique_ptr n, std::function cb, int64_t r, int64_t d) + : notification(std::move(n)) + , condition_callback(cb) + , remaining_time(r) + , delay_interval(d) + {} + }; + //pushes notification into the queue of notifications that are rendered //can be used to create custom notification bool push_notification_data(const NotificationData& notification_data, int timestamp); bool push_notification_data(std::unique_ptr notification, int timestamp); + // Delayed notifications goes first to the m_waiting_notifications vector and only after remaining time is <= 0 + // and condition callback is success, notification is regular pushed from update function. + // Otherwise another delay interval waiting. Timestamp is 0. + // Note that notification object is constructed when being added to the waiting list, but there are no updates called on it and its timer is reset at regular push. + // Also note that no control of same notification is done during push_delayed_notification but if waiting notif fails to push, it continues waiting. + void push_delayed_notification(std::unique_ptr notification, std::function condition_callback, int64_t initial_delay, int64_t delay_interval); + // Removes all notifications of type from m_waiting_notifications + void stop_delayed_notifications_of_type(const NotificationType type); //finds older notification of same type and moves it to the end of queue. returns true if found bool activate_existing(const NotificationManager::PopNotification* notification); // Put the more important notifications to the bottom of the list. @@ -526,11 +682,15 @@ private: // If there is some error notification active, then the "Export G-code" notification after the slicing is finished is suppressed. bool has_slicing_error_notification(); + // set by init(), until false notifications are only added not updated and frame is not requested after push + bool m_initialized{ false }; // Target for wxWidgets events sent by clicking on the hyperlink available at some notifications. wxEvtHandler* m_evt_handler; // Cache of IDs to identify and reuse ImGUI windows. NotificationIDProvider m_id_provider; std::deque> m_pop_notifications; + // delayed waiting notifications, first is remaining time + std::vector m_waiting_notifications; //timestamps used for slicing finished - notification could be gone so it needs to be stored here std::unordered_set m_used_timestamps; // True if G-code preview is active. False if the Plater is active. @@ -545,7 +705,7 @@ private: NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload, - NotificationType::SimplifySuggestion + NotificationType::ObjectWarning }; //prepared (basic) notifications static const NotificationData basic_notifications[]; diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 9417364ef1..928249e5b0 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -406,8 +406,10 @@ wxDataViewItem ObjectDataViewModel::AddInfoChild(const wxDataViewItem &parent_it // The new item should be added according to its order in InfoItemType. // Find last info item with lower index and append after it. const auto& children = root->GetChildren(); - int idx = -1; - for (int i=0; i 0 && children[0]->GetType() == itSettings; + int idx = is_settings_item ? 0 : -1; + for (size_t i = is_settings_item ? 1 : 0; i < children.size(); ++i) { if (children[i]->GetType() == itInfo && int(children[i]->GetInfoItemType()) < int(info_type) ) idx = i; } @@ -619,6 +621,15 @@ wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_ return layer_item; } +size_t ObjectDataViewModel::GetItemIndexForFirstVolume(ObjectDataViewModelNode* node_parent) +{ + assert(node_parent->m_volumes_cnt > 0); + for (size_t vol_idx = 0; vol_idx < node_parent->GetChildCount(); vol_idx++) + if (node_parent->GetNthChild(vol_idx)->GetType() == itVolume) + return vol_idx; + return -1; +} + wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) { auto ret_item = wxDataViewItem(0); @@ -714,44 +725,34 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) } // if there is last volume item after deleting, delete this last volume too - if (node_parent->GetChildCount() <= 3) // 3??? #ys_FIXME + if (node_parent->m_volumes_cnt == 1) { - int vol_cnt = 0; - int vol_idx = 0; - for (size_t i = 0; i < node_parent->GetChildCount(); ++i) { - if (node_parent->GetNthChild(i)->GetType() == itVolume) { - vol_idx = i; - vol_cnt++; - } - if (vol_cnt > 1) - break; - } + // delete selected (penult) volume + delete node; + ItemDeleted(parent, item); - if (vol_cnt == 1) { - delete node; - ItemDeleted(parent, item); + // get index of the last VolumeItem in CildrenList + size_t vol_idx = GetItemIndexForFirstVolume(node_parent); - ObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx); - DeleteSettings(wxDataViewItem(last_child_node)); - node_parent->GetChildren().Remove(last_child_node); - node_parent->m_volumes_cnt = 0; - delete last_child_node; + // delete this last volume + ObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx); + DeleteSettings(wxDataViewItem(last_child_node)); + node_parent->GetChildren().Remove(last_child_node); + node_parent->m_volumes_cnt = 0; + delete last_child_node; #ifndef __WXGTK__ - if (node_parent->GetChildCount() == 0) - node_parent->m_container = false; + if (node_parent->GetChildCount() == 0) + node_parent->m_container = false; #endif //__WXGTK__ - ItemDeleted(parent, wxDataViewItem(last_child_node)); + ItemDeleted(parent, wxDataViewItem(last_child_node)); - wxCommandEvent event(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED); - auto it = find(m_objects.begin(), m_objects.end(), node_parent); - event.SetInt(it == m_objects.end() ? -1 : it - m_objects.begin()); - wxPostEvent(m_ctrl, event); + wxCommandEvent event(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED); + auto it = find(m_objects.begin(), m_objects.end(), node_parent); + event.SetInt(it == m_objects.end() ? -1 : it - m_objects.begin()); + wxPostEvent(m_ctrl, event); - ret_item = parent; - - return ret_item; - } + return parent; } } else @@ -1361,7 +1362,7 @@ wxDataViewItem ObjectDataViewModel::ReorganizeChildren( const int current_volume if (!node_parent) // happens if item.IsOk()==false return ret_item; - const size_t shift = node_parent->GetChildren().Item(0)->m_type == itSettings ? 1 : 0; + size_t shift = GetItemIndexForFirstVolume(node_parent); ObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id+shift); node_parent->GetChildren().Remove(deleted_node); diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 86e64a8540..f65f829f4b 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -279,6 +279,7 @@ public: const t_layer_height_range& layer_range, const int extruder = 0, const int index = -1); + size_t GetItemIndexForFirstVolume(ObjectDataViewModelNode* node_parent); wxDataViewItem Delete(const wxDataViewItem &item); wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); void DeleteAll(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d315e7734d..7d344c389b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1208,6 +1208,8 @@ void Sidebar::update_sliced_info_sizer() wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : get_time_dhms(float(ps.estimated_print_time)); p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _L("Estimated printing time") + ":"); + p->plater->get_notification_manager()->set_slicing_complete_print_time(_utf8("Estimated printing time: ") + boost::nowide::narrow(t_est), p->plater->is_sidebar_collapsed()); + // Hide non-SLA sliced info parameters p->sliced_info->SetTextAndShow(siFilament_m, "N/A"); p->sliced_info->SetTextAndShow(siFilament_mm3, "N/A"); @@ -1296,10 +1298,7 @@ void Sidebar::update_sliced_info_sizer() new_label += format_wxstr("\n - %1%", _L("normal mode")); info_text += format_wxstr("\n%1%", short_time(ps.estimated_normal_print_time)); - // uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate - //if (p->plater->is_sidebar_collapsed()) - p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed()); - p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time); + p->plater->get_notification_manager()->set_slicing_complete_print_time(_utf8("Estimated printing time: ") + ps.estimated_normal_print_time, p->plater->is_sidebar_collapsed()); } if (ps.estimated_silent_print_time != "N/A") { @@ -1502,7 +1501,7 @@ struct Plater::priv GLToolbar view_toolbar; GLToolbar collapse_toolbar; Preview *preview; - NotificationManager* notification_manager { nullptr }; + std::shared_ptr notification_manager; ProjectDirtyStateManager dirty_state; @@ -1523,10 +1522,10 @@ struct Plater::priv public: Jobs(priv *_m) : m(_m) { - m_arrange_id = add_job(std::make_unique(m->statusbar(), m->q)); - m_fill_bed_id = add_job(std::make_unique(m->statusbar(), m->q)); - m_rotoptimize_id = add_job(std::make_unique(m->statusbar(), m->q)); - m_sla_import_id = add_job(std::make_unique(m->statusbar(), m->q)); + m_arrange_id = add_job(std::make_unique(m->notification_manager, m->q)); + m_fill_bed_id = add_job(std::make_unique(m->notification_manager, m->q)); + m_rotoptimize_id = add_job(std::make_unique(m->notification_manager, m->q)); + m_sla_import_id = add_job(std::make_unique(m->notification_manager, m->q)); } void arrange() @@ -1637,7 +1636,7 @@ struct Plater::priv void apply_free_camera_correction(bool apply = true); void update_ui_from_settings(); void update_main_toolbar_tooltips(); - std::shared_ptr statusbar(); +// std::shared_ptr statusbar(); std::string get_config(const std::string &key) const; BoundingBoxf bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const; @@ -1736,6 +1735,7 @@ struct Plater::priv void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid); // Update notification manager with the current state of warnings produced by the background process (slicing). void actualize_slicing_warnings(const PrintBase &print); + void actualize_object_warnings(const PrintBase& print); // Displays dialog window with list of warnings. // Returns true if user clicks OK. // Returns true if current_warnings vector is empty without showning the dialog @@ -1787,6 +1787,8 @@ struct Plater::priv // extension should contain the leading dot, i.e.: ".3mf" wxString get_project_filename(const wxString& extension = wxEmptyString) const; void set_project_filename(const wxString& filename); + // Call after plater and Canvas#D is initialized + void init_notification_manager(); // Caching last value of show_action_buttons parameter for show_action_buttons(), so that a callback which does not know this state will not override it. mutable bool ready_to_slice = { false }; @@ -1796,6 +1798,7 @@ struct Plater::priv std::string last_output_dir_path; bool inside_snapshot_capture() { return m_prevent_snapshots != 0; } bool process_completed_with_error { false }; + private: bool layers_height_allowed() const; @@ -1834,7 +1837,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) , main_frame(main_frame) , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ "bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", - "brim_width", "brim_offset", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material", + "brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. @@ -1844,6 +1847,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "support_material_contact_distance", "support_material_bottom_contact_distance", "raft_layers" })) , sidebar(new Sidebar(q)) + , notification_manager(std::make_shared(q)) , m_ui_jobs(this) , delayed_scene_refresh(false) , view_toolbar(GLToolbar::Radio, "View") @@ -2011,7 +2015,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) }); #endif /* _WIN32 */ - notification_manager = new NotificationManager(this->q); + //notification_manager = new NotificationManager(this->q); + if (wxGetApp().is_editor()) { this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); @@ -2021,12 +2026,12 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->show_action_buttons(this->ready_to_slice); notification_manager->close_notification_of_type(NotificationType::ExportFinished); notification_manager->push_notification(NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotification, + NotificationManager::NotificationLevel::RegularNotificationLevel, format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."), evt.data.first.name, evt.data.first.path) ); } else { notification_manager->push_notification(NotificationType::CustomNotification, - NotificationManager::NotificationLevel::ErrorNotification, + NotificationManager::NotificationLevel::ErrorNotificationLevel, format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path) ); } @@ -2073,6 +2078,7 @@ Plater::priv::~priv() { if (config != nullptr) delete config; + notification_manager->deactivate_loaded_hints(); } void Plater::priv::update(unsigned int flags) @@ -2147,6 +2153,8 @@ void Plater::priv::collapse_sidebar(bool collapse) new_tooltip += " [Shift+Tab]"; int id = collapse_toolbar.get_item_id("collapse_sidebar"); collapse_toolbar.set_tooltip(id, new_tooltip); + + notification_manager->set_sidebar_collapsed(collapse); } @@ -2174,10 +2182,11 @@ void Plater::priv::update_main_toolbar_tooltips() view3D->get_canvas3d()->update_tooltip_for_settings_item_in_main_toolbar(); } -std::shared_ptr Plater::priv::statusbar() -{ - return main_frame->m_statusbar; -} +//std::shared_ptr Plater::priv::statusbar() +//{ +// return nullptr; +// return main_frame->m_statusbar; +//} std::string Plater::priv::get_config(const std::string &key) const { @@ -2221,7 +2230,14 @@ std::vector Plater::priv::load_files(const std::vector& input_ std::vector obj_idxs; for (size_t i = 0; i < input_files.size(); ++i) { +#ifdef _WIN32 + auto path = input_files[i]; + // On Windows, we swap slashes to back slashes, see GH #6803 as read_from_file() does not understand slashes on Windows thus it assignes full path to names of loaded objects. + path.make_preferred(); +#else // _WIN32 + // Don't make a copy on Posix. Slash is a path separator, back slashes are not accepted as a substitute. const auto &path = input_files[i]; +#endif // _WIN32 const auto filename = path.filename(); dlg.Update(static_cast(100.0f * static_cast(i) / static_cast(input_files.size())), _L("Loading file") + ": " + from_path(filename)); dlg.Fit(); @@ -2315,7 +2331,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ for (std::string& name : names) notif_text += "\n - " + name; notification_manager->push_notification(NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotification, notif_text); + NotificationManager::NotificationLevel::RegularNotificationLevel, notif_text); } } @@ -2336,9 +2352,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ else { model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion)); for (auto obj : model.objects) - if (obj->name.empty() || - obj->name.find_first_of("/") != std::string::npos) // When file is imported from Fusion360 the path containes "/" instead of "\\" (see https://github.com/prusa3d/PrusaSlicer/issues/6803) - // But read_from_file doesn't support that direction separator and as a result object name containes full path + if (obj->name.empty()) obj->name = fs::path(obj->input_file).filename().string(); } } catch (const ConfigurationError &e) { @@ -2457,9 +2471,9 @@ std::vector Plater::priv::load_files(const std::vector& input_ } if (load_model) { - wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().string()); + wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().make_preferred().string()); // XXX: Plater.pm had @loaded_files, but didn't seem to fill them with the filenames... - statusbar()->set_status_text(_L("Loaded")); +// statusbar()->set_status_text(_L("Loaded")); } // automatic selection of added objects @@ -2717,7 +2731,7 @@ void Plater::priv::object_list_changed() { 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() == ModelInstancePVS_Inside; + const bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state() == ModelInstancePVS_Inside; sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits); } @@ -2872,7 +2886,7 @@ void Plater::priv::split_object() // If we splited object which is contain some parts/modifiers then all non-solid parts (modifiers) were deleted if (current_model_object->volumes.size() > 1 && current_model_object->volumes.size() != new_objects.size()) notification_manager->push_notification(NotificationType::CustomNotification, - NotificationManager::NotificationLevel::RegularNotification, + NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("All non-solid parts (modifiers) were deleted")); Plater::TakeSnapshot snapshot(q, _L("Split to Objects")); @@ -2948,7 +2962,7 @@ void Plater::priv::process_validation_warning(const std::string& warning) const notification_manager->push_notification( NotificationType::ValidateWarning, - NotificationManager::NotificationLevel::WarningNotification, + NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("WARNING:") + "\n" + text, hypertext, action_fn ); } @@ -2994,6 +3008,8 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // In SLA mode, we need to reload the 3D scene every time to show the support structures. if (printer_technology == ptSLA || (printer_technology == ptFFF && config->opt_bool("wipe_tower"))) return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE; + + notification_manager->set_slicing_progress_hidden(); } if ((invalidated != Print::APPLY_STATUS_UNCHANGED || force_validation) && ! background_process.empty()) { @@ -3041,6 +3057,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool //actualizate warnings if (invalidated != Print::APPLY_STATUS_UNCHANGED) { actualize_slicing_warnings(*this->background_process.current_print()); + actualize_object_warnings(*this->background_process.current_print()); show_warning_dialog = false; process_completed_with_error = false; } @@ -3065,9 +3082,9 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool 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")); +// 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)); @@ -3104,10 +3121,10 @@ bool Plater::priv::restart_background_process(unsigned int state) (state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) { // The print is valid and it can be started. if (this->background_process.start()) { - this->statusbar()->set_cancel_callback([this]() { - this->statusbar()->set_status_text(_L("Cancelling")); - this->background_process.stop(); - }); +// this->statusbar()->set_cancel_callback([this]() { +// this->statusbar()->set_status_text(_L("Cancelling")); +// this->background_process.stop(); +// }); if (!show_warning_dialog) on_slicing_began(); return true; @@ -3179,6 +3196,9 @@ void Plater::priv::update_sla_scene() void Plater::priv::replace_with_stl() { + if (! q->canvas3D()->get_gizmos_manager().check_gizmos_closed_except(GLGizmosManager::EType::Undefined)) + return; + const Selection& selection = get_selection(); if (selection.is_wipe_tower() || get_selection().get_volume_idxs().size() != 1) @@ -3523,14 +3543,8 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = // Do not fix anything when a gizmo is open. There might be issues with updates // and what is worse, the snapshot time would refer to the internal stack. - if (q->canvas3D()->get_gizmos_manager().get_current_type() != GLGizmosManager::Undefined) { - notification_manager->push_notification( - NotificationType::CustomSupportsAndSeamRemovedAfterRepair, - NotificationManager::NotificationLevel::RegularNotification, - _u8L("ERROR: Please close all manipulators available from " - "the left toolbar before fixing the mesh.")); + if (! q->canvas3D()->get_gizmos_manager().check_gizmos_closed_except(GLGizmosManager::Undefined)) return; - } // size_t snapshot_time = undo_redo_stack().active_snapshot_time(); Plater::TakeSnapshot snapshot(q, _L("Fix through NetFabb")); @@ -3583,10 +3597,7 @@ void Plater::priv::create_simplify_notification(const std::vector& obj_i manager.open_gizmo(GLGizmosManager::EType::Simplify); return true; }; - notification_manager->push_notification( - NotificationType::SimplifySuggestion, - NotificationManager::NotificationLevel::WarningNotification, - text.str(), hypertext, open_simplify); + notification_manager->push_object_warning_notification(text.str(), model.objects[object_id]->id(), hypertext, open_simplify); } } @@ -3660,7 +3671,7 @@ void Plater::priv::set_current_panel(wxPanel* panel) // see: Plater::priv::object_list_changed() // FIXME: it may be better to have a single function making this check and let it be called wherever needed bool export_in_progress = this->background_process.is_export_scheduled(); - bool model_fits = view3D->check_volumes_outside_state() != ModelInstancePVS_Partly_Outside; + bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state() != ModelInstancePVS_Partly_Outside; if (!model.objects.empty() && !export_in_progress && model_fits) this->q->reslice(); // keeps current gcode preview, if any @@ -3750,9 +3761,9 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) return; } - this->statusbar()->set_progress(evt.status.percent); - this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…")); - //notification_manager->set_progress_bar_percentage("Slicing progress", (float)evt.status.percent / 100.0f); +// this->statusbar()->set_progress(evt.status.percent); +// this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…")); + notification_manager->set_slicing_progress_percentage(evt.status.text, (float)evt.status.percent / 100.0f); } if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE | PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) { switch (this->printer_technology) { @@ -3803,7 +3814,6 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) void Plater::priv::on_slicing_completed(wxCommandEvent & evt) { - notification_manager->push_slicing_complete_notification(evt.GetInt(), is_sidebar_collapsed()); switch (this->printer_technology) { case ptFFF: this->update_fff_scene(); @@ -3826,8 +3836,8 @@ void Plater::priv::on_export_began(wxCommandEvent& evt) void Plater::priv::on_slicing_began() { clear_warnings(); - notification_manager->close_notification_of_type(NotificationType::SlicingComplete); notification_manager->close_notification_of_type(NotificationType::SignDetected); + notification_manager->close_notification_of_type(NotificationType::ExportFinished); } void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid) { @@ -3851,6 +3861,16 @@ void Plater::priv::actualize_slicing_warnings(const PrintBase &print) notification_manager->remove_slicing_warnings_of_released_objects(ids); notification_manager->set_all_slicing_warnings_gray(true); } +void Plater::priv::actualize_object_warnings(const PrintBase& print) +{ + std::vector ids; + for (const ModelObject* object : print.model().objects ) + { + ids.push_back(object->id()); + } + std::sort(ids.begin(), ids.end()); + notification_manager->remove_object_warnings_of_released_objects(ids); +} void Plater::priv::clear_warnings() { notification_manager->close_slicing_errors_and_warnings(); @@ -3882,8 +3902,9 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) // At this point of time the thread should be either finished or canceled, // so the following call just confirms, that the produced data were consumed. this->background_process.stop(); - this->statusbar()->reset_cancel_callback(); - this->statusbar()->stop_busy(); +// this->statusbar()->reset_cancel_callback(); +// this->statusbar()->stop_busy(); + notification_manager->set_slicing_progress_export_possible(); // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. this->background_process.reset_export(); @@ -3900,7 +3921,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) show_error(q, message.first, message.second); } else notification_manager->push_slicing_error_notification(message.first); - this->statusbar()->set_status_text(from_u8(message.first)); +// this->statusbar()->set_status_text(from_u8(message.first)); if (evt.invalidate_plater()) { const wxString invalid_str = _L("Invalid data"); @@ -3910,8 +3931,10 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) } has_error = true; } - if (evt.cancelled()) - this->statusbar()->set_status_text(_L("Cancelled")); + if (evt.cancelled()) { +// this->statusbar()->set_status_text(_L("Cancelled")); + this->notification_manager->set_slicing_progress_percentage(_utf8("Slicing Cancelled."), -1); + } this->sidebar->show_sliced_info_sizer(evt.success()); @@ -4115,6 +4138,20 @@ void Plater::priv::set_project_filename(const wxString& filename) wxGetApp().mainframe->add_to_recent_projects(filename); } +void Plater::priv::init_notification_manager() +{ + if (!notification_manager) + return; + notification_manager->init(); + + auto cancel_callback = [this]() { + this->background_process.stop(); + }; + notification_manager->init_slicing_progress_notification(cancel_callback); + notification_manager->set_fff(printer_technology == ptFFF); + notification_manager->init_progress_indicator(); +} + void Plater::priv::set_current_canvas_as_dirty() { if (current_panel == view3D) @@ -4754,7 +4791,7 @@ SLAPrint& Plater::sla_print() { return p->sla_print; } void Plater::new_project() { - if (!p->save_project_if_dirty()) + if (p->save_project_if_dirty() == wxID_CANCEL) return; p->select_view_3D("3D"); @@ -4767,7 +4804,7 @@ void Plater::new_project() void Plater::load_project() { - if (!p->save_project_if_dirty()) + if (p->save_project_if_dirty() == wxID_CANCEL) return; // Ask user for a project file name. @@ -4787,10 +4824,7 @@ void Plater::load_project(const wxString& filename) p->reset(); - std::vector input_paths; - input_paths.push_back(into_path(filename)); - - if (! load_files(input_paths).empty()) { + if (! load_files({ into_path(filename) }).empty()) { // At least one file was loaded. p->set_project_filename(filename); reset_project_dirty_initial_presets(); @@ -4807,7 +4841,7 @@ void Plater::add_model(bool imperial_units/* = false*/) std::vector paths; for (const auto &file : input_files) - paths.push_back(into_path(file)); + paths.emplace_back(into_path(file)); wxString snapshot_label; assert(! paths.empty()); @@ -4840,12 +4874,8 @@ void Plater::extract_config_from_project() wxString input_file; wxGetApp().load_project(this, input_file); - if (input_file.empty()) - return; - - std::vector input_paths; - input_paths.push_back(into_path(input_file)); - load_files(input_paths, false, true); + if (! input_file.empty()) + load_files({ into_path(input_file) }, false, true); } void Plater::load_gcode() @@ -4874,10 +4904,9 @@ void Plater::load_gcode(const wxString& filename) // process gcode GCodeProcessor processor; - processor.enable_producers(true); try { - processor.process_file(filename.ToUTF8().data(), false); + processor.process_file(filename.ToUTF8().data()); } catch (const std::exception& ex) { @@ -5076,15 +5105,11 @@ bool Plater::load_files(const wxArrayString& filenames) } case LoadType::LoadGeometry: { Plater::TakeSnapshot snapshot(this, _L("Import Object")); - std::vector in_paths; - in_paths.emplace_back(*it); - load_files(in_paths, true, false); + load_files({ *it }, true, false); break; } case LoadType::LoadConfig: { - std::vector in_paths; - in_paths.emplace_back(*it); - load_files(in_paths, false, true); + load_files({ *it }, false, true); break; } case LoadType::Unknown : { @@ -5159,8 +5184,11 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo void Plater::remove_selected() { + if (p->get_selection().is_empty()) + return; + Plater::TakeSnapshot snapshot(this, _L("Delete Selected Objects")); - this->p->view3D->delete_selected(); + p->view3D->delete_selected(); } void Plater::increase_instances(size_t num) @@ -5562,7 +5590,7 @@ void Plater::export_stl(bool extended, bool selection_only) } Slic3r::store_stl(path_u8.c_str(), &mesh, true); - p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path)); +// p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path)); } void Plater::export_amf() @@ -5579,17 +5607,25 @@ void Plater::export_amf() bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1"; if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) { // Success - p->statusbar()->set_status_text(format_wxstr(_L("AMF file exported to %s"), path)); +// p->statusbar()->set_status_text(format_wxstr(_L("AMF file exported to %s"), path)); } else { // Failure - p->statusbar()->set_status_text(format_wxstr(_L("Error exporting AMF file %s"), path)); +// p->statusbar()->set_status_text(format_wxstr(_L("Error exporting AMF file %s"), path)); } } bool Plater::export_3mf(const boost::filesystem::path& output_path) { +#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED + if (p->model.objects.empty()) { + MessageDialog dialog(nullptr, _L("The plater is empty.\nConfirm you want to save the project ?"), _L("Save project"), wxYES_NO); + if (dialog.ShowModal() != wxID_YES) + return false; + } +#else if (p->model.objects.empty()) return false; +#endif // ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED wxString path; bool export_config = true; @@ -5613,12 +5649,12 @@ bool Plater::export_3mf(const boost::filesystem::path& output_path) bool ret = Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data); if (ret) { // Success - p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); +// p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); p->set_project_filename(path); } else { // Failure - p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); +// p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); } return ret; } @@ -6139,16 +6175,6 @@ BoundingBoxf Plater::bed_shape_bb() const return p->bed_shape_bb(); } -void Plater::start_mapping_gcode_window() -{ - p->preview->get_canvas3d()->start_mapping_gcode_window(); -} - -void Plater::stop_mapping_gcode_window() -{ - p->preview->get_canvas3d()->stop_mapping_gcode_window(); -} - void Plater::arrange() { p->m_ui_jobs.arrange(); @@ -6202,6 +6228,8 @@ bool Plater::set_printer_technology(PrinterTechnology printer_technology) p->sidebar->get_searcher().set_printer_technology(printer_technology); + p->notification_manager->set_fff(printer_technology == ptFFF); + return ret; } @@ -6214,15 +6242,15 @@ void Plater::clear_before_change_mesh(int obj_idx) bool paint_removed = false; for (ModelVolume* mv : mo->volumes) { paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty(); - mv->supported_facets.clear(); - mv->seam_facets.clear(); - mv->mmu_segmentation_facets.clear(); + mv->supported_facets.reset(); + mv->seam_facets.reset(); + mv->mmu_segmentation_facets.reset(); } if (paint_removed) { // snapshot_time is captured by copy so the lambda knows where to undo/redo to. get_notification_manager()->push_notification( NotificationType::CustomSupportsAndSeamRemovedAfterRepair, - NotificationManager::NotificationLevel::RegularNotification, + NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("Custom supports, seams and multimaterial painting were " "removed after repairing the mesh.")); // _u8L("Undo the repair"), @@ -6235,7 +6263,7 @@ void Plater::clear_before_change_mesh(int obj_idx) // else // notification_manager->push_notification( // NotificationType::CustomSupportsAndSeamRemovedAfterRepair, -// NotificationManager::NotificationLevel::RegularNotification, +// NotificationManager::NotificationLevel::RegularNotificationLevel, // _u8L("Cannot undo to before the mesh repair!")); // return true; // }); @@ -6501,14 +6529,14 @@ Mouse3DController& Plater::get_mouse3d_controller() return p->mouse3d_controller; } -const NotificationManager* Plater::get_notification_manager() const +std::shared_ptr Plater::get_notification_manager() { return p->notification_manager; } -NotificationManager* Plater::get_notification_manager() +void Plater::init_notification_manager() { - return p->notification_manager; + p->init_notification_manager(); } bool Plater::can_delete() const { return p->can_delete(); } @@ -6578,6 +6606,11 @@ bool Plater::is_render_statistic_dialog_visible() const return p->show_render_statistic_dialog; } + +Plater::TakeSnapshot::TakeSnapshot(Plater *plater, const std::string &snapshot_name) +: TakeSnapshot(plater, from_u8(snapshot_name)) {} + + // Wrapper around wxWindow::PopupMenu to suppress error messages popping out while tracking the popup menu. bool Plater::PopupMenu(wxMenu *menu, const wxPoint& pos) { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 3556756e23..4a98797e5d 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -282,9 +282,6 @@ public: GLCanvas3D* get_current_canvas3D(); BoundingBoxf bed_shape_bb() const; - void start_mapping_gcode_window(); - void stop_mapping_gcode_window(); - void arrange(); void find_new_position(const ModelInstancePtrs &instances); @@ -362,11 +359,11 @@ public: void set_bed_shape() const; void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const; - const NotificationManager* get_notification_manager() const; - NotificationManager* get_notification_manager(); + std::shared_ptr get_notification_manager(); + void init_notification_manager(); void bring_instance_forward(); - + // ROII wrapper for suppressing the Undo / Redo snapshot to be taken. class SuppressSnapshots { @@ -383,10 +380,11 @@ public: Plater *m_plater; }; - // ROII wrapper for taking an Undo / Redo snapshot while disabling the snapshot taking by the methods called from inside this snapshot. + // RAII wrapper for taking an Undo / Redo snapshot while disabling the snapshot taking by the methods called from inside this snapshot. class TakeSnapshot { public: + TakeSnapshot(Plater *plater, const std::string &snapshot_name); TakeSnapshot(Plater *plater, const wxString &snapshot_name) : m_plater(plater) { m_plater->take_snapshot(snapshot_name); diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index b8d4ead509..5da3594582 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -636,8 +636,7 @@ PlaterPresetComboBox::~PlaterPresetComboBox() static void run_wizard(ConfigWizard::StartPage sp) { - if (wxGetApp().check_and_save_current_preset_changes()) - wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); + wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); } void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt) diff --git a/src/slic3r/GUI/ProgressStatusBar.cpp b/src/slic3r/GUI/ProgressStatusBar.cpp index 10c3911613..18485659c5 100644 --- a/src/slic3r/GUI/ProgressStatusBar.cpp +++ b/src/slic3r/GUI/ProgressStatusBar.cpp @@ -194,3 +194,4 @@ void ProgressStatusBar::hide_cancel_button() } } + diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index 73c046be6d..3ce3c85792 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -25,6 +25,7 @@ namespace Slic3r { * of the Slicer main window. It consists of a message area to the left and a * progress indication area to the right with an optional cancel button. */ + class ProgressStatusBar : public ProgressIndicator { wxStatusBar *self; // we cheat! It should be the base class but: perl! diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 3409c531da..92c758d4be 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1874,7 +1874,7 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) con const_cast(&m_arrow)->set_color(-1, uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); GLShaderProgram* shader = wxGetApp().get_current_shader(); if (shader != nullptr) - shader->set_uniform("emission_factor", 0.0); + shader->set_uniform("emission_factor", 0.0f); glsafe(::glTranslated(0.0, 5.0, 0.0)); m_arrow.render(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index bc4c6b7289..5effa35990 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -492,17 +492,15 @@ void Tab::OnActivate() activate_selected_page([](){}); m_hsizer->Layout(); - // Workaroud for Menu instead of NoteBook #ifdef _MSW_DARK_MODE -// if (wxGetApp().tabs_as_menu()) - { - wxSize sz = m_presets_choice->GetSize(); - wxSize ok_sz = wxSize(35 * m_em_unit, m_presets_choice->GetBestSize().y+1); - if (sz != ok_sz) { - m_presets_choice->SetMinSize(ok_sz); - m_presets_choice->SetSize(ok_sz); - GetSizer()->GetItem(size_t(0))->GetSizer()->Layout(); - } + // Because of DarkMode we use our own Notebook (inherited from wxSiplebook) instead of wxNotebook + // And it looks like first Layout of the page doesn't update a size of the m_presets_choice + // So we have to set correct size explicitely + if (wxSize ok_sz = wxSize(35 * m_em_unit, m_presets_choice->GetBestSize().y); + ok_sz != m_presets_choice->GetSize()) { + m_presets_choice->SetMinSize(ok_sz); + m_presets_choice->SetSize(ok_sz); + GetSizer()->GetItem(size_t(0))->GetSizer()->Layout(); } #endif // _MSW_DARK_MODE Refresh(); @@ -1509,7 +1507,7 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Brim")); optgroup->append_single_option_line("brim_type", category_path + "brim"); optgroup->append_single_option_line("brim_width", category_path + "brim"); - optgroup->append_single_option_line("brim_offset", category_path + "brim"); + optgroup->append_single_option_line("brim_separation", category_path + "brim"); page = add_options_page(L("Support material"), "support"); category_path = "support-material_1698#"; @@ -1565,12 +1563,14 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Modifiers")); optgroup->append_single_option_line("first_layer_speed"); + optgroup->append_single_option_line("first_layer_speed_over_raft"); optgroup = page->new_optgroup(L("Acceleration control (advanced)")); optgroup->append_single_option_line("perimeter_acceleration"); optgroup->append_single_option_line("infill_acceleration"); optgroup->append_single_option_line("bridge_acceleration"); optgroup->append_single_option_line("first_layer_acceleration"); + optgroup->append_single_option_line("first_layer_acceleration_over_raft"); optgroup->append_single_option_line("default_acceleration"); optgroup = page->new_optgroup(L("Autospeed (advanced)")); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 9fb8a1e642..ef480f60f8 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -696,7 +696,12 @@ void DiffViewCtrl::context_menu(wxDataViewEvent& event) auto it = m_items_map.find(item); if (it == m_items_map.end() || !it->second.is_long) return; - FullCompareDialog(it->second.opt_name, it->second.old_val, it->second.new_val).ShowModal(); + + size_t column_cnt = this->GetColumnCount(); + const wxString old_value_header = this->GetColumn(column_cnt - 2)->GetTitle(); + const wxString new_value_header = this->GetColumn(column_cnt - 1)->GetTitle(); + FullCompareDialog(it->second.opt_name, it->second.old_val, it->second.new_val, + old_value_header, new_value_header).ShowModal(); #ifdef __WXOSX__ wxWindow* parent = this->GetParent(); @@ -752,8 +757,8 @@ std::vector DiffViewCtrl::selected_options() // UnsavedChangesDialog //------------------------------------------ -UnsavedChangesDialog::UnsavedChangesDialog(const wxString& header) - : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, _L("PrusaSlicer is closing: Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +UnsavedChangesDialog::UnsavedChangesDialog(const wxString& header, const wxString& caption/* = wxString()*/) + : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, (caption.IsEmpty() ? _L("PrusaSlicer is closing") : caption) + ": " + _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { m_app_config_key = "default_action_on_close_application"; @@ -1281,7 +1286,8 @@ void UnsavedChangesDialog::on_sys_color_changed() // FullCompareDialog //------------------------------------------ -FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString& old_value, const wxString& new_value) +FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString& old_value, const wxString& new_value, + const wxString& old_value_header, const wxString& new_value_header) : wxDialog(nullptr, wxID_ANY, option_name, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { wxGetApp().UpdateDarkUI(this); @@ -1302,8 +1308,8 @@ FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString grid_sizer->Add(text, 0, wxALL, border); }; - add_header(_L("Old value")); - add_header(_L("New value")); + add_header(old_value_header); + add_header(new_value_header); auto get_set_from_val = [](wxString str) { if (str.Find("\n") == wxNOT_FOUND) @@ -1327,7 +1333,7 @@ FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString std::set_difference(new_set.begin(), new_set.end(), old_set.begin(), old_set.end(), std::inserter(new_old_diff_set, new_old_diff_set.begin())); auto add_value = [grid_sizer, border, this](wxString label, const std::set& diff_set, bool is_colored = false) { - wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, label, wxDefaultPosition, wxSize(400, 400), wxTE_MULTILINE | wxTE_READONLY | wxBORDER_SIMPLE | wxTE_RICH); + wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, label, wxDefaultPosition, wxSize(400, 400), wxTE_MULTILINE | wxTE_READONLY | wxBORDER_DEFAULT | wxTE_RICH); wxGetApp().UpdateDarkUI(text); text->SetStyle(0, label.Len(), wxTextAttr(is_colored ? wxColour(orange) : wxNullColour, wxNullColour, this->GetFont())); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 06838d9361..9dbaf6e99c 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -263,7 +263,7 @@ class UnsavedChangesDialog : public DPIDialog std::vector> names_and_types; public: - UnsavedChangesDialog(const wxString& header); + UnsavedChangesDialog(const wxString& header, const wxString& caption = wxString()); UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset); ~UnsavedChangesDialog() {} @@ -299,7 +299,8 @@ protected: class FullCompareDialog : public wxDialog { public: - FullCompareDialog(const wxString& option_name, const wxString& old_value, const wxString& new_value); + FullCompareDialog(const wxString& option_name, const wxString& old_value, const wxString& new_value, + const wxString& old_value_header, const wxString& new_value_header); ~FullCompareDialog() {} }; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 8903f3b30b..a3d6384e05 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -672,6 +672,9 @@ void ModeButton::focus_button(const bool focus) Slic3r::GUI::wxGetApp().normal_font(); SetFont(new_font); +//#ifdef _WIN32 +// GetParent()->Refresh(); +//#else #ifndef _WIN32 SetForegroundColour(wxSystemSettings::GetColour(focus ? wxSYS_COLOUR_BTNTEXT : #if defined (__linux__) && defined (__WXGTK3__) diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 1462e8aa01..25876bd235 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -283,6 +283,7 @@ public: void OnLeaveBtn(wxMouseEvent& event) { focus_button(m_is_selected); event.Skip(); } void SetState(const bool state); + bool is_selected() { return m_is_selected; } protected: void focus_button(const bool focus); @@ -312,6 +313,7 @@ public: void set_items_border(int border); void msw_rescale(); + const std::vector& get_btns() { return m_mode_btns; } private: std::vector m_mode_btns; diff --git a/t/combineinfill.t b/t/combineinfill.t index 925e5b11d4..a19e817a14 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -36,7 +36,15 @@ plan tests => 8; $layer_infill{$self->Z} = 1; } } - $layers{$args->{Z}} = 1 if $cmd eq 'G1' && $info->{dist_Z} > 0; + # Previously, all G-code commands had a fixed number of decimal points with means with redundant zeros after decimal points. + # We changed this behavior and got rid of these redundant padding zeros, which caused this test to fail + # because the position in Z-axis is compared as a string, and previously, G-code contained the following two commands: + # "G1 Z5 F5000 ; lift nozzle" + # "G1 Z5.000 F7800.000" + # That has a different Z-axis position from the view of string comparisons of floating-point numbers. + # To correct the computation of the number of printed layers, even in the case of string comparisons of floating-point numbers, + # we filtered out the G-code command with the commend 'lift nozzle'. + $layers{$args->{Z}} = 1 if $cmd eq 'G1' && $info->{dist_Z} && index($info->{comment}, 'lift nozzle') == -1; }); my $layers_with_perimeters = scalar(keys %layer_infill); diff --git a/t/cooling.t b/t/cooling.t index 2f444cf9d1..e46cfa2f77 100644 --- a/t/cooling.t +++ b/t/cooling.t @@ -33,7 +33,10 @@ sub buffer { $gcodegen = Slic3r::GCode->new; $gcodegen->apply_print_config($print_config); $gcodegen->set_layer_count(10); - $gcodegen->set_extruders([ 0 ]); + + my $extruders_ref = shift; + $extruders_ref = [ 0 ] if !defined $extruders_ref; + $gcodegen->set_extruders($extruders_ref); return Slic3r::GCode::CoolingBuffer->new($gcodegen); } @@ -131,8 +134,8 @@ $config->set('disable_fan_first_layers', [ 0 ]); 'cooling' => [ 1 , 0 ], 'fan_below_layer_time' => [ $print_time2 + 1, $print_time2 + 1 ], 'slowdown_below_layer_time' => [ $print_time2 + 2, $print_time2 + 2 ] - }); - $buffer->gcodegen->set_extruders([ 0, 1 ]); + }, + [ 0, 1]); my $gcode = $buffer->process_layer($gcode1 . "T1\nG1 X0 E1 F3000\n", 0); like $gcode, qr/^M106/, 'fan is activated for the 1st tool'; like $gcode, qr/.*M107/, 'fan is disabled for the 2nd tool'; diff --git a/tests/fff_print/test_gcodewriter.cpp b/tests/fff_print/test_gcodewriter.cpp index a4c8aa09a7..15ffaebb48 100644 --- a/tests/fff_print/test_gcodewriter.cpp +++ b/tests/fff_print/test_gcodewriter.cpp @@ -78,13 +78,13 @@ SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") { } } WHEN("set_speed is called to set speed to 1") { - THEN("Output string is G1 F1.000") { - REQUIRE_THAT(writer.set_speed(1.0), Catch::Equals("G1 F1.000\n")); + THEN("Output string is G1 F1") { + REQUIRE_THAT(writer.set_speed(1.0), Catch::Equals("G1 F1\n")); } } WHEN("set_speed is called to set speed to 203.200022") { - THEN("Output string is G1 F203.200") { - REQUIRE_THAT(writer.set_speed(203.200022), Catch::Equals("G1 F203.200\n")); + THEN("Output string is G1 F203.2") { + REQUIRE_THAT(writer.set_speed(203.200022), Catch::Equals("G1 F203.2\n")); } } WHEN("set_speed is called to set speed to 203.200522") { diff --git a/tests/libslic3r/test_indexed_triangle_set.cpp b/tests/libslic3r/test_indexed_triangle_set.cpp index a02d0e411f..6a8cdfc05b 100644 --- a/tests/libslic3r/test_indexed_triangle_set.cpp +++ b/tests/libslic3r/test_indexed_triangle_set.cpp @@ -279,14 +279,32 @@ TEST_CASE("Simplify mesh by Quadric edge collapse to 5%", "[its]") CHECK(is_similar(its, mesh.its, cfg)); } +bool exist_triangle_with_twice_vertices(const std::vector& indices) +{ + for (const auto &face : indices) + if (face[0] == face[1] || + face[0] == face[2] || + face[1] == face[2]) return true; + return false; +} + TEST_CASE("Simplify trouble case", "[its]") { TriangleMesh tm = load_model("simplification.obj"); REQUIRE_FALSE(tm.empty()); float max_error = std::numeric_limits::max(); - uint32_t wanted_count = 8; + uint32_t wanted_count = 0; its_quadric_edge_collapse(tm.its, wanted_count, &max_error); - CHECK(tm.its.indices.size() <= 8); + CHECK(!exist_triangle_with_twice_vertices(tm.its.indices)); +} + +TEST_CASE("Simplified cube should not be empty.", "[its]") +{ + auto its = its_make_cube(1, 2, 3); + float max_error = std::numeric_limits::max(); + uint32_t wanted_count = 0; + its_quadric_edge_collapse(its, wanted_count, &max_error); + CHECK(!its.indices.empty()); } TEST_CASE("Neighbors in cube", "[its]") diff --git a/version.inc b/version.inc index 4e7aab2355..d981d620ab 100644 --- a/version.inc +++ b/version.inc @@ -3,7 +3,7 @@ set(SLIC3R_APP_NAME "PrusaSlicer") set(SLIC3R_APP_KEY "PrusaSlicer") -set(SLIC3R_VERSION "2.4.0-alpha0") +set(SLIC3R_VERSION "2.4.0-alpha1") set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN") set(SLIC3R_RC_VERSION "2,4,0,0") set(SLIC3R_RC_VERSION_DOTS "2.4.0.0") diff --git a/xs/t/09_polyline.t b/xs/t/09_polyline.t index 0a7b8dcb05..5203ec5ef4 100644 --- a/xs/t/09_polyline.t +++ b/xs/t/09_polyline.t @@ -89,40 +89,40 @@ is_deeply $polyline->pp, [ @$points, @$points ], 'append_polyline'; } # disabled because we now use a more efficient but incomplete algorithm -if (0) { - my $polyline = Slic3r::Polyline->new( - map [$_,10], (0,10,20,30,40,50,60) - ); - { - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( - [25,0], [55,0], [55,30], [25,30], - )); - my $p = $polyline->clone; - $p->simplify_by_visibility($expolygon); - is_deeply $p->pp, [ - map [$_,10], (0,10,20,30,50,60) - ], 'simplify_by_visibility()'; - } - { - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( - [-15,0], [75,0], [75,30], [-15,30], - )); - my $p = $polyline->clone; - $p->simplify_by_visibility($expolygon); - is_deeply $p->pp, [ - map [$_,10], (0,60) - ], 'simplify_by_visibility()'; - } - { - my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( - [-15,0], [25,0], [25,30], [-15,30], - )); - my $p = $polyline->clone; - $p->simplify_by_visibility($expolygon); - is_deeply $p->pp, [ - map [$_,10], (0,20,30,40,50,60) - ], 'simplify_by_visibility()'; - } -} +#if (0) { +# my $polyline = Slic3r::Polyline->new( +# map [$_,10], (0,10,20,30,40,50,60) +# ); +# { +# my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( +# [25,0], [55,0], [55,30], [25,30], +# )); +# my $p = $polyline->clone; +# $p->simplify_by_visibility($expolygon); +# is_deeply $p->pp, [ +# map [$_,10], (0,10,20,30,50,60) +# ], 'simplify_by_visibility()'; +# } +# { +# my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( +# [-15,0], [75,0], [75,30], [-15,30], +# )); +# my $p = $polyline->clone; +# $p->simplify_by_visibility($expolygon); +# is_deeply $p->pp, [ +# map [$_,10], (0,60) +# ], 'simplify_by_visibility()'; +# } +# { +# my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new( +# [-15,0], [25,0], [25,30], [-15,30], +# )); +# my $p = $polyline->clone; +# $p->simplify_by_visibility($expolygon); +# is_deeply $p->pp, [ +# map [$_,10], (0,20,30,40,50,60) +# ], 'simplify_by_visibility()'; +# } +#} __END__ diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 78a5dde376..4c25838946 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -10,7 +10,6 @@ CoolingBuffer(GCode* gcode) %code{% RETVAL = new CoolingBuffer(*gcode); %}; ~CoolingBuffer(); - Ref gcodegen(); std::string process_layer(std::string gcode, size_t layer_id) %code{% RETVAL = THIS->process_layer(std::move(gcode), layer_id, true); %}; diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp index 10bbb263f8..7846ea5f4c 100644 --- a/xs/xsp/Polyline.xsp +++ b/xs/xsp/Polyline.xsp @@ -31,8 +31,6 @@ void extend_end(double distance); void extend_start(double distance); void simplify(double tolerance); - void simplify_by_visibility(ExPolygon* expolygon) - %code{% THIS->simplify_by_visibility(*expolygon); %}; void split_at(Point* point, Polyline* p1, Polyline* p2) %code{% THIS->split_at(*point, p1, p2); %}; bool is_straight(); diff --git a/xs/xsp/XS.xsp b/xs/xsp/XS.xsp index 083d21d388..68ea282bc0 100644 --- a/xs/xsp/XS.xsp +++ b/xs/xsp/XS.xsp @@ -23,12 +23,6 @@ BUILD() RETVAL = newSVpv(SLIC3R_BUILD_ID, 0); OUTPUT: RETVAL -SV* -DEBUG_OUT_PATH_PREFIX() - CODE: - RETVAL = newSVpv(SLIC3R_DEBUG_OUT_PATH_PREFIX, 0); - OUTPUT: RETVAL - SV* FORK_NAME() CODE: