Fixed conflicts after merge with master

This commit is contained in:
enricoturri1966 2021-09-09 11:01:49 +02:00
commit 3369e1b599
85 changed files with 2018 additions and 969 deletions

View file

@ -1,87 +1,103 @@
# Building PrusaSlicer on UNIX/Linux
Please understand that PrusaSlicer team cannot support compilation on all possible Linux distros. Namely, we cannot help trouble shooting OpenGL driver issues or dependency issues if compiled against distro provided libraries. We can only support PrusaSlicer compiled the same way we do compile PrusaSlicer for our [binary builds](https://github.com/prusa3d/PrusaSlicer/releases), that means linked statically agains the dependencies compiled with the `deps` scripts.
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).
Instead of compiling PrusaSlicer from source code, one may consider to install PrusaSlicer [pre-compiled by contributors](https://github.com/prusa3d/PrusaSlicer/wiki/PrusaSlicer-on-Linux---binary-distributions).
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.
### How to build
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).
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.
## Step by step guide
Perl is not required anymore.
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.
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:
#### 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=<target destdir> -DDEP_DOWNLOAD_DIR=<download cache dir>
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. The optional `DEP_DOWNLOAD_DIR` argument specifies a directory to cache the downloaded
source packages for each dependent library. Can be useful for repeated builds, to avoid unnecessary network traffic.
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=<path to destdir>/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=<target destdir>` 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=<download cache 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
```
### 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

View file

@ -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 the<a>Minimum shell thickness</a>in 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

View file

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

View file

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

View file

@ -15,11 +15,11 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
#define INTENSITY_AMBIENT 0.3
// vertex attributes
in vec3 v_position;
in vec3 v_normal;
attribute vec3 v_position;
attribute vec3 v_normal;
// instance attributes
in vec3 i_offset;
in vec2 i_scales;
attribute vec3 i_offset;
attribute vec2 i_scales;
// x = tainted, y = specular;
varying vec2 intensity;
@ -34,7 +34,9 @@ void main()
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
vec4 world_position = vec4(v_position * vec3(vec2(i_scales.x), i_scales.y) + i_offset, 1.0);
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);

View file

@ -12,7 +12,7 @@ namespace Slic3r {
#ifdef WIN32
//only dll name with .dll suffix - currently case sensitive
const std::vector<std::wstring> BlacklistedLibraryCheck::blacklist({ L"NahimicOSD.dll" });
const std::vector<std::wstring> BlacklistedLibraryCheck::blacklist({ L"NahimicOSD.dll", L"SS2OSD.dll" });
bool BlacklistedLibraryCheck::get_blacklisted(std::vector<std::wstring>& 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();
}

View file

@ -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);
}
@ -214,7 +214,7 @@ static ExPolygons inner_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 top_outer_brim = top_level_objects_idx.find(object->id().id) != top_level_objects_idx.end();
@ -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())));

View file

@ -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;
}
@ -2293,6 +2336,16 @@ namespace Slic3r {
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\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 << "</" << METADATA_TAG << ">\n";
if (model.is_fdm_support_painted())
stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION << "\">" << FDM_SUPPORTS_PAINTING_VERSION << "</" << METADATA_TAG << ">\n";
if (model.is_seam_painted())
stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_SEAM_PAINTING_VERSION << "\">" << SEAM_PAINTING_VERSION << "</" << METADATA_TAG << ">\n";
if (model.is_mm_painted())
stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_MM_PAINTING_VERSION << "\">" << MM_PAINTING_VERSION << "</" << METADATA_TAG << ">\n";
std::string name = xml_escape(boost::filesystem::path(filename).stem().string());
stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"Designer\">" << "</" << METADATA_TAG << ">\n";
@ -2613,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";
@ -2990,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<ConfigOptionFloat>("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<ConfigOptionFloat>("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)
@ -3000,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;
}

View file

@ -744,27 +744,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.
@ -782,7 +783,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) {
@ -1046,7 +1047,7 @@ std::vector<const PrintInstance*> 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();
@ -1111,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<ConfigOptionPoints>("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)
@ -1125,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();
@ -1139,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 &region = 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();
@ -1218,7 +1219,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, true));
// Let the start-up script prime the 1st printing tool.
m_placeholder_parser.set("initial_tool", initial_extruder_id);
@ -1261,10 +1262,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) {
@ -1272,14 +1273,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);
@ -1291,7 +1292,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.
@ -1317,8 +1318,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();
@ -1330,7 +1331,7 @@ 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();
@ -1346,7 +1347,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
}
#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.
@ -1361,9 +1362,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;
@ -1375,15 +1376,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.
@ -1410,19 +1411,19 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
}
#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(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());
// Process filament-specific gcode in extruder order.
{
@ -1434,48 +1435,48 @@ 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();
}
@ -1565,16 +1566,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),
@ -1587,18 +1588,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));
}
@ -1608,7 +1609,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);
@ -1621,7 +1622,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.
@ -1629,7 +1630,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;
@ -1646,7 +1647,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()) {
@ -1654,7 +1655,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));
}
}
}
@ -1891,7 +1892,7 @@ namespace Skirt {
// and performing the extruder specific extrusions together.
void GCode::process_layer(
// Write into the output file.
FILE *file,
GCodeOutputStream &file,
const Print &print,
// Set of object & print layers of the same PrintObject and with the same print_z.
const std::vector<LayerToPrint> &layers,
@ -1977,6 +1978,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));
@ -2235,8 +2237,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)
@ -2249,11 +2256,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)
@ -2298,7 +2307,7 @@ 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);
BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
log_memory_info();
}
@ -2634,22 +2643,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);
@ -2673,7 +2702,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);
@ -2705,6 +2734,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())) {
@ -2749,6 +2780,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(

View file

@ -126,6 +126,7 @@ public:
m_layer_count(0),
m_layer_index(-1),
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<coord_t>::max(), std::numeric_limits<coord_t>::max()))
{}
~GCode() {}
~GCode() = default;
// throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled().
@ -183,13 +184,40 @@ 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<LayerToPrint> collect_layers_to_print(const PrintObject &object);
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
void process_layer(
// Write into the output file.
FILE *file,
GCodeOutputStream &file,
const Print &print,
// Set of object & print layers of the same PrintObject and with the same print_z.
const std::vector<LayerToPrint> &layers,
@ -319,6 +347,8 @@ private:
// 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 +385,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<unsigned int, std::vector<ObjectByExtruder>> &by_extruder,

View file

@ -21,6 +21,9 @@
#include <chrono>
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<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true;
}
void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector<MoveVertex>& 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<MoveVertex>& moves, std::vector<size_t>& 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 <percent, remaining time>
@ -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<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
const TimeMachine& machine = machines[i];
const TimeMachine& machine = self.machines[i];
if (machine.enabled) {
// export pair <percent, remaining time>
// 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<std::pair<unsigned int, unsigned int>> offsets;
while (std::getline(in, gcode_line)) {
if (!in.good()) {
fclose(out);
{
// Read the input stream 64kB at a time, extract lines and process them.
std::vector<char> 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;
gcode_line += "\n";
// replace placeholder lines
auto [processed, result, lines_added_count] = process_placeholders(gcode_line);
auto [processed, 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;
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);
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<GCodeProcessor::MoveVertex>();
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<size_t>(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<void()> cancel_callback)
void GCodeProcessor::process_file(const std::string& filename, std::function<void()> 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<std::chrono::milliseconds>(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::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
m_result.time = std::chrono::duration_cast<std::chrono::milliseconds>(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<std::string> 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<float>(string_to_double_decimal_point(s)));
}
@ -1364,9 +1443,15 @@ 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<float> extruder_diameters;
extract_floats(comment, "extruderDiameter", extruder_diameters);
m_result.extruders_count = extruder_diameters.size();
}
}
});
if (m_result.extruders_count == 0)
m_result.extruders_count = std::max<size_t>(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size()));
if (bed_size.is_defined()) {
@ -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 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;
}
break;
case 't':
case 'T':
{
process_T(line); // Select Tool
break;
}
default: { 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<typename T>
}
}
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<double>(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<double>(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<double>(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<float>(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast<float>(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<float>(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) {

View file

@ -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<MoveVertex>& moves);
void post_process(const std::string& filename, std::vector<MoveVertex>& moves, std::vector<size_t>& lines_ends);
};
struct UsedFilaments // filaments per ColorChange
@ -350,6 +350,8 @@ namespace Slic3r {
std::string filename;
unsigned int id;
std::vector<MoveVertex> moves;
// Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code.
std::vector<size_t> 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<size_t> m_move_id;
std::optional<size_t> 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<std::chrono::high_resolution_clock> m_start_time;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
enum class EProducer
{
@ -511,7 +558,6 @@ namespace Slic3r {
static const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> 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<size_t>(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<void()> cancel_callback = nullptr);
void process_file(const std::string& filename, std::function<void()> 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);

View file

@ -2,6 +2,7 @@
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/nowide/fstream.hpp>
#include <charconv>
#include <fstream>
#include <iostream>
#include <iomanip>
@ -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<const char*, const char*> &command)
const char* GCodeReader::parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair<const char*, const char*> &command)
{
PROFILE_FUNC();
@ -70,9 +71,16 @@ const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline,
}
if (axis != NUM_AXES_WITH_UNKNOWN) {
// Try to parse the numeric value.
#ifdef WIN32
double v;
auto [pend, ec] = std::from_chars(++ c, end, v);
if (pend != c && is_end_of_word(*pend)) {
#else
// The older version of GCC and Clang support std::from_chars just for integers, so strtod we used it instead.
char *pend = nullptr;
double v = strtod(++ c, &pend);
if (pend != nullptr && is_end_of_word(*pend)) {
#endif
// The axis value has been parsed correctly.
if (axis != UNKNOWN_AXIS)
gline.m_axis[int(axis)] = float(v);
@ -125,13 +133,42 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair<const char*, co
}
}
void GCodeReader::parse_file(const std::string &file, callback_t callback)
bool GCodeReader::parse_file(const std::string &file, callback_t callback)
{
boost::nowide::ifstream f(file);
f.sync_with_stdio(false);
std::vector<char> 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

View file

@ -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<void(GCodeReader&, const GCodeLine&)> 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<typename Callback>
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<const char*, const char*> 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<typename Callback>
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<const char*, const char*> &command);
const char* parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair<const char*, const char*> &command);
void update_coordinates(GCodeLine &gline, std::pair<const char*, const char*> &command);
static bool is_whitespace(char c) { return c == ' ' || c == '\t'; }

View file

@ -1,17 +1,21 @@
#include "GCodeWriter.hpp"
#include "CustomGCode.hpp"
#include <algorithm>
#include <charconv>
#include <iomanip>
#include <iostream>
#include <map>
#include <assert.h>
#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 {
@ -288,16 +292,89 @@ 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) {
*ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = axis;
#ifdef WIN32
this->ptr_err = std::to_chars(this->ptr_err.ptr, this->buf_end, v, std::chars_format::fixed, digits);
#else
int buf_capacity = int(this->buf_end - this->ptr_err.ptr);
int ret = snprintf(this->ptr_err.ptr, buf_capacity, "%.*lf", int(digits), v);
if (ret <= 0 || ret > buf_capacity)
ptr_err.ec = std::errc::value_too_large;
else
this->ptr_err.ptr = this->ptr_err.ptr + ret;
#endif
}
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 +382,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 +415,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 +449,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
@ -403,15 +474,11 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
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 +487,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)

View file

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

View file

@ -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;
@ -723,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:
@ -1127,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();

View file

@ -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 <class T>
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<ExPolygon>(const ExPolygon &area);
template void Polyline::simplify_by_visibility<ExPolygonCollection>(const ExPolygonCollection &area);
#endif
void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
{

View file

@ -72,7 +72,7 @@ public:
void extend_start(double distance);
Points equally_spaced_points(double distance) const;
void simplify(double tolerance);
template <class T> void simplify_by_visibility(const T &area);
// template <class T> 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(); }

View file

@ -428,9 +428,9 @@ static std::vector<std::string> 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",

View file

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

View file

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

View file

@ -505,10 +505,10 @@ void PrintConfigDef::init_fff_params()
def->mode = comSimple;
def->set_default_value(new ConfigOptionEnum<BrimType>(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");

View file

@ -449,13 +449,15 @@ protected: \
PRINT_CONFIG_CLASS_DEFINE(
PrintObjectConfig,
((ConfigOptionFloat, brim_offset))
((ConfigOptionFloat, brim_separation))
((ConfigOptionEnum<BrimType>, 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))

View file

@ -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,14 +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())
#if 0
if (! custom_facets.indices.empty()) {
if (seam)
project_triangles_to_slabs(this->layers(), custom_facets,
(this->trafo_centered() * mv->get_matrix()).cast<float>(),
seam, out);
#else
slice_mesh_slabs(custom_facets, zs_from_layers(this->layers()), this->trafo_centered() * mv->get_matrix(), nullptr, &out, [](){});
#endif
else {
std::vector<Polygons> 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]));
}
}
}
}

View file

@ -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<stl_vertex>;
@ -79,10 +83,13 @@ namespace QuadricEdgeCollapse {
init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn);
std::optional<uint32_t> 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,13 +99,24 @@ 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 */
// 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<void(void)> throw_on_cancel,
std::function<void(int)> 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<float>::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<int>(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<int>(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<TriangleInfos, VertexInfos, EdgeInfos, Errors>
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<uint32_t> 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<uint32_t>(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<uint32_t>(t0[t0i]);
if (t0vi0 == vi0) {
++t0i;
t0vi0 = static_cast<uint32_t>(t0[t0i]);
}
++t0i;
uint32_t t0vi1 = static_cast<uint32_t>(t0[t0i]);
if (t0vi1 == vi0) {
++t0i;
t0vi1 = static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(t1[t1i]) == vi1) {
++t1i;
if (t1i == 3) t1i = 0; // triangle loop(modulo 3)
}
if (static_cast<uint32_t>(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 */

View file

@ -2813,19 +2813,19 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf
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<float>(object.config().brim_offset.value + object.config().brim_width.value);
const auto brim_separation = scaled<float>(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

View file

@ -37,24 +37,37 @@
//====================
// 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_ALPHA0)
#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_ALPHA0)
#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)
// Enable replacing a missing file during reload from disk command
#define ENABLE_RELOAD_FROM_DISK_REPLACE_FILE (1 && ENABLE_2_4_0_ALPHA0)
#define ENABLE_RELOAD_FROM_DISK_REPLACE_FILE (1 && ENABLE_2_4_0_ALPHA2)
#endif // _prusaslicer_technologies_h_

View file

@ -984,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)))
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";
CloseHandle(hProcess);
}
#elif defined(__linux__) or defined(__APPLE__)
// Get current memory usage.
#ifdef __APPLE__

View file

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

View file

@ -1,62 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>com.prusa3d.PrusaSlicer</id>
<launchable type="desktop-id">com.prusa3d.PrusaSlicer.desktop</launchable>
<provides>
<id>prusa-slicer.desktop</id>
</provides>
<name>PrusaSlicer</name>
<summary>Powerful 3D printing slicer optimized for Prusa printers</summary>
<metadata_license>0BSD</metadata_license>
<project_license>AGPL-3.0-only</project_license>
<description>
<p>
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.
</p>
<p>
PrusaSlicer is based on Slic3r by Alessandro Ranelucci and the RepRap community.
</p>
<p>
What are some of PrusaSlicer's main features?
</p>
<ul>
<li>multi-platform (Linux/Mac/Win) and packaged as standalone-app with no dependencies required</li>
<li>complete command-line interface to use it with no GUI</li>
<li>multi-material (multiple extruders) object printing</li>
<li>multiple G-code flavors supported (RepRap, Makerbot, Mach3, Machinekit etc.)</li>
<li>ability to plate multiple objects having distinct print settings</li>
<li>multithread processing</li>
<li>STL auto-repair (tolerance for broken models)</li>
<li>wide automated unit testing</li>
</ul>
</description>
<url type="homepage">https://www.prusa3d.com/prusaslicer/</url>
<url type="help">https://help.prusa3d.com</url>
<url type="bugtracker">https://github.com/prusa3d/PrusaSlicer/issues</url>
<screenshots>
<screenshot type="default">
<image>https://user-images.githubusercontent.com/590307/78981854-24d07580-7b21-11ea-9441-77923534a659.png</image>
</screenshot>
<screenshot>
<image>https://user-images.githubusercontent.com/590307/78981860-2863fc80-7b21-11ea-8c2d-8ff79ced2578.png</image>
</screenshot>
<screenshot>
<image>https://user-images.githubusercontent.com/590307/78981862-28fc9300-7b21-11ea-9b0d-d03e16b709d3.png</image>
</screenshot>
</screenshots>
<content_rating type="oars-1.1" />
<releases>
<release version="2.2.0" date="2020-03-21">
<description>
<p>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.</p>
</description>
</release>
</releases>
</component>

View file

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

View file

@ -574,12 +574,9 @@ 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;
}

View file

@ -281,7 +281,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
toggle_field(el, have_skirt);
bool have_brim = config->opt_enum<BrimType>("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 +312,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" })

View file

@ -382,6 +382,8 @@ 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
// Do NOT add gcode viewer desktop file on ChromeOS
if (platform_flavor() != PlatformFlavor::LinuxOnChromium) {
// Icon
if (!target_dir_icons.empty())
{
@ -416,6 +418,8 @@ void DesktopIntegrationDialog::perform_desktop_integration()
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()
@ -433,7 +437,9 @@ void DesktopIntegrationDialog::undo_desktop_intgration()
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
// gcode viwer .desktop
// 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;
@ -445,6 +451,7 @@ void DesktopIntegrationDialog::undo_desktop_intgration()
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
}
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess);
}

View file

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

View file

@ -70,18 +70,9 @@ static std::vector<std::array<float, 4>> decode_colors(const std::vector<std::st
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()
@ -146,7 +137,7 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const
// 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.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed &&
height == round_to_nearest(move.height, 2) && width == round_to_nearest(move.width, 2) &&
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: {
@ -183,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 } } });
}
@ -293,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, const std::vector<size_t> &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<uint64_t>(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<uint64_t>(line.length());
m_lines_map.push_back({ offset, line_length });
offset += static_cast<uint64_t>(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<uint64_t>(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;
@ -354,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;
@ -388,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
@ -406,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<uint64_t>(m_lines_map.size())) {
end_id = static_cast<uint64_t>(m_lines_map.size()) - 1;
if (end_id >= static_cast<uint64_t>(m_lines_ends.size())) {
end_id = static_cast<uint64_t>(m_lines_ends.size()) - 1;
start_id = end_id - lines_count + 1;
}
@ -512,7 +474,7 @@ void GCodeViewer::SequentialView::render(float legend_height) const
}
const std::vector<GCodeViewer::Color> GCodeViewer::Extrusion_Role_Colors {{
{ 0.75f, 0.75f, 0.75f, 1.0f }, // erNone
{ 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
@ -615,8 +577,7 @@ 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, gcode_result.lines_ends);
#if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER
if (wxGetApp().is_gcode_viewer())
@ -704,8 +665,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
@ -721,11 +682,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:
@ -807,7 +768,26 @@ void GCodeViewer::render()
case EMoveType::Unretract:
case EMoveType::Seam: {
#if ENABLE_SEAMS_USING_MODELS
if (wxGetApp().is_gl_version_greater_or_equal_to(3, 1)) {
#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));
@ -819,6 +799,7 @@ void GCodeViewer::render()
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;
@ -1100,7 +1081,7 @@ 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.x(), v.y(), v.x());
fprintf(fp, "v %g %g %g\n", v.x(), v.y(), v.z());
}
// save normals to file
@ -1155,16 +1136,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
@ -1219,9 +1190,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<unsigned int>(indices.size()));
indices.push_back(static_cast<IBufferType>(indices.size()));
buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1);
buffer.paths.back().sub_paths.front().first.position = prev.position;
}
@ -1229,11 +1200,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<unsigned int>(indices.size()));
indices.push_back(static_cast<IBufferType>(indices.size()));
}
// add current index
indices.push_back(static_cast<unsigned int>(indices.size()));
indices.push_back(static_cast<IBufferType>(indices.size()));
last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position };
};
@ -1436,20 +1407,63 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
};
#if ENABLE_SEAMS_USING_MODELS
// format data into the buffers to be rendered as model
// 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(1.5f * curr.width);
instances.push_back(curr.width);
// append height
instances.push_back(1.5f * curr.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<double>(1.5f * curr.width);
const double height = static_cast<double>(1.5f * curr.height);
const Transform3d trafo = Geometry::assemble_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast<double>(), Vec3d::Zero(), { width, width, height });
const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> 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<double>();
vertices.push_back(static_cast<float>(position.x()));
vertices.push_back(static_cast<float>(position.y()));
vertices.push_back(static_cast<float>(position.z()));
// append normal
const Vec3d normal = normal_matrix * entity.normals[i].cast<double>();
vertices.push_back(static_cast<float>(normal.x()));
vertices.push_back(static_cast<float>(normal.y()));
vertices.push_back(static_cast<float>(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<IBufferType>(entity.indices[i] + base_index));
}
}
};
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
#endif // ENABLE_SEAMS_USING_MODELS
#if ENABLE_GCODE_VIEWER_STATISTICS
@ -1533,7 +1547,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();
@ -1550,6 +1569,24 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
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<unsigned int>(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);
@ -1559,6 +1596,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
break;
}
#endif // ENABLE_SEAMS_USING_MODELS
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
}
// collect options zs for later use
@ -1741,6 +1779,23 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
for (size_t i = 0; i < m_buffers.size(); ++i) {
TBuffer& t_buffer = m_buffers[i];
#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()) {
@ -1749,6 +1804,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
}
}
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) {
@ -1842,10 +1898,20 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
// 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<unsigned int>(i_multibuffer.size()) - 1, 0, i - 1);
}
@ -1853,14 +1919,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<unsigned int>(i_multibuffer.size()) - 1, 0, i - 1);
}
@ -1884,6 +1960,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast<unsigned int>(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; }
}
}
@ -1898,7 +1981,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result)
for (size_t i = 0; i < m_buffers.size(); ++i) {
TBuffer& t_buffer = m_buffers[i];
#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) {
@ -2174,7 +2261,12 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
continue;
#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;
@ -2239,7 +2331,12 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
bool found = false;
for (const TBuffer& buffer : m_buffers) {
#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();
@ -2400,15 +2497,24 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
}
#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<TBuffer&>(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)
if (!buffer.visible || buffer.model.instances.s_ids.empty())
continue;
buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, buffer.model.color });
@ -2419,7 +2525,11 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
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;
@ -2665,7 +2775,11 @@ void GCodeViewer::render_toolpaths()
};
#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) {
@ -2685,6 +2799,66 @@ void GCodeViewer::render_toolpaths()
}
}
};
#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<size_t>(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<size_t>(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) {
@ -2708,11 +2882,22 @@ void GCodeViewer::render_toolpaths()
shader->start_using();
#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) {
@ -2860,7 +3045,7 @@ void GCodeViewer::render_legend(float& legend_height)
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<float, 4>& offsets = { 0.0f, 0.0f, 0.0f, 0.0f },
double used_filament_m = 0.0, double used_filament_g = 0.0,
std::function<void()> callback = nullptr) {
@ -2878,6 +3063,7 @@ void GCodeViewer::render_legend(float& legend_height)
}
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);
@ -2887,6 +3073,7 @@ void GCodeViewer::render_legend(float& legend_height)
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;
@ -3532,7 +3719,11 @@ void GCodeViewer::render_legend(float& legend_height)
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<unsigned int>(color)], text);
#else
append_item((buffer.shader == "options_110") ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast<unsigned int>(color)], text);
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
};
// options section
@ -3752,7 +3943,11 @@ void GCodeViewer::render_statistics()
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
}
@ -3784,6 +3979,9 @@ void GCodeViewer::render_statistics()
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);

View file

@ -105,9 +105,16 @@ class GCodeViewer
};
#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
@ -130,6 +137,16 @@ class GCodeViewer
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
@ -138,7 +155,18 @@ class GCodeViewer
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();
@ -280,7 +308,12 @@ class GCodeViewer
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
@ -298,6 +331,9 @@ class GCodeViewer
GLModel model;
Color color;
InstanceVBuffer instances;
#if ENABLE_SEAMS_USING_BATCHED_MODELS
GLModel::InitializationData data;
#endif // ENABLE_SEAMS_USING_BATCHED_MODELS
void reset();
};
@ -362,7 +398,15 @@ class GCodeViewer
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; }
}
}
@ -520,6 +564,9 @@ class GCodeViewer
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 };
@ -541,6 +588,9 @@ class GCodeViewer
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 };
@ -569,6 +619,9 @@ class GCodeViewer
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
}
@ -594,6 +647,9 @@ class GCodeViewer
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;
@ -639,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<std::pair<uint64_t, uint64_t>> m_lines_map;
std::vector<size_t> m_lines_ends;
// current visible lines
std::vector<Line> 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, const std::vector<size_t> &lines_ends);
void reset() {
stop_mapping_file();
m_lines_map.clear();
m_lines_ends.clear();
m_lines.clear();
m_filename.clear();
}
@ -777,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

View file

@ -1114,16 +1114,6 @@ int GLCanvas3D::check_volumes_outside_state() const
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();
}
void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx)
{
m_render_sla_auxiliaries = visible;
@ -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;
#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);

View file

@ -621,9 +621,6 @@ public:
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);

View file

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

View file

@ -48,6 +48,15 @@ namespace GUI {
};
std::vector<Entity> 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:

View file

@ -33,13 +33,21 @@ std::pair<bool, std::string> 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
#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, 1))
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
@ -49,6 +57,7 @@ std::pair<bool, std::string> GLShadersManager::init()
#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

View file

@ -2082,10 +2082,10 @@ std::vector<std::pair<unsigned int, std::string>> 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;
@ -2334,10 +2334,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;

View file

@ -247,7 +247,7 @@ public:
bool has_current_preset_changes() const;
void update_saved_preset_from_current_preset();
std::vector<std::pair<unsigned int, std::string>> 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);

View file

@ -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__
) {
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<size_t> res = wxGetApp().plater()->load_files(paths, true, false);
if (!res.empty())
if (! wxGetApp().plater()->load_files(paths, true, false).empty())
wxGetApp().mainframe->update_title();
}
@ -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());
@ -4028,17 +4025,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);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 =
@ -773,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");
}

View file

@ -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
@ -126,11 +126,10 @@ bool GLGizmoMmuSegmentation::on_init()
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,13 +239,13 @@ 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);
@ -261,7 +260,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
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();

View file

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

View file

@ -282,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;
}
@ -379,10 +379,10 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
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)
@ -400,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;
@ -440,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)

View file

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

View file

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

View file

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

View file

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

View file

@ -44,7 +44,7 @@ bool GLGizmoSimplify::on_init()
std::string GLGizmoSimplify::on_get_name() const
{
return (_L("Simplify")).ToUTF8().data();
return _u8L("Simplify");
}
void GLGizmoSimplify::on_render() {}
@ -55,6 +55,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();
@ -96,7 +97,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
@ -329,9 +330,9 @@ 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(
@ -383,4 +384,20 @@ 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;
}
} // namespace Slic3r::GUI

View file

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

View file

@ -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();
@ -863,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
@ -901,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<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
@ -925,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;
}
}

View file

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

View file

@ -22,6 +22,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
#include "libslic3r/format.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
@ -163,11 +164,9 @@ 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()
{
@ -181,14 +180,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::RegularNotification,
_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)
@ -1193,31 +1206,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;
}

View file

@ -104,7 +104,7 @@ private:
std::vector<size_t> 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
{
@ -176,6 +176,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);

View file

@ -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<std::string>& 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<std::string>& ids)
}
void read_used_binary(std::vector<std::string>& 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 {

View file

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

View file

@ -14,6 +14,7 @@
#include <wx/debug.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/log/trivial.hpp>
#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<PrusaSlicerTaskBarIcon>(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<GCodeViewerTaskBarIcon>(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__
@ -824,15 +825,26 @@ bool MainFrame::can_start_new_project() const
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()
@ -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;

View file

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

View file

@ -1107,6 +1107,10 @@ NotificationManager::NotificationManager(wxEvtHandler* evt_handler) :
m_evt_handler(evt_handler)
{
}
NotificationManager::~NotificationManager()
{
HintDatabase::get_instance().uninit();
}
void NotificationManager::push_notification(const NotificationType type, int timestamp)
{
auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications),

View file

@ -121,6 +121,7 @@ public:
};
NotificationManager(wxEvtHandler* evt_handler);
~NotificationManager();
// 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);

View file

@ -1361,7 +1361,8 @@ 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;
for (shift = 0; shift < node_parent->GetChildCount() && node_parent->GetNthChild(shift)->GetType() != itVolume; shift ++);
ObjectDataViewModelNode *deleted_node = node_parent->GetNthChild(current_volume_id+shift);
node_parent->GetChildren().Remove(deleted_node);

View file

@ -1837,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.
@ -2076,6 +2076,8 @@ Plater::priv::~priv()
{
if (config != nullptr)
delete config;
if (notification_manager != nullptr)
delete notification_manager;
}
void Plater::priv::update(unsigned int flags)
@ -2224,7 +2226,14 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
std::vector<size_t> 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<int>(100.0f * static_cast<float>(i) / static_cast<float>(input_files.size())), _L("Loading file") + ": " + from_path(filename));
dlg.Fit();
@ -2339,9 +2348,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& 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) {
@ -2460,7 +2467,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& 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"));
}
@ -3252,6 +3259,9 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const
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)
@ -3631,14 +3641,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"));
@ -4862,7 +4866,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");
@ -4875,7 +4879,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.
@ -4895,10 +4899,7 @@ void Plater::load_project(const wxString& filename)
p->reset();
std::vector<fs::path> 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();
@ -4915,7 +4916,7 @@ void Plater::add_model(bool imperial_units/* = false*/)
std::vector<fs::path> 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());
@ -4948,12 +4949,8 @@ void Plater::extract_config_from_project()
wxString input_file;
wxGetApp().load_project(this, input_file);
if (input_file.empty())
return;
std::vector<fs::path> 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()
@ -4982,10 +4979,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)
{
@ -5184,15 +5180,11 @@ bool Plater::load_files(const wxArrayString& filenames)
}
case LoadType::LoadGeometry: {
Plater::TakeSnapshot snapshot(this, _L("Import Object"));
std::vector<fs::path> in_paths;
in_paths.emplace_back(*it);
load_files(in_paths, true, false);
load_files({ *it }, true, false);
break;
}
case LoadType::LoadConfig: {
std::vector<fs::path> in_paths;
in_paths.emplace_back(*it);
load_files(in_paths, false, true);
load_files({ *it }, false, true);
break;
}
case LoadType::Unknown : {
@ -5699,8 +5691,16 @@ void Plater::export_amf()
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;
@ -6250,16 +6250,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();
@ -6689,6 +6679,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)
{

View file

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

View file

@ -636,7 +636,6 @@ PlaterPresetComboBox::~PlaterPresetComboBox()
static void run_wizard(ConfigWizard::StartPage sp)
{
if (wxGetApp().check_and_save_current_preset_changes())
wxGetApp().run_wizard(ConfigWizard::RR_USER, sp);
}

View file

@ -492,18 +492,16 @@ 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) {
// 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)"));

View file

@ -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<std::string> DiffViewCtrl::selected_options()
// UnsavedChangesDialog
//------------------------------------------
UnsavedChangesDialog::UnsavedChangesDialog(const wxString& header)
: DPIDialog(static_cast<wxWindow*>(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<wxWindow*>(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<wxString>& 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()));

View file

@ -263,7 +263,7 @@ class UnsavedChangesDialog : public DPIDialog
std::vector<std::pair<std::string, Preset::Type>> 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() {}
};

View file

@ -279,12 +279,30 @@ 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<stl_triangle_vertex_indices>& 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<float>::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<float>::max();
uint32_t wanted_count = 0;
its_quadric_edge_collapse(its, wanted_count, &max_error);
CHECK(!its.indices.empty());
}

View file

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

View file

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

View file

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