Fixed conflicts after merge with master

This commit is contained in:
enricoturri1966 2021-10-25 12:44:59 +02:00
commit fe6b288743
68 changed files with 1493 additions and 605 deletions

View File

@ -65,6 +65,7 @@ SET PS_DESTDIR=
CALL :RESOLVE_DESTDIR_CACHE
REM Set up parameters used by help menu
SET EXIT_STATUS=0
SET PS_CONFIG_DEFAULT=%PS_CONFIG%
SET PS_ARCH_HOST=%PS_ARCH%
(echo " -help /help -h /h -? /? ")| findstr /I /C:" %~1 ">nul && GOTO :HELP
@ -159,11 +160,14 @@ REM Build deps
:BUILD_DEPS
SET EXIT_STATUS=3
SET PS_CURRENT_STEP=deps
IF "%PS_STEPS_DIRTY%" EQU "" CALL :MAKE_OR_CLEAN_DIRECTORY deps\build "%PS_DEPS_PATH_FILE_NAME%"
IF "%PS_STEPS_DIRTY%" EQU "" CALL :MAKE_OR_CLEAN_DIRECTORY deps\build "%PS_DEPS_PATH_FILE_NAME%" .vs
cd deps\build || GOTO :END
cmake.exe .. -DDESTDIR="%PS_DESTDIR%" || GOTO :END
cmake.exe .. -DDESTDIR="%PS_DESTDIR%"
IF %ERRORLEVEL% NEQ 0 IF "%PS_STEPS_DIRTY%" NEQ "" (
(del CMakeCache.txt && cmake.exe .. -DDESTDIR="%PS_DESTDIR%") || GOTO :END
) ELSE GOTO :END
(echo %PS_DESTDIR%)> "%PS_DEPS_PATH_FILE%"
msbuild /m ALL_BUILD.vcxproj /p:Configuration=%PS_CONFIG% || GOTO :END
msbuild /m ALL_BUILD.vcxproj /p:Configuration=%PS_CONFIG% /v:quiet || GOTO :END
cd ..\..
IF /I "%PS_STEPS:~0,4%" EQU "deps" GOTO :RUN_APP
@ -171,7 +175,7 @@ REM Build app
:BUILD_APP
SET EXIT_STATUS=4
SET PS_CURRENT_STEP=app
IF "%PS_STEPS_DIRTY%" EQU "" CALL :MAKE_OR_CLEAN_DIRECTORY build "%PS_CUSTOM_RUN_FILE%"
IF "%PS_STEPS_DIRTY%" EQU "" CALL :MAKE_OR_CLEAN_DIRECTORY build "%PS_CUSTOM_RUN_FILE%" .vs
cd build || GOTO :END
REM Make sure we have a custom batch file skeleton for the run stage
set PS_CUSTOM_BAT=%PS_CUSTOM_RUN_FILE%
@ -181,9 +185,12 @@ SET PS_PROJECT_IS_OPEN=
FOR /F "tokens=2 delims=," %%I in (
'tasklist /V /FI "IMAGENAME eq devenv.exe " /NH /FO CSV ^| find "%PS_SOLUTION_NAME%"'
) do SET PS_PROJECT_IS_OPEN=%%~I
cmake.exe .. -DCMAKE_PREFIX_PATH="%PS_DESTDIR%\usr\local" -DCMAKE_CONFIGURATION_TYPES=%PS_CONFIG_LIST% || GOTO :END
cmake.exe .. -DCMAKE_PREFIX_PATH="%PS_DESTDIR%\usr\local" -DCMAKE_CONFIGURATION_TYPES=%PS_CONFIG_LIST%
IF %ERRORLEVEL% NEQ 0 IF "%PS_STEPS_DIRTY%" NEQ "" (
(del CMakeCache.txt && cmake.exe .. -DCMAKE_PREFIX_PATH="%PS_DESTDIR%\usr\local" -DCMAKE_CONFIGURATION_TYPES=%PS_CONFIG_LIST%) || GOTO :END
) ELSE GOTO :END
REM Skip the build step if we're using the undocumented app-cmake to regenerate the full config from inside devenv
IF "%PS_STEPS%" NEQ "app-cmake" msbuild /m ALL_BUILD.vcxproj /p:Configuration=%PS_CONFIG% || GOTO :END
IF "%PS_STEPS%" NEQ "app-cmake" msbuild /m ALL_BUILD.vcxproj /p:Configuration=%PS_CONFIG% /v:quiet || GOTO :END
(echo %PS_DESTDIR%)> "%PS_DEPS_PATH_FILE_FOR_CONFIG%"
REM Run app
@ -262,8 +269,11 @@ REM Functions and stubs start here.
:RESOLVE_DESTDIR_CACHE
@REM Resolves all DESTDIR cache values and sets PS_STEPS_DEFAULT
@REM Note: This just sets global variableq, so it doesn't use setlocal.
SET PS_DEPS_PATH_FILE_FOR_CONFIG=%~dp0build\%PS_ARCH%\%PS_CONFIG%\%PS_DEPS_PATH_FILE_NAME%
@REM Note: This just sets global variables, so it doesn't use setlocal.
SET PS_DEPS_PATH_FILE_FOR_CONFIG=%~dp0build\.vs\%PS_ARCH%\%PS_CONFIG%\%PS_DEPS_PATH_FILE_NAME%
mkdir "%~dp0build\.vs\%PS_ARCH%\%PS_CONFIG%" > nul 2> nul
REM Copy a legacy file if we don't have one in the proper location.
echo f|xcopy /D "%~dp0build\%PS_ARCH%\%PS_CONFIG%\%PS_DEPS_PATH_FILE_NAME%" "%PS_DEPS_PATH_FILE_FOR_CONFIG%"
CALL :CANONICALIZE_PATH PS_DEPS_PATH_FILE_FOR_CONFIG
IF EXIST "%PS_DEPS_PATH_FILE_FOR_CONFIG%" (
FOR /F "tokens=* USEBACKQ" %%I IN ("%PS_DEPS_PATH_FILE_FOR_CONFIG%") DO (

View File

@ -3,7 +3,7 @@
### Install the tools
Install Visual Studio Community 2019 from [visualstudio.microsoft.com/vs/](https://visualstudio.microsoft.com/vs/). Older versions are not supported as PrusaSlicer requires support for C++17.
Select all workload options for C++
Select all workload options for C++ and make sure to launch Visual Studio after install (to ensure that the full setup completes).
Install git for Windows from [gitforwindows.org](https://gitforwindows.org/)
Download and run the exe accepting all defaults
@ -17,6 +17,42 @@ c:> cd src
c:\src> git clone https://github.com/prusa3d/PrusaSlicer.git
```
### Run the automatic build script
The script `build_win.bat` will automatically find the default Visual Studio installation, set up the build environment, and then run both CMake and MSBuild to generate the dependencies and application as needed. If you'd rather do these steps manually, you can skip to the [Manual Build Instructions](#manual-build-instructions) in the next section. Otherwise, just run the following command to get everything going with the default configs:
```
c:\src>cd c:\src\PrusaSlicer
c:\src\PrusaSlicer>build_win.bat -d=..\PrusaSlicer-deps -r=console
```
The build script will run for a while (over an hour, depending on your machine) and automatically perform the following steps:
1. Configure and build [deps](#compile-the-dependencies) as RelWithDebInfo with `c:\src\PrusaSlicer-deps` as the destination directory
2. Configure and build all [application targets](#compile-prusaslicer) as RelWithDebInfo
3. Launch the resulting `prusa-slicer-console.exe` binary
You can change the above command line options to do things like:
* Change the destination for the dependencies by pointing `-d` to a different directory such as: `build_win.bat -d=s:\PrusaSlicerDeps`
* Open the solution in Visual Studio after the build completes by changing the `-r` switch to `-r=ide`
* Generate a release build without debug info by adding `-c=Release` or a full debug build with `-c=Debug`
* Perform an incremental application build (the default) with: `build_win.bat -s=app-dirty`
* Clean and rebuild the application: `build_win.bat -s=app`
* Clean and rebuild the dependencies: `build_win.bat -s=deps`
* Clean and rebuild everything (app and deps): `build_win.bat -s=all`
* _The full list of build script options can be listed by running:_ `build_win.bat -?`
### Troubleshooting
You're best off initiating builds from within Visual Studio for day-to-day development. However, the `build_win.bat` script can be very helpful if you run into build failures after updating your source tree. Here are some tips to keep in mind:
* The last several lines of output from `build_win.bat` will usually have the most helpful error messages.
* If CMake complains about missing binaries or paths (e.g. after updating Visual Studio), building with `build_win.bat` will force CMake to regenerate its cache on an error.
* After a deps change, you may just need to rebuild everything with the `-s=all` switch.
* Reading through the instructions in the next section may help diagnose more complex issues.
# Manual Build Instructions
_Follow the steps below if you want to understand how to perform a manual build, or if you're troubleshooting issues with the automatic build script._
### Compile the dependencies.
Dependencies are updated seldomly, thus they are compiled out of the PrusaSlicer source tree.
Go to the Windows Start Menu and Click on "Visual Studio 2019" folder, then select the ->"x64 Native Tools Command Prompt" to open a command window and run the following:

View File

@ -1,3 +1,5 @@
min_slic3r_version = 2.4.0-beta0
1.4.0-beta0 Added multiple Filatech and BASF filament profiles. Added material profiles for SL1S.
min_slic3r_version = 2.4.0-alpha0
1.4.0-alpha8 Added material profiles for Prusament Resin. Detect bridging perimeters enabled by default.
1.4.0-alpha7 Updated brim_separation value. Updated Prusa MINI end g-code. Added Filamentworld filament profiles.

View File

@ -5,7 +5,7 @@
name = Prusa Research
# Configuration version of this file. Config file will only be installed, if the config_version differs.
# This means, the server may force the PrusaSlicer configuration to be downgraded.
config_version = 1.4.0-alpha8
config_version = 1.4.0-beta0
# Where to get the updates from?
config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/
changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1%
@ -2715,6 +2715,275 @@ min_print_speed = 15
slowdown_below_layer_time = 10
cooling = 1
[filament:Filatech FilaFlex30]
inherits = Filatech FilaFlex40
temperature = 225
filament_density = 1.15
extrusion_multiplier = 1.1
filament_cost =
[filament:Filatech FilaFlex55]
inherits = Filatech FilaFlex40
temperature = 230
filament_density = 1.18
bed_temperature = 60
fan_always_on = 0
fan_below_layer_time = 60
filament_cost =
first_layer_temperature = 235
extrusion_multiplier = 1
# [filament:Filatech TPE]
# inherits = Filatech FilaFlex40
# first_layer_temperature = 230
# temperature = 225
# filament_density = 1.2
# fan_below_layer_time = 60
# max_fan_speed = 80
# min_fan_speed = 80
# fan_always_on = 1
[filament:Filatech TPU]
inherits = Filatech FilaFlex40
first_layer_temperature = 230
filament_density = 1.2
fan_below_layer_time = 60
max_fan_speed = 80
min_fan_speed = 80
fan_always_on = 1
temperature = 235
[filament:Filatech ABS]
inherits = *ABSC*
filament_vendor = Filatech
filament_cost =
extrusion_multiplier = 0.95
filament_density = 1.05
[filament:Filatech ABS @MINI]
inherits = Filatech ABS; *ABSMINI*
[filament:Filatech FilaCarbon]
inherits = *ABSC*
filament_vendor = Filatech
filament_cost =
extrusion_multiplier = 0.95
filament_density = 1.1
first_layer_bed_temperature = 105
bed_temperature = 100
compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
[filament:Filatech FilaCarbon @MINI]
inherits = Filatech FilaCarbon; *ABSMINI*
compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model=="MINI"
[filament:Filatech FilaPLA]
inherits = *PLA*
filament_vendor = Filatech
filament_cost =
filament_density = 1.3
first_layer_temperature = 235
first_layer_bed_temperature = 50
temperature = 230
bed_temperature = 55
[filament:Filatech PLA]
inherits = *PLA*
filament_vendor = Filatech
filament_cost =
filament_density = 1.25
first_layer_temperature = 215
temperature = 210
[filament:Filatech PLA+]
inherits = Filatech PLA
filament_density = 1.24
[filament:Filatech FilaTough]
inherits = Filatech ABS
filament_cost =
extrusion_multiplier = 0.95
filament_density = 1.29
first_layer_temperature = 245
first_layer_bed_temperature = 80
temperature = 240
bed_temperature = 90
cooling = 0
compatible_printers_condition = ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
[filament:Filatech HIPS]
inherits = Prusa HIPS
filament_vendor = Filatech
filament_cost =
filament_density = 1.07
filament_spool_weight =
first_layer_temperature = 230
first_layer_bed_temperature = 100
temperature = 225
bed_temperature = 110
compatible_printers_condition = printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
[filament:Filatech HIPS @MINI]
inherits = Filatech HIPS; *ABSMINI*
[filament:Filatech PA]
inherits = *ABSC*
filament_vendor = Filatech
filament_cost =
filament_density = 1.1
first_layer_temperature = 275
first_layer_bed_temperature = 110
temperature = 275
bed_temperature = 115
fan_always_on = 0
cooling = 0
bridge_fan_speed = 25
filament_type = NYLON
filament_max_volumetric_speed = 8
compatible_printers_condition = printer_notes!~/.*PRINTER_MODEL_MK(2|2.5).*/ and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
[filament:Filatech PA @MK2]
inherits = Filatech PA
first_layer_bed_temperature = 105
bed_temperature = 110
compatible_printers_condition = printer_notes=~/.*PRINTER_MODEL_MK(2|2.5).*/ and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
[filament:Filatech PA @MINI]
inherits = Filatech PA
first_layer_bed_temperature = 100
bed_temperature = 100
compatible_printers_condition = printer_model=="MINI"
[filament:Filatech PC]
inherits = Filatech PA
first_layer_bed_temperature = 110
bed_temperature = 115
filament_density = 1.2
filament_type = PC
[filament:Filatech PC @MK2]
inherits = Filatech PC
first_layer_bed_temperature = 105
bed_temperature = 110
compatible_printers_condition = printer_notes=~/.*PRINTER_MODEL_MK(2|2.5).*/ and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
[filament:Filatech PC-ABS]
inherits = Filatech PC
first_layer_temperature = 270
temperature = 270
first_layer_bed_temperature = 110
bed_temperature = 115
filament_density = 1.08
filament_type = PC
fan_always_on = 0
cooling = 1
extrusion_multiplier = 0.95
disable_fan_first_layers = 6
[filament:Filatech PC-ABS @MK2]
inherits = Filatech PC-ABS
first_layer_bed_temperature = 105
bed_temperature = 110
compatible_printers_condition = printer_notes=~/.*PRINTER_MODEL_MK(2|2.5).*/ and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
[filament:Filatech PETG]
inherits = *PET*
filament_vendor = Filatech
filament_cost =
filament_density = 1.27
first_layer_temperature = 240
first_layer_bed_temperature = 75
temperature = 245
bed_temperature = 80
extrusion_multiplier = 0.95
fan_always_on = 0
[filament:Filatech PETG @MINI]
inherits = Filatech PETG; *PETMINI*
[filament:Filatech Wood-PLA]
inherits = Filatech PLA
filament_cost =
filament_density = 1.05
first_layer_temperature = 210
compatible_printers_condition = nozzle_diameter[0]>=0.4 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
[filament:Ultrafuse PET]
inherits = *PET*
filament_vendor = BASF
filament_cost =
filament_density = 1.33
first_layer_temperature = 220
first_layer_bed_temperature = 70
temperature = 215
bed_temperature = 70
fan_below_layer_time = 10
min_fan_speed = 75
max_fan_speed = 100
bridge_fan_speed = 100
filament_type = PET
disable_fan_first_layers = 1
full_fan_speed_layer = 3
filament_retract_lift = 0
# filament_retract_length = 3
# filament_max_volumetric_speed = 7
[filament:Ultrafuse PET @MINI]
inherits = Ultrafuse PET; *PETMINI*
[filament:Ultrafuse PRO1]
inherits = Prusament PLA
filament_vendor = BASF
filament_cost =
filament_density = 1.25
filament_spool_weight = 0
filament_colour = #FFFFFF
# filament overrides
# filament_retract_length = 2
# filament_retract_speed = 40
# filament_retract_before_travel = 2
# filament_wipe = 0
[filament:Ultrafuse ABS]
inherits = *ABSC*
filament_vendor = BASF
filament_cost =
filament_density = 1.04
min_fan_speed = 10
max_fan_speed = 20
bed_temperature = 100
disable_fan_first_layers = 3
filament_colour = #FFFFFF
# filament overrides
# filament_retract_length = 2
# filament_retract_speed = 40
# filament_retract_before_travel = 2
# filament_wipe = 0
[filament:Ultrafuse ABS @MINI]
inherits = Ultrafuse ABS; *ABSMINI*
[filament:Ultrafuse 17-4 PH]
inherits = *ABSC*
filament_vendor = BASF
filament_cost =
filament_density = 4.5
extrusion_multiplier = 1.08
first_layer_temperature = 250
first_layer_bed_temperature = 100
temperature = 250
bed_temperature = 100
min_fan_speed = 0
max_fan_speed = 0
bridge_fan_speed = 0
cooling = 0
fan_always_on = 0
filament_max_volumetric_speed = 4
filament_type = METAL
compatible_printers_condition = nozzle_diameter[0]>=0.4 and printer_model!="MINI" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material)
start_filament_gcode = "M900 K0"
filament_colour = #FFFFFF
[filament:Polymaker PC-Max]
inherits = *ABS*
filament_vendor = Polymaker
@ -5712,6 +5981,83 @@ initial_exposure_time = 25
material_type = Tough
material_vendor = Made for Prusa
[sla_material:Prusa Vibrant Orange Tough @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 4
initial_exposure_time = 25
material_type = Tough
material_vendor = Made for Prusa
[sla_material:Prusa Deep Blue Transparent Tough @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 4
initial_exposure_time = 25
material_type = Tough
material_vendor = Made for Prusa
[sla_material:Prusa Green Dental Casting @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 3
initial_exposure_time = 50
material_type = Casting
material_vendor = Made for Prusa
[sla_material:PrimaCreator Tough Light Grey @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 1.8
initial_exposure_time = 25
material_type = Tough
material_vendor = PrimaCreator
[sla_material:PrimaCreator Tough Clear @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 1.8
initial_exposure_time = 25
material_type = Tough
material_vendor = PrimaCreator
[sla_material:PrimaCreator Tough White @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 1.8
initial_exposure_time = 25
material_type = Tough
material_vendor = PrimaCreator
[sla_material:PrimaCreator Flex Clear @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 1.8
initial_exposure_time = 25
material_type = Flexible
material_vendor = PrimaCreator
[sla_material:PrimaCreator Water Washable Transparent @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 1.8
initial_exposure_time = 25
material_type = Tough
material_vendor = PrimaCreator
[sla_material:DruckWege Type D Dental Model @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 1.2
initial_exposure_time = 15
material_type = Dental
material_vendor = DruckWege
[sla_material:DruckWege Type D Standard White @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 1.6
initial_exposure_time = 15
material_type = Tough
material_vendor = DruckWege
[sla_material:DruckWege Type D Standard Pigmentfrei Clear @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 1.8
initial_exposure_time = 15
material_type = Tough
material_vendor = DruckWege
[sla_material:3DM-ABS Orange @0.025 SL1S]
inherits = *0.025_sl1s*
exposure_time = 1.8
@ -5851,6 +6197,83 @@ initial_exposure_time = 25
material_type = Tough
material_vendor = Made for Prusa
[sla_material:Prusa Vibrant Orange Tough @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 5
initial_exposure_time = 25
material_type = Tough
material_vendor = Made for Prusa
[sla_material:Prusa Deep Blue Transparent Tough @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 5
initial_exposure_time = 25
material_type = Tough
material_vendor = Made for Prusa
[sla_material:Prusa Green Dental Casting @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 4
initial_exposure_time = 50
material_type = Casting
material_vendor = Made for Prusa
[sla_material:PrimaCreator Tough Light Grey @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 2.4
initial_exposure_time = 25
material_type = Tough
material_vendor = PrimaCreator
[sla_material:PrimaCreator Tough Clear @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 2
initial_exposure_time = 25
material_type = Tough
material_vendor = PrimaCreator
[sla_material:PrimaCreator Tough White @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 2
initial_exposure_time = 25
material_type = Tough
material_vendor = PrimaCreator
[sla_material:PrimaCreator Flex Clear @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 2
initial_exposure_time = 25
material_type = Flexible
material_vendor = PrimaCreator
[sla_material:PrimaCreator Water Washable Transparent @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 2
initial_exposure_time = 25
material_type = Tough
material_vendor = PrimaCreator
[sla_material:DruckWege Type D Dental Model @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 1.4
initial_exposure_time = 15
material_type = Dental
material_vendor = DruckWege
[sla_material:DruckWege Type D Standard White @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 2
initial_exposure_time = 15
material_type = Tough
material_vendor = DruckWege
[sla_material:DruckWege Type D Standard Pigmentfrei Clear @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 2
initial_exposure_time = 15
material_type = Tough
material_vendor = DruckWege
[sla_material:3DM-ABS Orange @0.05 SL1S]
inherits = *0.05_sl1s*
exposure_time = 2.6
@ -5990,6 +6413,69 @@ initial_exposure_time = 25
material_type = Tough
material_vendor = Made for Prusa
[sla_material:Prusa Vibrant Orange Tough @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 10
initial_exposure_time = 25
material_type = Tough
material_vendor = Made for Prusa
[sla_material:Prusa Deep Blue Transparent Tough @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 10
initial_exposure_time = 25
material_type = Tough
material_vendor = Made for Prusa
[sla_material:Prusa Green Dental Casting @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 8
initial_exposure_time = 50
material_type = Casting
material_vendor = Made for Prusa
[sla_material:PrimaCreator Tough Light Grey @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 3
initial_exposure_time = 25
material_type = Tough
material_vendor = PrimaCreator
[sla_material:PrimaCreator Tough Clear @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 2.6
initial_exposure_time = 25
material_type = Tough
material_vendor = PrimaCreator
[sla_material:PrimaCreator Tough White @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 2.6
initial_exposure_time = 25
material_type = Tough
material_vendor = PrimaCreator
[sla_material:PrimaCreator Flex Clear @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 2.6
initial_exposure_time = 25
material_type = Flexible
material_vendor = PrimaCreator
[sla_material:PrimaCreator Water Washable Transparent @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 2.6
initial_exposure_time = 25
material_type = Tough
material_vendor = PrimaCreator
[sla_material:DruckWege Type D Dental Model @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 2.6
initial_exposure_time = 15
material_type = Dental
material_vendor = DruckWege
[sla_material:3DM-ABS Orange @0.1 SL1S]
inherits = *0.1_sl1s*
exposure_time = 3

View File

@ -22,6 +22,8 @@ uniform vec4 uniform_color;
varying vec3 clipping_planes_dots;
varying vec4 model_pos;
uniform bool volume_mirrored;
void main()
{
if (any(lessThan(clipping_planes_dots, ZERO)))
@ -34,6 +36,9 @@ void main()
triangle_normal = -triangle_normal;
#endif
if (volume_mirrored)
triangle_normal = -triangle_normal;
// First transform the normal into camera space and normalize the result.
vec3 eye_normal = normalize(gl_NormalMatrix * triangle_normal);

View File

@ -705,7 +705,7 @@ bool CLI::setup(int argc, char **argv)
// Initialize with defaults.
for (const t_optiondef_map *options : { &cli_actions_config_def.options, &cli_transform_config_def.options, &cli_misc_config_def.options })
for (const std::pair<t_config_option_key, ConfigOptionDef> &optdef : *options)
for (const t_optiondef_map::value_type &optdef : *options)
m_config.option(optdef.first, true);
set_data_dir(m_config.opt_string("datadir"));

View File

@ -577,7 +577,7 @@ private:
template<class Level>
Shapes calcnfp(const Item &trsh, Level)
Shapes calcnfp(const Item &/*trsh*/, Level)
{ // Function for arbitrary level of nfp implementation
// TODO: implement

View File

@ -33,7 +33,8 @@ public:
PackResult(Item& item):
item_ptr_(&item),
move_(item.translation()),
rot_(item.rotation()) {}
rot_(item.rotation()),
overfit_(1.0) {}
PackResult(double overfit = 1.0):
item_ptr_(nullptr), overfit_(overfit) {}

View File

@ -114,6 +114,7 @@ static ConstPrintObjectPtrs get_top_level_objects_with_brim(const Print &print,
clipper.AddPaths(islands_clip, ClipperLib_Z::ptSubject, true);
// Execute union operation to construct polytree
ClipperLib_Z::PolyTree islands_polytree;
//FIXME likely pftNonZero or ptfPositive would be better. Why are we using ptfEvenOdd for Unions?
clipper.Execute(ClipperLib_Z::ctUnion, islands_polytree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
std::unordered_set<size_t> processed_objects_idx;
@ -486,7 +487,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
clipper.AddPaths(input_subject, ClipperLib_Z::ptSubject, true);
clipper.AddPaths(input_clip, ClipperLib_Z::ptClip, true);
// perform operation
clipper.Execute(ClipperLib_Z::ctDifference, trimming, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
clipper.Execute(ClipperLib_Z::ctDifference, trimming, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
}
// Second, trim the extrusion loops with the trimming regions.
@ -515,7 +516,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
clipper.AddPaths(trimming, ClipperLib_Z::ptClip, true);
// perform operation
ClipperLib_Z::PolyTree loops_trimmed_tree;
clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(loops_trimmed_tree, loops_trimmed);
}

View File

@ -193,12 +193,8 @@ bool ExtrusionLoop::split_at_vertex(const Point &point)
return false;
}
// Splitting an extrusion loop, possibly made of multiple segments, some of the segments may be bridging.
void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
std::pair<size_t, Point> ExtrusionLoop::get_closest_path_and_point(const Point& point, bool prefer_non_overhang) const
{
if (this->paths.empty())
return;
// Find the closest path and closest point belonging to that path. Avoid overhangs, if asked for.
size_t path_idx = 0;
Point p;
@ -207,15 +203,15 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
Point p_non_overhang;
size_t path_idx_non_overhang = 0;
double min_non_overhang = std::numeric_limits<double>::max();
for (const ExtrusionPath &path : this->paths) {
for (const ExtrusionPath& path : this->paths) {
Point p_tmp = point.projection_onto(path.polyline);
double dist = (p_tmp - point).cast<double>().norm();
if (dist < min) {
p = p_tmp;
min = dist;
path_idx = &path - &this->paths.front();
}
if (prefer_non_overhang && ! is_bridge(path.role()) && dist < min_non_overhang) {
}
if (prefer_non_overhang && !is_bridge(path.role()) && dist < min_non_overhang) {
p_non_overhang = p_tmp;
min_non_overhang = dist;
path_idx_non_overhang = &path - &this->paths.front();
@ -224,9 +220,19 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
if (prefer_non_overhang && min_non_overhang != std::numeric_limits<double>::max()) {
// Only apply the non-overhang point if there is one.
path_idx = path_idx_non_overhang;
p = p_non_overhang;
p = p_non_overhang;
}
}
return std::make_pair(path_idx, p);
}
// Splitting an extrusion loop, possibly made of multiple segments, some of the segments may be bridging.
void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
{
if (this->paths.empty())
return;
auto [path_idx, p] = get_closest_path_and_point(point, prefer_non_overhang);
// now split path_idx in two parts
const ExtrusionPath &path = this->paths[path_idx];

View File

@ -258,6 +258,7 @@ public:
double length() const override;
bool split_at_vertex(const Point &point);
void split_at(const Point &point, bool prefer_non_overhang);
std::pair<size_t, Point> get_closest_path_and_point(const Point& point, bool prefer_non_overhang) const;
void clip_end(double distance, ExtrusionPaths* paths) const;
// Test, whether the point is extruded by a bridging flow.
// This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead.

View File

@ -916,7 +916,12 @@ namespace Slic3r {
}
//FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment.
// Each config line is prefixed with a semicolon (G-code comment), that is ugly.
config_substitutions.substitutions = config.load_from_ini_string_commented(std::move(buffer), config_substitutions.rule);
// Replacing the legacy function with load_from_ini_string_commented leads to issues when
// parsing 3MFs from before PrusaSlicer 2.0.0 (which can have duplicated entries in the INI.
// See https://github.com/prusa3d/PrusaSlicer/issues/7155. We'll revert it for now.
//config_substitutions.substitutions = config.load_from_ini_string_commented(std::move(buffer), config_substitutions.rule);
ConfigBase::load_from_gcode_string_legacy(config, buffer.data(), config_substitutions);
}
}

View File

@ -711,7 +711,12 @@ void AMFParserContext::endElement(const char * /* name */)
if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) {
//FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment.
// Each config line is prefixed with a semicolon (G-code comment), that is ugly.
m_config_substitutions->substitutions = m_config->load_from_ini_string_commented(std::move(m_value[1].c_str()), m_config_substitutions->rule);
// Replacing the legacy function with load_from_ini_string_commented leads to issues when
// parsing 3MFs from before PrusaSlicer 2.0.0 (which can have duplicated entries in the INI.
// See https://github.com/prusa3d/PrusaSlicer/issues/7155. We'll revert it for now.
//m_config_substitutions->substitutions = m_config->load_from_ini_string_commented(std::move(m_value[1].c_str()), m_config_substitutions->rule);
ConfigBase::load_from_gcode_string_legacy(*m_config, std::move(m_value[1].c_str()), *m_config_substitutions);
}
else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) {
const char *opt_key = m_value[0].c_str() + 7;

View File

@ -2479,57 +2479,49 @@ std::string GCode::change_layer(coordf_t print_z)
static std::unique_ptr<EdgeGrid::Grid> calculate_layer_edge_grid(const Layer& layer)
{
auto out = make_unique<EdgeGrid::Grid>();
// Create the distance field for a layer below.
const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5);
out->create(layer.lslices, distance_field_resolution);
out->calculate_sdf();
#if 0
{
static int iRun = 0;
BoundingBox bbox = (*lower_layer_edge_grid)->bbox();
bbox.min(0) -= scale_(5.f);
bbox.min(1) -= scale_(5.f);
bbox.max(0) += scale_(5.f);
bbox.max(1) += scale_(5.f);
EdgeGrid::save_png(*(*lower_layer_edge_grid), bbox, scale_(0.1f), debug_out_path("GCode_extrude_loop_edge_grid-%d.png", iRun++));
}
#endif
return out;
}
std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid)
{
// get a copy; don't modify the orientation of the original loop object otherwise
// next copies (if any) would not detect the correct orientation
if (m_layer->lower_layer != nullptr && lower_layer_edge_grid != nullptr) {
if (! *lower_layer_edge_grid) {
// Create the distance field for a layer below.
const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5);
*lower_layer_edge_grid = make_unique<EdgeGrid::Grid>();
(*lower_layer_edge_grid)->create(m_layer->lower_layer->lslices, distance_field_resolution);
(*lower_layer_edge_grid)->calculate_sdf();
#if 0
{
static int iRun = 0;
BoundingBox bbox = (*lower_layer_edge_grid)->bbox();
bbox.min(0) -= scale_(5.f);
bbox.min(1) -= scale_(5.f);
bbox.max(0) += scale_(5.f);
bbox.max(1) += scale_(5.f);
EdgeGrid::save_png(*(*lower_layer_edge_grid), bbox, scale_(0.1f), debug_out_path("GCode_extrude_loop_edge_grid-%d.png", iRun++));
}
#endif
}
}
if (m_layer->lower_layer && lower_layer_edge_grid != nullptr && ! *lower_layer_edge_grid)
*lower_layer_edge_grid = calculate_layer_edge_grid(*m_layer->lower_layer);
// extrude all loops ccw
bool was_clockwise = loop.make_counter_clockwise();
SeamPosition seam_position = m_config.seam_position;
if (loop.loop_role() == elrSkirt)
seam_position = spNearest;
// find the point of the loop that is closest to the current extruder position
// or randomize if requested
Point last_pos = this->last_pos();
if (m_config.spiral_vase) {
loop.split_at(last_pos, false);
} else {
const EdgeGrid::Grid* edge_grid_ptr = (lower_layer_edge_grid && *lower_layer_edge_grid)
? lower_layer_edge_grid->get()
: nullptr;
Point seam = m_seam_placer.get_seam(*m_layer, seam_position, loop,
last_pos, EXTRUDER_CONFIG(nozzle_diameter),
(m_layer == NULL ? nullptr : m_layer->object()),
was_clockwise, edge_grid_ptr);
// Split the loop at the point with a minium penalty.
if (!loop.split_at_vertex(seam))
// The point is not in the original loop. Insert it.
loop.split_at(seam, true);
}
else
m_seam_placer.place_seam(loop, this->last_pos(), m_config.external_perimeters_first,
EXTRUDER_CONFIG(nozzle_diameter), lower_layer_edge_grid ? lower_layer_edge_grid->get() : nullptr);
// clip the path to avoid the extruder to get exactly on the first point of the loop;
// if polyline was shorter than the clipping distance we'd get a null polyline, so
@ -2652,7 +2644,17 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vector<Obje
for (const ObjectByExtruder::Island::Region &region : by_region)
if (! region.perimeters.empty()) {
m_config.apply(print.get_print_region(&region - &by_region.front()).config());
for (const ExtrusionEntity *ee : region.perimeters)
// plan_perimeters tries to place seams, it needs to have the lower_layer_edge_grid calculated already.
if (m_layer->lower_layer && ! lower_layer_edge_grid)
lower_layer_edge_grid = calculate_layer_edge_grid(*m_layer->lower_layer);
m_seam_placer.plan_perimeters(std::vector<const ExtrusionEntity*>(region.perimeters.begin(), region.perimeters.end()),
*m_layer, m_config.seam_position, this->last_pos(), EXTRUDER_CONFIG(nozzle_diameter),
(m_layer == NULL ? nullptr : m_layer->object()),
(lower_layer_edge_grid ? lower_layer_edge_grid.get() : nullptr));
for (const ExtrusionEntity* ee : region.perimeters)
gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid);
}
return gcode;
@ -2807,7 +2809,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
gcode += this->unretract();
// adjust acceleration
{
if (m_config.default_acceleration.value > 0) {
double acceleration;
if (this->on_first_layer() && m_config.first_layer_acceleration.value > 0) {
acceleration = m_config.first_layer_acceleration.value;

View File

@ -208,9 +208,7 @@ void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time)
if (!enabled)
return;
time += additional_time;
gcode_time.cache += additional_time;
calculate_time();
calculate_time(0, additional_time);
}
static void planner_forward_pass_kernel(GCodeProcessor::TimeBlock& prev, GCodeProcessor::TimeBlock& curr)
@ -283,7 +281,7 @@ static void recalculate_trapezoids(std::vector<GCodeProcessor::TimeBlock>& block
}
}
void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks)
void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, float additional_time)
{
if (!enabled || blocks.size() < 2)
return;
@ -305,6 +303,9 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks)
for (size_t i = 0; i < n_blocks_process; ++i) {
const TimeBlock& block = blocks[i];
float block_time = block.time();
if (i == 0)
block_time += additional_time;
time += block_time;
#if ENABLE_TRAVEL_TIME
if (block.move_type == EMoveType::Travel)
@ -317,16 +318,14 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks)
#if !ENABLE_TRAVEL_TIME
roles_time[static_cast<size_t>(block.role)] += block_time;
#endif // !ENABLE_TRAVEL_TIME
if (block.layer_id > 0) {
if (block.layer_id >= layers_time.size()) {
size_t curr_size = layers_time.size();
layers_time.resize(block.layer_id);
for (size_t i = curr_size; i < layers_time.size(); ++i) {
layers_time[i] = 0.0f;
}
if (block.layer_id >= layers_time.size()) {
const size_t curr_size = layers_time.size();
layers_time.resize(block.layer_id);
for (size_t i = curr_size; i < layers_time.size(); ++i) {
layers_time[i] = 0.0f;
}
layers_time[block.layer_id - 1] += block_time;
}
layers_time[block.layer_id - 1] += block_time;
g1_times_cache.push_back({ block.g1_line_id, time });
// update times for remaining time to printer stop placeholders
auto it_stop_time = std::lower_bound(stop_times.begin(), stop_times.end(), block.g1_line_id,
@ -2565,7 +2564,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
block.role = m_extrusion_role;
block.distance = distance;
block.g1_line_id = m_g1_line_id;
block.layer_id = m_layer_id;
block.layer_id = std::max<unsigned int>(1, m_layer_id);
// calculates block cruise feedrate
float min_feedrate_factor = 1.0f;

View File

@ -280,7 +280,7 @@ namespace Slic3r {
// Simulates firmware st_synchronize() call
void simulate_st_synchronize(float additional_time = 0.0f);
void calculate_time(size_t keep_last_n_blocks = 0);
void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f);
};
struct TimeProcessor

View File

@ -292,11 +292,165 @@ void SeamPlacer::init(const Print& print)
Point SeamPlacer::get_seam(const Layer& layer, const SeamPosition seam_position,
const ExtrusionLoop& loop, Point last_pos, coordf_t nozzle_dmr,
const PrintObject* po, bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid)
void SeamPlacer::plan_perimeters(const std::vector<const ExtrusionEntity*> perimeters,
const Layer& layer, SeamPosition seam_position,
Point last_pos, coordf_t nozzle_dmr, const PrintObject* po,
const EdgeGrid::Grid* lower_layer_edge_grid)
{
// When printing the perimeters, we want the seams on external and internal perimeters to match.
// We have a list of perimeters in the order to be printed. Each internal perimeter must inherit
// the seam from the previous external perimeter.
m_plan.clear();
m_plan_idx = 0;
if (perimeters.empty() || ! po)
return;
m_plan.resize(perimeters.size());
for (int i = 0; i < int(perimeters.size()); ++i) {
if (perimeters[i]->role() == erExternalPerimeter && perimeters[i]->is_loop()) {
last_pos = this->calculate_seam(
layer, seam_position, *dynamic_cast<const ExtrusionLoop*>(perimeters[i]), nozzle_dmr,
po, lower_layer_edge_grid, last_pos);
m_plan[i].external = true;
m_plan[i].seam_position = seam_position;
m_plan[i].layer = &layer;
m_plan[i].po = po;
}
m_plan[i].pt = last_pos;
}
}
void SeamPlacer::place_seam(ExtrusionLoop& loop, const Point& last_pos, bool external_first, double nozzle_diameter,
const EdgeGrid::Grid* lower_layer_edge_grid)
{
const double seam_offset = nozzle_diameter;
Point seam = last_pos;
if (! m_plan.empty() && m_plan_idx < m_plan.size()) {
if (m_plan[m_plan_idx].external) {
seam = m_plan[m_plan_idx].pt;
// One more heuristics: if the seam is too far from current nozzle position,
// try to place it again. This can happen in cases where the external perimeter
// does not belong to the preceding ones and they are ordered so they end up
// far from each other.
if ((seam.cast<double>() - last_pos.cast<double>()).squaredNorm() > std::pow(scale_(5.*nozzle_diameter), 2.))
seam = this->calculate_seam(*m_plan[m_plan_idx].layer, m_plan[m_plan_idx].seam_position, loop, nozzle_diameter,
m_plan[m_plan_idx].po, lower_layer_edge_grid, last_pos);
}
else if (! external_first) {
// Internal perimeter printed before the external.
// First get list of external seams.
std::vector<size_t> ext_seams;
for (size_t i = 0; i < m_plan.size(); ++i) {
if (m_plan[i].external)
ext_seams.emplace_back(i);
}
if (! ext_seams.empty()) {
// First find the line segment closest to an external seam:
int path_idx = 0;
int line_idx = 0;
size_t ext_seam_idx = size_t(-1);
double min_dist_sqr = std::numeric_limits<double>::max();
std::vector<Lines> lines_vect;
for (int i = 0; i < int(loop.paths.size()); ++i) {
lines_vect.emplace_back(loop.paths[i].polyline.lines());
const Lines& lines = lines_vect.back();
for (int j = 0; j < int(lines.size()); ++j) {
for (size_t k : ext_seams) {
double d_sqr = lines[j].distance_to_squared(m_plan[k].pt);
if (d_sqr < min_dist_sqr) {
path_idx = i;
line_idx = j;
ext_seam_idx = k;
min_dist_sqr = d_sqr;
}
}
}
}
// Only accept seam that is reasonably close.
double limit_dist_sqr = std::pow(double(scale_((ext_seam_idx - m_plan_idx) * nozzle_diameter * 2.)), 2.);
if (ext_seam_idx != size_t(-1) && min_dist_sqr < limit_dist_sqr) {
// Now find a projection of the external seam
const Lines& lines = lines_vect[path_idx];
Point closest = m_plan[ext_seam_idx].pt.projection_onto(lines[line_idx]);
double dist = (closest.cast<double>() - lines[line_idx].b.cast<double>()).norm();
// And walk along the perimeter until we make enough space for
// seams of all perimeters beforethe external one.
double offset = (ext_seam_idx - m_plan_idx) * scale_(seam_offset);
double last_offset = offset;
offset -= dist;
const Point* a = &closest;
const Point* b = &lines[line_idx].b;
while (++line_idx < int(lines.size()) && offset > 0.) {
last_offset = offset;
offset -= lines[line_idx].length();
a = &lines[line_idx].a;
b = &lines[line_idx].b;
}
// We have walked far enough, too far maybe. Interpolate on the
// last segment to find the end precisely.
offset = std::min(0., offset); // In case that offset is still positive (we may have "wrapped around")
double ratio = last_offset / (last_offset - offset);
seam = (a->cast<double>() + ((b->cast<double>() - a->cast<double>()) * ratio)).cast<coord_t>();
}
}
}
else {
// We should have a candidate ready from before. If not, use last_pos.
if (m_plan_idx > 0 && m_plan[m_plan_idx - 1].precalculated)
seam = m_plan[m_plan_idx - 1].pt;
}
}
// Split the loop at the point with a minium penalty.
if (!loop.split_at_vertex(seam))
// The point is not in the original loop. Insert it.
loop.split_at(seam, true);
if (external_first && m_plan_idx+1<m_plan.size() && ! m_plan[m_plan_idx+1].external) {
// Next perimeter should start near this one.
const double dist_sqr = std::pow(double(scale_(seam_offset)), 2.);
double running_sqr = 0.;
double running_sqr_last = 0.;
if (!loop.paths.empty() && loop.paths.back().polyline.points.size() > 1) {
const ExtrusionPath& last = loop.paths.back();
auto it = last.polyline.points.crbegin() + 1;
for (; it != last.polyline.points.crend(); ++it) {
running_sqr += (it->cast<double>() - (it - 1)->cast<double>()).squaredNorm();
if (running_sqr > dist_sqr)
break;
running_sqr_last = running_sqr;
}
if (running_sqr <= dist_sqr)
it = last.polyline.points.crend() - 1;
// Now interpolate.
double ratio = (std::sqrt(dist_sqr) - std::sqrt(running_sqr_last)) / (std::sqrt(running_sqr) - std::sqrt(running_sqr_last));
m_plan[m_plan_idx + 1].pt = ((it - 1)->cast<double>() + (it->cast<double>() - (it - 1)->cast<double>()) * std::min(ratio, 1.)).cast<coord_t>();
m_plan[m_plan_idx + 1].precalculated = true;
}
}
++m_plan_idx;
}
// Returns a seam for an EXTERNAL perimeter.
Point SeamPlacer::calculate_seam(const Layer& layer, const SeamPosition seam_position,
const ExtrusionLoop& loop, coordf_t nozzle_dmr, const PrintObject* po,
const EdgeGrid::Grid* lower_layer_edge_grid, Point last_pos)
{
assert(loop.role() == erExternalPerimeter);
Polygon polygon = loop.polygon();
bool was_clockwise = polygon.make_counter_clockwise();
BoundingBox polygon_bb = polygon.bounding_box();
const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
@ -438,7 +592,7 @@ Point SeamPlacer::get_seam(const Layer& layer, const SeamPosition seam_position,
}
}
if (seam_position == spAligned && loop.role() == erExternalPerimeter)
if (seam_position == spAligned)
m_seam_history.add_seam(po, polygon.points[idx_min], polygon_bb);
@ -466,42 +620,8 @@ Point SeamPlacer::get_seam(const Layer& layer, const SeamPosition seam_position,
#endif
return polygon.points[idx_min];
} else { // spRandom
if (po->print()->default_region_config().external_perimeters_first) {
if (loop.role() == erExternalPerimeter)
last_pos = this->get_random_seam(layer_idx, polygon, po_idx);
else {
// Internal perimeters will just use last_pos.
}
} else {
if (loop.loop_role() == elrContourInternalPerimeter && loop.role() != erExternalPerimeter) {
// This loop does not contain any other loop. Set a random position.
// The other loops will get a seam close to the random point chosen
// on the innermost contour.
last_pos = this->get_random_seam(layer_idx, polygon, po_idx);
m_last_loop_was_external = false;
}
if (loop.role() == erExternalPerimeter) {
if (m_last_loop_was_external) {
// There was no internal perimeter before this one.
last_pos = this->get_random_seam(layer_idx, polygon, po_idx);
} else {
if (is_custom_seam_on_layer(layer_idx, po_idx)) {
// There is a possibility that the loop will be influenced by custom
// seam enforcer/blocker. In this case do not inherit the seam
// from internal loops (which may conflict with the custom selection
// and generate another random one.
bool saw_custom = false;
Point candidate = this->get_random_seam(layer_idx, polygon, po_idx, &saw_custom);
if (saw_custom)
last_pos = candidate;
}
}
m_last_loop_was_external = true;
}
}
return last_pos;
}
} else
return this->get_random_seam(layer_idx, polygon, po_idx);
}

View File

@ -2,7 +2,9 @@
#define libslic3r_SeamPlacer_hpp_
#include <optional>
#include <vector>
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/BoundingBox.hpp"
@ -41,16 +43,30 @@ class SeamPlacer {
public:
void init(const Print& print);
Point get_seam(const Layer& layer, const SeamPosition seam_position,
const ExtrusionLoop& loop, Point last_pos,
coordf_t nozzle_diameter, const PrintObject* po,
bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid);
// When perimeters are printed, first call this function with the respective
// external perimeter. SeamPlacer will find a location for its seam and remember it.
// Subsequent calls to get_seam will return this position.
void plan_perimeters(const std::vector<const ExtrusionEntity*> perimeters,
const Layer& layer, SeamPosition seam_position,
Point last_pos, coordf_t nozzle_dmr, const PrintObject* po,
const EdgeGrid::Grid* lower_layer_edge_grid);
void place_seam(ExtrusionLoop& loop, const Point& last_pos, bool external_first, double nozzle_diameter,
const EdgeGrid::Grid* lower_layer_edge_grid);
using TreeType = AABBTreeIndirect::Tree<2, coord_t>;
using AlignedBoxType = Eigen::AlignedBox<TreeType::CoordType, TreeType::NumDimensions>;
private:
// When given an external perimeter (!), returns the seam.
Point calculate_seam(const Layer& layer, const SeamPosition seam_position,
const ExtrusionLoop& loop, coordf_t nozzle_dmr, const PrintObject* po,
const EdgeGrid::Grid* lower_layer_edge_grid, Point last_pos);
struct CustomTrianglesPerLayer {
Polygons polys;
TreeType tree;
@ -61,7 +77,16 @@ private:
coordf_t m_last_print_z = -1.;
const PrintObject* m_last_po = nullptr;
bool m_last_loop_was_external = true;
struct SeamPoint {
Point pt;
bool precalculated = false;
bool external = false;
const Layer* layer = nullptr;
SeamPosition seam_position;
const PrintObject* po = nullptr;
};
std::vector<SeamPoint> m_plan;
size_t m_plan_idx;
std::vector<std::vector<CustomTrianglesPerLayer>> m_enforcers;
std::vector<std::vector<CustomTrianglesPerLayer>> m_blockers;

View File

@ -1671,7 +1671,7 @@ void visit_antipodals (Idx& ia, Idx &ib, Fn &&fn)
} // namespace rotcalip
bool intersects(const Polygon &A, const Polygon &B)
bool convex_polygons_intersect(const Polygon &A, const Polygon &B)
{
using namespace rotcalip;

View File

@ -563,7 +563,7 @@ inline bool is_rotation_ninety_degrees(const Vec3d &rotation)
// Returns true if the intersection of the two convex polygons A and B
// is not an empty set.
bool intersects(const Polygon &A, const Polygon &B);
bool convex_polygons_intersect(const Polygon &A, const Polygon &B);
} } // namespace Slicer::Geometry

View File

@ -63,11 +63,25 @@ bool Line::parallel_to(double angle) const
return Slic3r::Geometry::directions_parallel(this->direction(), angle);
}
bool Line::parallel_to(const Line& line) const
{
const Vec2d v1 = (this->b - this->a).cast<double>();
const Vec2d v2 = (line.b - line.a).cast<double>();
return sqr(cross2(v1, v2)) < sqr(EPSILON) * v1.squaredNorm() * v2.squaredNorm();
}
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
bool Line::perpendicular_to(double angle) const
{
return Slic3r::Geometry::directions_perpendicular(this->direction(), angle);
}
bool Line::perpendicular_to(const Line& line) const
{
const Vec2d v1 = (this->b - this->a).cast<double>();
const Vec2d v2 = (line.b - line.a).cast<double>();
return sqr(v1.dot(v2)) < sqr(EPSILON) * v1.squaredNorm() * v2.squaredNorm();
}
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
bool Line::intersection(const Line &l2, Point *intersection) const

View File

@ -104,10 +104,10 @@ public:
double distance_to(const Point &point) const { return distance_to(point, this->a, this->b); }
double perp_distance_to(const Point &point) const;
bool parallel_to(double angle) const;
bool parallel_to(const Line &line) const { return this->parallel_to(line.direction()); }
bool parallel_to(const Line& line) const;
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
bool perpendicular_to(double angle) const;
bool perpendicular_to(const Line& line) const { return this->perpendicular_to(line.direction()); }
bool perpendicular_to(const Line& line) const;
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
double atan2_() const { return atan2(this->b(1) - this->a(1), this->b(0) - this->a(0)); }
double orientation() const;

View File

@ -27,9 +27,10 @@
namespace Slic3r {
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
// Using rotating callipers to check for collision of two convex polygons. Thus both printbed_shape and obj_hull_2d are convex polygons.
ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const Polygon& obj_hull_2d, double obj_min_z, double obj_max_z)
{
if (!Geometry::intersects(printbed_shape, obj_hull_2d))
if (!Geometry::convex_polygons_intersect(printbed_shape, obj_hull_2d))
return ModelInstancePVS_Fully_Outside;
bool contained_xy = true;
@ -43,6 +44,7 @@ ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_
return (contained_xy && contained_z) ? ModelInstancePVS_Inside : ModelInstancePVS_Partly_Outside;
}
/*
ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const BoundingBoxf3& box)
{
const Polygon box_hull_2d({
@ -53,6 +55,7 @@ ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_
});
return printbed_collision_state(printbed_shape, print_volume_height, box_hull_2d, box.min.z(), box.max.z());
}
*/
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
Model& Model::assign_copy(const Model &rhs)
@ -360,6 +363,7 @@ BoundingBoxf3 Model::bounding_box() const
}
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
// printbed_shape is convex polygon
unsigned int Model::update_print_volume_state(const Polygon& printbed_shape, double print_volume_height)
{
unsigned int num_printable = 0;
@ -1569,6 +1573,7 @@ double ModelObject::get_instance_max_z(size_t instance_idx) const
}
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
// printbed_shape is convex polygon
unsigned int ModelObject::check_instances_print_volume_state(const Polygon& printbed_shape, double print_volume_height)
{
unsigned int num_printable = 0;

View File

@ -911,10 +911,12 @@ enum ModelInstanceEPrintVolumeState : unsigned char
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
// return the state of the given object's volume (extrusion along z of obj_hull_2d from obj_min_z to obj_max_z)
// with respect to the given print volume (extrusion along z of printbed_shape from zero to print_volume_height)
// Using rotating callipers to check for collision of two convex polygons.
ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const Polygon& obj_hull_2d, double obj_min_z, double obj_max_z);
// return the state of the given box
// with respect to the given print volume (extrusion along z of printbed_shape from zero to print_volume_height)
ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const BoundingBoxf3& box);
// Commented out, using rotating callipers is quite expensive for a bounding box test.
//ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const BoundingBoxf3& box);
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
// A single instance of a ModelObject.
@ -1122,6 +1124,7 @@ public:
// Set the print_volume_state of PrintObject::instances,
// return total number of printable objects.
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
// printbed_shape is convex polygon
unsigned int update_print_volume_state(const Polygon& printbed_shape, double print_volume_height);
#else
unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume);

View File

@ -1324,6 +1324,18 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
}
}
auto filter_out_small_polygons = [&num_extruders, &num_layers](std::vector<std::vector<Polygons>> &raw_surfaces, double min_area) -> void {
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx)
if (!raw_surfaces[extruder_idx].empty())
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx)
if (!raw_surfaces[extruder_idx][layer_idx].empty())
remove_small(raw_surfaces[extruder_idx][layer_idx], min_area);
};
// Filter out polygons less than 0.1mm^2, because they are unprintable and causing dimples on outer primers (#7104)
filter_out_small_polygons(top_raw, Slic3r::sqr(scale_(0.1f)));
filter_out_small_polygons(bottom_raw, Slic3r::sqr(scale_(0.1f)));
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
{
const char* colors[] = { "aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow" };

View File

@ -47,9 +47,9 @@ void MultiPoint::rotate(double angle, const Point &center)
double MultiPoint::length() const
{
Lines lines = this->lines();
const Lines& lines = this->lines();
double len = 0;
for (Lines::iterator it = lines.begin(); it != lines.end(); ++it) {
for (auto it = lines.cbegin(); it != lines.cend(); ++it) {
len += it->length();
}
return len;

View File

@ -1244,8 +1244,8 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("external");
def->enum_values.push_back("all");
def->enum_labels.push_back(L("None"));
def->enum_labels.push_back(L("External perimeters"));
def->enum_labels.push_back(L("All perimeters"));
def->enum_labels.push_back(L("Outside walls"));
def->enum_labels.push_back(L("All walls"));
def->mode = comSimple;
def->set_default_value(new ConfigOptionEnum<FuzzySkinType>(FuzzySkinType::None));
@ -3970,10 +3970,16 @@ std::string validate(const FullPrintConfig &cfg)
if (em <= 0)
return "Invalid value for --extrusion-multiplier";
// The following test was commented out after 482841b, see also https://github.com/prusa3d/PrusaSlicer/pull/6743.
// The backend should now handle this case correctly. I.e., zero default_acceleration behaves as if all others
// were zero too. This is now consistent with what the UI said would happen.
// The UI already grays the fields out, there is no more reason to reject it here. This function validates the
// config before exporting, leaving this check in would mean that config would be rejected before export
// (although both the UI and the backend handle it).
// --default-acceleration
if ((cfg.perimeter_acceleration != 0. || cfg.infill_acceleration != 0. || cfg.bridge_acceleration != 0. || cfg.first_layer_acceleration != 0.) &&
cfg.default_acceleration == 0.)
return "Invalid zero value for --default-acceleration when using other acceleration settings";
//if ((cfg.perimeter_acceleration != 0. || cfg.infill_acceleration != 0. || cfg.bridge_acceleration != 0. || cfg.first_layer_acceleration != 0.) &&
// cfg.default_acceleration == 0.)
// return "Invalid zero value for --default-acceleration when using other acceleration settings";
// --spiral-vase
if (cfg.spiral_vase) {

View File

@ -40,36 +40,6 @@ Point ConcaveHull::centroid(const Points &pp)
return c;
}
// As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound
// mode
template<typename PolygonsProvider>
static ClipperLib::Paths fast_offset(PolygonsProvider &&paths,
coord_t delta,
ClipperLib::JoinType jointype)
{
using ClipperLib::ClipperOffset;
using ClipperLib::etClosedPolygon;
using ClipperLib::Paths;
using ClipperLib::Path;
ClipperOffset offs;
offs.ArcTolerance = scaled<double>(0.01);
for (auto &p : paths)
// If the input is not at least a triangle, we can not do this algorithm
if(p.size() < 3) {
BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!";
return {};
}
offs.AddPaths(std::forward<PolygonsProvider>(paths), jointype, etClosedPolygon);
Paths result;
offs.Execute(result, static_cast<double>(delta));
return result;
}
Points ConcaveHull::calculate_centroids() const
{
// We get the centroids of all the islands in the 2D slice
@ -158,15 +128,18 @@ ExPolygons ConcaveHull::to_expolygons() const
ExPolygons offset_waffle_style_ex(const ConcaveHull &hull, coord_t delta)
{
ExPolygons ret = ClipperPaths_to_Slic3rExPolygons(
fast_offset(fast_offset(ClipperUtils::PolygonsProvider(hull.polygons()), 2 * delta, ClipperLib::jtRound), -delta, ClipperLib::jtRound));
for (ExPolygon &p : ret) p.holes.clear();
return ret;
return to_expolygons(offset_waffle_style(hull, delta));
}
Polygons offset_waffle_style(const ConcaveHull &hull, coord_t delta)
{
return to_polygons(offset_waffle_style_ex(hull, delta));
auto arc_tolerance = scaled<double>(0.01);
Polygons res = closing(hull.polygons(), 2 * delta, delta, ClipperLib::jtRound, arc_tolerance);
auto it = std::remove_if(res.begin(), res.end(), [](Polygon &p) { return p.is_clockwise(); });
res.erase(it, res.end());
return res;
}
}} // namespace Slic3r::sla

View File

@ -1216,7 +1216,7 @@ DynamicConfig SLAPrintStatistics::config() const
DynamicConfig SLAPrintStatistics::placeholders()
{
DynamicConfig config;
for (const std::string &key : {
for (const char *key : {
"print_time", "total_cost", "total_weight",
"objects_used_material", "support_used_material" })
config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}"));

View File

@ -462,85 +462,9 @@ BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& trafo, d
TriangleMesh TriangleMesh::convex_hull_3d() const
{
// The qhull call:
orgQhull::Qhull qhull;
qhull.disableOutputStream(); // we want qhull to be quiet
std::vector<realT> src_vertices;
try
{
#if REALfloat
qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt");
#else
src_vertices.reserve(this->its.vertices.size() * 3);
// We will now fill the vector with input points for computation:
for (const stl_vertex &v : this->its.vertices)
for (int i = 0; i < 3; ++ i)
src_vertices.emplace_back(v(i));
qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt");
#endif
}
catch (...)
{
std::cout << "Unable to create convex hull" << std::endl;
return TriangleMesh();
}
// Let's collect results:
std::vector<Vec3f> dst_vertices;
std::vector<Vec3i> dst_facets;
// Map of QHull's vertex ID to our own vertex ID (pointing to dst_vertices).
std::vector<int> map_dst_vertices;
#ifndef NDEBUG
Vec3f centroid = Vec3f::Zero();
for (const stl_vertex& pt : this->its.vertices)
centroid += pt;
centroid /= float(this->its.vertices.size());
#endif // NDEBUG
for (const orgQhull::QhullFacet facet : qhull.facetList()) {
// Collect face vertices first, allocate unique vertices in dst_vertices based on QHull's vertex ID.
Vec3i indices;
int cnt = 0;
for (const orgQhull::QhullVertex vertex : facet.vertices()) {
int id = vertex.id();
assert(id >= 0);
if (id >= int(map_dst_vertices.size()))
map_dst_vertices.resize(next_highest_power_of_2(size_t(id + 1)), -1);
if (int i = map_dst_vertices[id]; i == -1) {
// Allocate a new vertex.
i = int(dst_vertices.size());
map_dst_vertices[id] = i;
orgQhull::QhullPoint pt(vertex.point());
dst_vertices.emplace_back(pt[0], pt[1], pt[2]);
indices[cnt] = i;
} else {
// Reuse existing vertex.
indices[cnt] = i;
}
if (cnt ++ == 3)
break;
}
assert(cnt == 3);
if (cnt == 3) {
// QHull sorts vertices of a face lexicographically by their IDs, not by face normals.
// Calculate face normal based on the order of vertices.
Vec3f n = (dst_vertices[indices(1)] - dst_vertices[indices(0)]).cross(dst_vertices[indices(2)] - dst_vertices[indices(1)]);
auto *n2 = facet.getBaseT()->normal;
auto d = n.x() * n2[0] + n.y() * n2[1] + n.z() * n2[2];
#ifndef NDEBUG
Vec3f n3 = (dst_vertices[indices(0)] - centroid);
auto d3 = n.dot(n3);
assert((d < 0.f) == (d3 < 0.f));
#endif // NDEBUG
// Get the face normal from QHull.
if (d < 0.f)
// Fix face orientation.
std::swap(indices[1], indices[2]);
dst_facets.emplace_back(indices);
}
}
TriangleMesh mesh{ std::move(dst_vertices), std::move(dst_facets) };
assert(mesh.stats().manifold());
TriangleMesh mesh(its_convex_hull(this->its));
// Quite often qhull produces non-manifold mesh.
// assert(mesh.stats().manifold());
return mesh;
}
@ -1108,6 +1032,90 @@ indexed_triangle_set its_make_sphere(double radius, double fa)
return mesh;
}
indexed_triangle_set its_convex_hull(const std::vector<Vec3f> &pts)
{
std::vector<Vec3f> dst_vertices;
std::vector<Vec3i> dst_facets;
if (! pts.empty()) {
// The qhull call:
orgQhull::Qhull qhull;
qhull.disableOutputStream(); // we want qhull to be quiet
#if ! REALfloat
std::vector<realT> src_vertices;
#endif
try {
#if REALfloat
qhull.runQhull("", 3, (int)pts.size(), (const realT*)(pts.front().data()), "Qt");
#else
src_vertices.reserve(pts.size() * 3);
// We will now fill the vector with input points for computation:
for (const stl_vertex &v : pts)
for (int i = 0; i < 3; ++ i)
src_vertices.emplace_back(v(i));
qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt");
#endif
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "its_convex_hull: Unable to create convex hull";
return {};
}
// Let's collect results:
// Map of QHull's vertex ID to our own vertex ID (pointing to dst_vertices).
std::vector<int> map_dst_vertices;
#ifndef NDEBUG
Vec3f centroid = Vec3f::Zero();
for (const stl_vertex& pt : pts)
centroid += pt;
centroid /= float(pts.size());
#endif // NDEBUG
for (const orgQhull::QhullFacet facet : qhull.facetList()) {
// Collect face vertices first, allocate unique vertices in dst_vertices based on QHull's vertex ID.
Vec3i indices;
int cnt = 0;
for (const orgQhull::QhullVertex vertex : facet.vertices()) {
int id = vertex.id();
assert(id >= 0);
if (id >= int(map_dst_vertices.size()))
map_dst_vertices.resize(next_highest_power_of_2(size_t(id + 1)), -1);
if (int i = map_dst_vertices[id]; i == -1) {
// Allocate a new vertex.
i = int(dst_vertices.size());
map_dst_vertices[id] = i;
orgQhull::QhullPoint pt(vertex.point());
dst_vertices.emplace_back(pt[0], pt[1], pt[2]);
indices[cnt] = i;
} else {
// Reuse existing vertex.
indices[cnt] = i;
}
if (cnt ++ == 3)
break;
}
assert(cnt == 3);
if (cnt == 3) {
// QHull sorts vertices of a face lexicographically by their IDs, not by face normals.
// Calculate face normal based on the order of vertices.
Vec3f n = (dst_vertices[indices(1)] - dst_vertices[indices(0)]).cross(dst_vertices[indices(2)] - dst_vertices[indices(1)]);
auto *n2 = facet.getBaseT()->normal;
auto d = n.x() * n2[0] + n.y() * n2[1] + n.z() * n2[2];
#ifndef NDEBUG
Vec3f n3 = (dst_vertices[indices(0)] - centroid);
auto d3 = n.dot(n3);
assert((d < 0.f) == (d3 < 0.f));
#endif // NDEBUG
// Get the face normal from QHull.
if (d < 0.f)
// Fix face orientation.
std::swap(indices[1], indices[2]);
dst_facets.emplace_back(indices);
}
}
}
return { std::move(dst_facets), std::move(dst_vertices) };
}
void its_reverse_all_facets(indexed_triangle_set &its)
{
for (stl_triangle_vertex_indices &face : its.indices)

View File

@ -301,6 +301,9 @@ indexed_triangle_set its_make_cone(double r, double h, double fa=(2*PI/360));
indexed_triangle_set its_make_pyramid(float base, float height);
indexed_triangle_set its_make_sphere(double radius, double fa);
indexed_triangle_set its_convex_hull(const std::vector<Vec3f> &pts);
inline indexed_triangle_set its_convex_hull(const indexed_triangle_set &its) { return its_convex_hull(its.vertices); }
inline TriangleMesh make_cube(double x, double y, double z) { return TriangleMesh(its_make_cube(x, y, z)); }
inline TriangleMesh make_prism(float width, float length, float height) { return TriangleMesh(its_make_prism(width, length, height)); }
inline TriangleMesh make_cylinder(double r, double h, double fa=(2*PI/360)) { return TriangleMesh{its_make_cylinder(r, h, fa)}; }

View File

@ -1919,6 +1919,7 @@ void slice_mesh_slabs(
#endif // EXPENSIVE_DEBUG_CHECKS
std::vector<stl_vertex> vertices_transformed = transform_mesh_vertices_for_slicing(mesh, trafo);
const bool mirrored = trafo.matrix().determinant() < 0;
std::vector<FaceOrientation> face_orientation(mesh.indices.size(), FaceOrientation::Up);
for (const stl_triangle_vertex_indices &tri : mesh.indices) {
@ -1929,7 +1930,7 @@ void slice_mesh_slabs(
const Point a = to_2d(fa).cast<coord_t>();
const Point b = to_2d(fb).cast<coord_t>();
const Point c = to_2d(fc).cast<coord_t>();
const int64_t d = cross2((b - a).cast<int64_t>(), (c - b).cast<int64_t>());
const int64_t d = cross2((b - a).cast<int64_t>(), (c - b).cast<int64_t>()) * (mirrored ? -1 : 1);
FaceOrientation fo = FaceOrientation::Vertical;
if (d > 0)
fo = FaceOrientation::Up;

View File

@ -128,13 +128,13 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
const Vec3f& source, float radius,
CursorType cursor_type, EnforcerBlockerType new_state,
const Transform3d& trafo, const Transform3d& trafo_no_translate,
bool triangle_splitting, float highlight_by_angle_deg)
bool triangle_splitting, const ClippingPlane &clp, float highlight_by_angle_deg)
{
assert(facet_start < m_orig_size_indices);
// Save current cursor center, squared radius and camera direction, so we don't
// have to pass it around.
m_cursor = Cursor(hit, source, radius, cursor_type, trafo);
m_cursor = Cursor(hit, source, radius, cursor_type, trafo, clp);
// In case user changed cursor size since last time, update triangle edge limit.
// It is necessary to compare the internal radius in m_cursor! radius is in
@ -172,15 +172,23 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
}
}
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start,
const Transform3d& trafo_no_translate,
float seed_fill_angle, float highlight_by_angle_deg,
bool TriangleSelector::is_facet_clipped(int facet_idx, const ClippingPlane &clp) const
{
for (int vert_idx : m_triangles[facet_idx].verts_idxs)
if (clp.is_active() && clp.is_mesh_point_clipped(m_vertices[vert_idx].v))
return true;
return false;
}
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, const Transform3d& trafo_no_translate,
const ClippingPlane &clp, float seed_fill_angle, float highlight_by_angle_deg,
bool force_reselection)
{
assert(facet_start < m_orig_size_indices);
// Recompute seed fill only if the cursor is pointing on facet unselected by seed fill.
if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection)
// Recompute seed fill only if the cursor is pointing on facet unselected by seed fill or a clipping plane is active.
if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection && !clp.is_active())
return;
this->seed_fill_unselect_all_triangles();
@ -215,7 +223,7 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st
// Propagate over the original triangles.
for (int neighbor_idx : m_neighbors[current_facet]) {
assert(neighbor_idx >= -1);
if (neighbor_idx >= 0 && !visited[neighbor_idx]) {
if (neighbor_idx >= 0 && !visited[neighbor_idx] && !is_facet_clipped(neighbor_idx, clp)) {
// Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do.
const Vec3f &n1 = m_face_normals[m_triangles[neighbor_idx].source_triangle];
const Vec3f &n2 = m_face_normals[m_triangles[current_facet].source_triangle];
@ -331,12 +339,12 @@ void TriangleSelector::append_touching_edges(int itriangle, int vertexi, int ver
process_subtriangle(touching.second, Partition::Second);
}
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate, bool force_reselection)
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, const ClippingPlane &clp, bool propagate, bool force_reselection)
{
int start_facet_idx = select_unsplit_triangle(hit, facet_start);
assert(start_facet_idx != -1);
// Recompute bucket fill only if the cursor is pointing on facet unselected by bucket fill.
if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection))
// Recompute bucket fill only if the cursor is pointing on facet unselected by bucket fill or a clipping plane is active.
if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection && !clp.is_active()))
return;
assert(!m_triangles[start_facet_idx].is_split());
@ -379,7 +387,7 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_
std::vector<int> touching_triangles = get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]);
for(const int tr_idx : touching_triangles) {
if (tr_idx < 0 || visited[tr_idx] || m_triangles[tr_idx].get_state() != start_facet_state)
if (tr_idx < 0 || visited[tr_idx] || m_triangles[tr_idx].get_state() != start_facet_state || is_facet_clipped(tr_idx, clp))
continue;
assert(!m_triangles[tr_idx].is_split());
@ -1687,11 +1695,12 @@ void TriangleSelector::seed_fill_apply_on_triangles(EnforcerBlockerType new_stat
TriangleSelector::Cursor::Cursor(
const Vec3f& center_, const Vec3f& source_, float radius_world,
CursorType type_, const Transform3d& trafo_)
CursorType type_, const Transform3d& trafo_, const ClippingPlane &clipping_plane_)
: center{center_},
source{source_},
type{type_},
trafo{trafo_.cast<float>()}
trafo{trafo_.cast<float>()},
clipping_plane(clipping_plane_)
{
Vec3d sf = Geometry::Transformation(trafo_).get_scaling_factor();
if (is_approx(sf(0), sf(1)) && is_approx(sf(1), sf(2))) {
@ -1714,22 +1723,19 @@ TriangleSelector::Cursor::Cursor(
dir = (center - source).normalized();
}
// Is a point (in mesh coords) inside a cursor?
bool TriangleSelector::Cursor::is_mesh_point_inside(Vec3f point) const
bool TriangleSelector::Cursor::is_mesh_point_inside(const Vec3f &point) const
{
if (! uniform_scaling)
point = trafo * point;
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
const Vec3f diff = center - transformed_point;
const bool is_point_inside = (type == CIRCLE ? (diff - diff.dot(dir) * dir).squaredNorm() : diff.squaredNorm()) < radius_sqr;
Vec3f diff = center - point;
return (type == CIRCLE ?
(diff - diff.dot(dir) * dir).squaredNorm() :
diff.squaredNorm())
< radius_sqr;
if (is_point_inside && clipping_plane.is_active())
return !clipping_plane.is_mesh_point_clipped(point);
return is_point_inside;
}
// p1, p2, p3 are in mesh coords!
bool TriangleSelector::Cursor::is_pointer_in_triangle(const Vec3f& p1_,
const Vec3f& p2_,

View File

@ -4,6 +4,7 @@
// #define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
#include <cfloat>
#include "Point.hpp"
#include "TriangleMesh.hpp"
@ -22,6 +23,18 @@ public:
POINTER
};
struct ClippingPlane
{
Vec3f normal;
float offset;
ClippingPlane() : normal{0.f, 0.f, 1.f}, offset{FLT_MAX} {};
explicit ClippingPlane(const std::array<float, 4> &clp) : normal{clp[0], clp[1], clp[2]}, offset{clp[3]} {}
bool is_active() const { return offset != FLT_MAX; }
bool is_mesh_point_clipped(const Vec3f &point) const { return normal.dot(point) - offset > 0.f; }
};
std::pair<std::vector<Vec3i>, std::vector<Vec3i>> precompute_all_neighbors() const;
void precompute_all_neighbors_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec3i> &neighbors_out, std::vector<Vec3i> &neighbors_normal_out) const;
@ -47,19 +60,22 @@ public:
const Transform3d &trafo, // matrix to get from mesh to world
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
bool triangle_splitting, // If triangles will be split base on the cursor or not
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
float seed_fill_angle, // the maximal angle between two facets to be painted by the same color
float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
float seed_fill_angle, // the maximal angle between two facets to be painted by the same color
float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
void bucket_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
bool propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
void bucket_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
bool propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
bool has_facets(EnforcerBlockerType state) const;
static bool has_facets(const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> &data, EnforcerBlockerType test_state);
@ -183,8 +199,8 @@ protected:
struct Cursor {
Cursor() = default;
Cursor(const Vec3f& center_, const Vec3f& source_, float radius_world,
CursorType type_, const Transform3d& trafo_);
bool is_mesh_point_inside(Vec3f pt) const;
CursorType type_, const Transform3d& trafo_, const ClippingPlane &clipping_plane_);
bool is_mesh_point_inside(const Vec3f &pt) const;
bool is_pointer_in_triangle(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) const;
Vec3f center;
@ -195,6 +211,7 @@ protected:
Transform3f trafo;
Transform3f trafo_normal;
bool uniform_scaling;
ClippingPlane clipping_plane;
};
Cursor m_cursor;
@ -211,6 +228,7 @@ private:
void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant.
bool is_pointer_in_triangle(int facet_idx) const;
bool is_edge_inside_cursor(int facet_idx) const;
bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const;
int push_triangle(int a, int b, int c, int source_triangle, EnforcerBlockerType state = EnforcerBlockerType{0});
void perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state);
Vec3i child_neighbors(const Triangle &tr, const Vec3i &neighbors, int child_idx) const;

View File

@ -271,7 +271,8 @@ bool Bed3D::is_circle(const Pointfs& shape, Vec2d* center, double* radius)
// Analyze the array of points.
// Do they reside on a circle ?
const Vec2d box_center = BoundingBoxf(shape).center();
const Vec2d box_center = Geometry::circle_center_taubin_newton(shape);
std::vector<double> vertex_distances;
double avg_dist = 0.0;
for (const Vec2d& pt : shape) {

View File

@ -40,12 +40,6 @@
#include <Eigen/Dense>
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
#include <libqhullcpp/Qhull.h>
#include <libqhullcpp/QhullFacetList.h>
#include <libqhullcpp/QhullVertexSet.h>
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
#ifdef HAS_GLSAFE
void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char* function_name)
{
@ -626,100 +620,16 @@ void GLVolume::render_sinking_contours()
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
void GLVolume::calc_convex_hull_3d()
{
if (this->indexed_vertex_array.vertices_and_normals_interleaved.empty())
return;
TriangleMesh mesh;
for (size_t i = 0; i < this->indexed_vertex_array.vertices_and_normals_interleaved.size(); i += 6) {
const size_t v_id = 3 + i;
mesh.its.vertices.push_back({ this->indexed_vertex_array.vertices_and_normals_interleaved[v_id + 0],
this->indexed_vertex_array.vertices_and_normals_interleaved[v_id + 1],
this->indexed_vertex_array.vertices_and_normals_interleaved[v_id + 2]
});
const std::vector<float> &src = this->indexed_vertex_array.vertices_and_normals_interleaved;
std::vector<Vec3f> pts;
assert(src.size() % 6 == 0);
pts.reserve(src.size() / 6);
for (auto it = src.begin(); it != src.end(); ) {
it += 3;
pts.push_back({ *it, *(it + 1), *(it + 2) });
it += 3;
}
const std::vector<Vec3f>& vertices = mesh.its.vertices;
// The qhull call:
orgQhull::Qhull qhull;
qhull.disableOutputStream(); // we want qhull to be quiet
std::vector<realT> src_vertices;
try
{
#if REALfloat
qhull.runQhull("", 3, (int)vertices.size(), (const realT*)(vertices.front().data()), "Qt");
#else
src_vertices.reserve(vertices.size() * 3);
// We will now fill the vector with input points for computation:
for (const stl_vertex& v : vertices)
for (int i = 0; i < 3; ++i)
src_vertices.emplace_back(v(i));
qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt");
#endif
}
catch (...)
{
std::cout << "GLVolume::calc_convex_hull_3d() - Unable to create convex hull" << std::endl;
return ;
}
// Let's collect results:
std::vector<Vec3f> dst_vertices;
std::vector<Vec3i> dst_facets;
// Map of QHull's vertex ID to our own vertex ID (pointing to dst_vertices).
std::vector<int> map_dst_vertices;
#ifndef NDEBUG
Vec3f centroid = Vec3f::Zero();
for (const auto& pt : vertices)
centroid += pt;
centroid /= float(vertices.size());
#endif // NDEBUG
for (const orgQhull::QhullFacet& facet : qhull.facetList()) {
// Collect face vertices first, allocate unique vertices in dst_vertices based on QHull's vertex ID.
Vec3i indices;
int cnt = 0;
for (const orgQhull::QhullVertex vertex : facet.vertices()) {
const int id = vertex.id();
assert(id >= 0);
if (id >= int(map_dst_vertices.size()))
map_dst_vertices.resize(next_highest_power_of_2(size_t(id + 1)), -1);
if (int i = map_dst_vertices[id]; i == -1) {
// Allocate a new vertex.
i = int(dst_vertices.size());
map_dst_vertices[id] = i;
orgQhull::QhullPoint pt(vertex.point());
dst_vertices.emplace_back(pt[0], pt[1], pt[2]);
indices[cnt] = i;
}
else
// Reuse existing vertex.
indices[cnt] = i;
if (cnt++ == 3)
break;
}
assert(cnt == 3);
if (cnt == 3) {
// QHull sorts vertices of a face lexicographically by their IDs, not by face normals.
// Calculate face normal based on the order of vertices.
const Vec3f n = (dst_vertices[indices(1)] - dst_vertices[indices(0)]).cross(dst_vertices[indices(2)] - dst_vertices[indices(1)]);
auto* n2 = facet.getBaseT()->normal;
const auto d = n.x() * n2[0] + n.y() * n2[1] + n.z() * n2[2];
#ifndef NDEBUG
const Vec3f n3 = (dst_vertices[indices(0)] - centroid);
const auto d3 = n.dot(n3);
assert((d < 0.f) == (d3 < 0.f));
#endif // NDEBUG
// Get the face normal from QHull.
if (d < 0.f)
// Fix face orientation.
std::swap(indices[1], indices[2]);
dst_facets.emplace_back(indices);
}
}
TriangleMesh out_mesh{ std::move(dst_vertices), std::move(dst_facets) };
this->set_convex_hull(out_mesh);
this->set_convex_hull(TriangleMesh(its_convex_hull(pts)));
}
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
@ -1070,7 +980,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
{ unscale<double>(bed_box_2D.max.x()), unscale<double>(bed_box_2D.max.y()), bed_height });
auto check_against_rectangular_bed = [&print_volume](GLVolume& volume, ModelInstanceEPrintVolumeState& state) {
const BoundingBoxf3* const bb = volume.is_sinking() ? &volume.transformed_non_sinking_bounding_box() : &volume.transformed_convex_hull_bounding_box();
const BoundingBoxf3* const bb = (volume.is_sinking() && volume.object_idx() != -1 && volume.volume_idx() != -1) ? &volume.transformed_non_sinking_bounding_box() : &volume.transformed_convex_hull_bounding_box();
volume.is_outside = !print_volume.contains(*bb);
if (volume.printable) {
if (state == ModelInstancePVS_Inside && volume.is_outside)
@ -1081,7 +991,8 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
};
auto check_against_circular_bed = [](GLVolume& volume, ModelInstanceEPrintVolumeState& state, const Vec2d& center, double radius) {
const TriangleMesh* mesh = volume.is_sinking() ? &GUI::wxGetApp().plater()->model().objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : volume.convex_hull();
const TriangleMesh* mesh = (volume.is_sinking() && volume.object_idx() != -1 && volume.volume_idx() != -1)? &GUI::wxGetApp().plater()->model().objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : volume.convex_hull();
//FIXME 2D convex hull is O(n log n), while testing the 2D points against 2D circle is O(n).
const Polygon volume_hull_2d = its_convex_hull_2d_above(mesh->its, volume.world_matrix().cast<float>(), 0.0f);
size_t outside_count = 0;
const double sq_radius = sqr(radius);
@ -1100,9 +1011,10 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
};
auto check_against_convex_bed = [&bed_poly, bed_height](GLVolume& volume, ModelInstanceEPrintVolumeState& state) {
const TriangleMesh* mesh = volume.is_sinking() ? &GUI::wxGetApp().plater()->model().objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : volume.convex_hull();
const TriangleMesh* mesh = (volume.is_sinking() && volume.object_idx() != -1 && volume.volume_idx() != -1) ? &GUI::wxGetApp().plater()->model().objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : volume.convex_hull();
const Polygon volume_hull_2d = its_convex_hull_2d_above(mesh->its, volume.world_matrix().cast<float>(), 0.0f);
const BoundingBoxf3* const bb = volume.is_sinking() ? &volume.transformed_non_sinking_bounding_box() : &volume.transformed_convex_hull_bounding_box();
const BoundingBoxf3* const bb = (volume.is_sinking() && volume.object_idx() != -1 && volume.volume_idx() != -1) ? &volume.transformed_non_sinking_bounding_box() : &volume.transformed_convex_hull_bounding_box();
// Using rotating callipers to check for collision of two convex polygons.
ModelInstanceEPrintVolumeState volume_state = printbed_collision_state(bed_poly, bed_height, volume_hull_2d, bb->min.z(), bb->max.z());
bool contained = (volume_state == ModelInstancePVS_Inside);
bool intersects = (volume_state == ModelInstancePVS_Partly_Outside);
@ -1131,6 +1043,14 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
ModelInstanceEPrintVolumeState overall_state = ModelInstancePVS_Inside;
bool contained_min_one = false;
enum class BedShape { Rectangle, Circle, Convex, NonConvex };
Vec2d center;
double radius;
BedShape bed_shape =
GUI::Bed3D::is_rectangle(opt->values) ? BedShape::Rectangle :
GUI::Bed3D::is_circle(opt->values, &center, &radius) ? BedShape::Circle :
GUI::Bed3D::is_convex(opt->values) ? BedShape::Convex : BedShape::NonConvex;
for (GLVolume* volume : this->volumes) {
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
if (as_toolpaths && !volume->is_extrusion_path)
@ -1138,15 +1058,11 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
else if (!as_toolpaths && (volume->is_modifier || (!volume->shader_outside_printer_detection_enabled && (volume->is_wipe_tower || volume->composite_id.volume_id < 0))))
continue;
if (GUI::Bed3D::is_rectangle(opt->values))
check_against_rectangular_bed(*volume, overall_state);
else {
Vec2d center;
double radius;
if (GUI::Bed3D::is_circle(opt->values, &center, &radius))
check_against_circular_bed(*volume, overall_state, center, radius);
else if (GUI::Bed3D::is_convex(opt->values))
check_against_convex_bed(*volume, overall_state);
switch (bed_shape) {
case BedShape::Rectangle: check_against_rectangular_bed(*volume, overall_state); break;
case BedShape::Circle: check_against_circular_bed(*volume, overall_state, center, radius); break;
case BedShape::Convex: check_against_convex_bed(*volume, overall_state); break;
default: break;
}
contained_min_one |= !volume->is_outside;

View File

@ -2080,7 +2080,7 @@ void Control::auto_color_change()
}
int extruders_cnt = GUI::wxGetApp().extruders_edited_cnt();
int extruder = 2;
// int extruder = 2;
const Print& print = GUI::wxGetApp().plater()->fff_print();
for (auto object : print.objects()) {

View File

@ -2581,12 +2581,15 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
if (camera_space) {
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> inv_view_3x3 = wxGetApp().plater()->get_camera().get_view_matrix().inverse().matrix().block(0, 0, 3, 3);
displacement = multiplier * (inv_view_3x3 * direction);
displacement(2) = 0.0;
displacement.z() = 0.0;
}
else
displacement = multiplier * direction;
m_selection.translate(displacement);
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
m_selection.stop_dragging();
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
m_dirty = true;
}
);
@ -2685,6 +2688,9 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
auto do_rotate = [this](double angle_z_rad) {
m_selection.start_dragging();
m_selection.rotate(Vec3d(0.0, 0.0, angle_z_rad), TransformationType(TransformationType::World_Relative_Joint));
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
m_selection.stop_dragging();
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
m_dirty = true;
// wxGetApp().obj_manipul()->set_dirty();
};

View File

@ -673,6 +673,7 @@ void GUI_App::post_init()
// to popup a modal dialog on start without screwing combo boxes.
// This is ugly but I honestly found no better way to do it.
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
assert(this->preset_updater); // FIXME Following condition is probably not neccessary.
if (this->preset_updater) {
this->check_updates(false);
CallAfter([this] {
@ -720,9 +721,11 @@ GUI_App::~GUI_App()
delete preset_updater;
}
std::string GUI_App::get_gl_info(bool format_as_html, bool extensions)
// If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
// Otherwise HTML formatted for the system info dialog.
std::string GUI_App::get_gl_info(bool for_github)
{
return OpenGLManager::get_gl_info().to_string(format_as_html, extensions);
return OpenGLManager::get_gl_info().to_string(for_github);
}
wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas)
@ -745,7 +748,8 @@ void GUI_App::init_app_config()
{
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
// SetAppName(SLIC3R_APP_KEY);
SetAppName(SLIC3R_APP_KEY "-alpha");
// SetAppName(SLIC3R_APP_KEY "-alpha");
SetAppName(SLIC3R_APP_KEY "-beta");
// SetAppDisplayName(SLIC3R_APP_NAME);
// Set the Slic3r data directory at the Slic3r XS module.

View File

@ -172,7 +172,9 @@ public:
// Process command line parameters cached in this->init_params,
// load configs, STLs etc.
void post_init();
static std::string get_gl_info(bool format_as_html, bool extensions);
// If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
// Otherwise HTML formatted for the system info dialog.
static std::string get_gl_info(bool for_github);
wxGLContext* init_glcontext(wxGLCanvas& canvas);
bool init_opengl();

View File

@ -1433,7 +1433,7 @@ void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false
selection_changed();
}
/*
void ObjectList::load_part(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery/* = false* /)
void ObjectList::load_part(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false)
{
if (type != ModelVolumeType::MODEL_PART)
return;

View File

@ -820,6 +820,9 @@ void ObjectManipulation::change_position_value(int axis, double value)
Selection& selection = canvas->get_selection();
selection.start_dragging();
selection.translate(position - m_cache.position, selection.requires_local_axes());
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
selection.stop_dragging();
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
canvas->do_move(L("Set Position"));
m_cache.position = position;
@ -851,6 +854,9 @@ void ObjectManipulation::change_rotation_value(int axis, double value)
selection.rotate(
(M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation),
transformation_type);
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
selection.stop_dragging();
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
canvas->do_rotate(L("Set Orientation"));
m_cache.rotation = rotation;
@ -923,6 +929,9 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const
selection.start_dragging();
selection.scale(scaling_factor, transformation_type);
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
selection.stop_dragging();
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale"));
}

View File

@ -508,20 +508,23 @@ RENDER_AGAIN:
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 =
std::max({m_imgui->calc_text_size(m_desc.at("offset")).x,
m_imgui->calc_text_size(m_desc.at("quality")).x,
m_imgui->calc_text_size(m_desc.at("closing_distance")).x,
m_imgui->calc_text_size(m_desc.at("hole_diameter")).x,
m_imgui->calc_text_size(m_desc.at("hole_depth")).x})
+ m_imgui->scaled(1.f);
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(0.5f);
const float settings_sliders_left =
std::max(std::max({m_imgui->calc_text_size(m_desc.at("offset")).x,
m_imgui->calc_text_size(m_desc.at("quality")).x,
m_imgui->calc_text_size(m_desc.at("closing_distance")).x,
m_imgui->calc_text_size(m_desc.at("hole_diameter")).x,
m_imgui->calc_text_size(m_desc.at("hole_depth")).x}) + m_imgui->scaled(0.5f), clipping_slider_left);
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 diameter_slider_left = settings_sliders_left; //m_imgui->calc_text_size(m_desc.at("hole_diameter")).x + m_imgui->scaled(1.f);
const float minimal_slider_width = m_imgui->scaled(4.f);
const float button_preview_width = m_imgui->calc_button_size(m_desc.at("preview")).x;
float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left});
window_width = std::max(window_width, m_imgui->calc_text_size(m_desc.at("preview")).x);
window_width = std::max(window_width, button_preview_width);
if (m_imgui->button(m_desc["preview"]))
hollow_mesh();
@ -544,9 +547,9 @@ RENDER_AGAIN:
float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("offset"));
ImGui::SameLine(settings_sliders_left);
ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
ImGui::PushItemWidth(window_width - settings_sliders_left);
m_imgui->slider_float(" ", &offset, offset_min, offset_max, "%.1f mm");
m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm");
if (ImGui::IsItemHovered())
m_imgui->tooltip((_utf8(opts[0].second->tooltip)).c_str(), max_tooltip_width);
@ -557,8 +560,8 @@ RENDER_AGAIN:
if (current_mode >= quality_mode) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("quality"));
ImGui::SameLine(settings_sliders_left);
m_imgui->slider_float(" ", &quality, quality_min, quality_max, "%.1f");
ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f");
if (ImGui::IsItemHovered())
m_imgui->tooltip((_utf8(opts[1].second->tooltip)).c_str(), max_tooltip_width);
@ -570,8 +573,8 @@ RENDER_AGAIN:
if (current_mode >= closing_d_mode) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("closing_distance"));
ImGui::SameLine(settings_sliders_left);
m_imgui->slider_float(" ", &closing_d, closing_d_min, closing_d_max, "%.1f mm");
ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm");
if (ImGui::IsItemHovered())
m_imgui->tooltip((_utf8(opts[2].second->tooltip)).c_str(), max_tooltip_width);
@ -614,11 +617,11 @@ RENDER_AGAIN:
m_new_hole_radius = diameter_upper_cap / 2.f;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("hole_diameter"));
ImGui::SameLine(diameter_slider_left);
ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x);
ImGui::PushItemWidth(window_width - diameter_slider_left);
float diam = 2.f * m_new_hole_radius;
m_imgui->slider_float("", &diam, 1.f, 15.f, "%.1f mm", 1.f, false);
m_imgui->slider_float("##hole_diameter", &diam, 1.f, 25.f, "%.1f mm", 1.f, false);
// Let's clamp the value (which could have been entered by keyboard) to a larger range
// than the slider. This allows entering off-scale values and still protects against
//complete non-sense.
@ -630,8 +633,8 @@ RENDER_AGAIN:
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc["hole_depth"]);
ImGui::SameLine(diameter_slider_left);
m_imgui->slider_float(" ", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false);
ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x);
m_imgui->slider_float("##hole_depth", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false);
// Same as above:
m_new_hole_height = std::clamp(m_new_hole_height, 0.f, 100.f);
@ -697,10 +700,10 @@ RENDER_AGAIN:
}
}
ImGui::SameLine(clipping_slider_left);
ImGui::PushItemWidth(window_width - clipping_slider_left);
ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x);
ImGui::PushItemWidth(window_width - settings_sliders_left);
float clp_dist = m_c->object_clipper()->get_position();
if (m_imgui->slider_float(" ", &clp_dist, 0.f, 1.f, "%.2f"))
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true);
// make sure supports are shown/hidden as appropriate
@ -732,7 +735,7 @@ RENDER_AGAIN:
if (force_refresh)
m_parent.set_as_dirty();
if (config_changed)
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE));
}

View File

@ -203,6 +203,7 @@ void GLGizmoMmuSegmentation::render_triangles(const Selection &selection) const
glsafe(::glMultMatrixd(trafo_matrix.data()));
shader->set_uniform("volume_world_matrix", trafo_matrix);
shader->set_uniform("volume_mirrored", is_left_handed);
m_triangle_selectors[mesh_id]->render(m_imgui);
glsafe(::glPopMatrix());

View File

@ -267,7 +267,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
const ModelObject *mo = m_c->selection_info()->model_object();
const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true);
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle,
const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix();
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
m_seed_fill_last_mesh_id = m_rr.mesh_id;
@ -364,22 +365,22 @@ 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()));
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
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::SMART_FILL)
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, m_smart_fill_angle,
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, 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);
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, false, true);
else if (m_tool_type == ToolType::BUCKET_FILL)
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), true, true);
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, true, true);
m_seed_fill_last_mesh_id = -1;
} else if (m_tool_type == ToolType::BRUSH)
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type,
new_state, trafo_matrix, trafo_matrix_not_translate, m_triangle_splitting_enabled,
new_state, trafo_matrix, trafo_matrix_not_translate, m_triangle_splitting_enabled, clp,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
m_last_mouse_click = mouse_position;
}
@ -430,16 +431,18 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
if(m_rr.mesh_id != m_seed_fill_last_mesh_id)
seed_fill_unselect_all();
const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id];
const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id];
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
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), trafo_matrix_not_translate, m_smart_fill_angle,
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle,
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
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);
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, false);
else if (m_tool_type == ToolType::BUCKET_FILL)
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), true);
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, true);
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
m_seed_fill_last_mesh_id = m_rr.mesh_id;
return true;
@ -569,6 +572,25 @@ void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&)
m_schedule_update = true;
}
TriangleSelector::ClippingPlane GLGizmoPainterBase::get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const {
const ::Slic3r::GUI::ClippingPlane *const clipping_plane = m_c->object_clipper()->get_clipping_plane();
if (clipping_plane == nullptr || !clipping_plane->is_active())
return {};
const Vec3d clp_normal = clipping_plane->get_normal();
const double clp_offset = clipping_plane->get_offset();
const Transform3d trafo_normal = Transform3d(trafo.linear().transpose());
const Transform3d trafo_inv = trafo.inverse();
Vec3d point_on_plane = clp_normal * clp_offset;
Vec3d point_on_plane_transformed = trafo_inv * point_on_plane;
Vec3d normal_transformed = trafo_normal * clp_normal;
auto offset_transformed = float(point_on_plane_transformed.dot(normal_transformed));
return TriangleSelector::ClippingPlane({float(normal_transformed.x()), float(normal_transformed.y()), float(normal_transformed.z()), offset_transformed});
}
std::array<float, 4> TriangleSelectorGUI::get_seed_fill_color(const std::array<float, 4> &base_color)
{
return {base_color[0] * 0.75f, base_color[1] * 0.75f, base_color[2] * 0.75f, 1.f};

View File

@ -184,6 +184,8 @@ protected:
ClippingPlaneDataWrapper get_clipping_plane_data() const;
TriangleSelector::ClippingPlane get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const;
private:
bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const;
void update_raycast_cache(const Vec2d& mouse_position,

View File

@ -24,6 +24,7 @@ GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
, m_obj_index(0)
, m_need_reload(false)
, m_show_wireframe(false)
, m_move_to_center(false)
// translation for GUI size
, tr_mesh_name(_u8L("Mesh name"))
, tr_triangles(_u8L("Triangles"))
@ -50,6 +51,62 @@ bool GLGizmoSimplify::on_esc_key_down() {
return true;
}
// while opening needs GLGizmoSimplify to set window position
void GLGizmoSimplify::add_simplify_suggestion_notification(
const std::vector<size_t> &object_ids,
const ModelObjectPtrs & objects,
NotificationManager & manager)
{
std::vector<size_t> big_ids;
big_ids.reserve(object_ids.size());
auto is_big_object = [&objects](size_t object_id) {
const uint32_t triangles_to_suggest_simplify = 1000000;
if (object_id >= objects.size()) return false; // out of object index
ModelVolumePtrs &volumes = objects[object_id]->volumes;
if (volumes.size() != 1) return false; // not only one volume
size_t triangle_count = volumes.front()->mesh().its.indices.size();
if (triangle_count < triangles_to_suggest_simplify)
return false; // small volume
return true;
};
std::copy_if(object_ids.begin(), object_ids.end(),
std::back_inserter(big_ids), is_big_object);
if (big_ids.empty()) return;
for (size_t object_id : big_ids) {
std::string t = _u8L(
"Processing model '@object_name' with more than 1M triangles "
"could be slow. It is highly recommend to reduce "
"amount of triangles.");
t.replace(t.find("@object_name"), sizeof("@object_name") - 1,
objects[object_id]->name);
// std::stringstream text;
// text << t << "\n";
std::string hypertext = _u8L("Simplify model");
std::function<bool(wxEvtHandler *)> open_simplify =
[object_id](wxEvtHandler *) {
auto plater = wxGetApp().plater();
if (object_id >= plater->model().objects.size()) return true;
Selection &selection = plater->canvas3D()->get_selection();
selection.clear();
selection.add_object((unsigned int) object_id);
auto &manager = plater->canvas3D()->get_gizmos_manager();
bool close_notification = true;
if(!manager.open_gizmo(GLGizmosManager::Simplify))
return close_notification;
GLGizmoSimplify* simplify = dynamic_cast<GLGizmoSimplify*>(manager.get_current());
if (simplify == nullptr) return close_notification;
simplify->set_center_position();
return close_notification;
};
manager.push_simplify_suggestion_notification(
t, objects[object_id]->id(), hypertext, open_simplify);
}
}
std::string GLGizmoSimplify::on_get_name() const
{
return _u8L("Simplify");
@ -91,8 +148,16 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
m_is_valid_result = false;
m_exist_preview = false;
init_wireframe();
if (change_window_position) {
live_preview();
// set window position
if (m_move_to_center && change_window_position) {
m_move_to_center = false;
auto parent_size = m_parent.get_canvas_size();
ImVec2 pos(parent_size.get_width() / 2 - m_gui_cfg->window_offset_x,
parent_size.get_height() / 2 - m_gui_cfg->window_offset_y);
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
}else if (change_window_position) {
ImVec2 pos = ImGui::GetMousePos();
pos.x -= m_gui_cfg->window_offset_x;
pos.y -= m_gui_cfg->window_offset_y;
@ -202,7 +267,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
ImGui::NewLine();
ImGui::SameLine(m_gui_cfg->bottom_left_width);
ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_count);
ImGui::Text(_u8L("%d triangles").c_str(), m_configuration.wanted_count);
m_imgui->disabled_end(); // use_count
if (ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe)) {
@ -212,7 +277,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
bool is_canceling = m_state == State::canceling;
m_imgui->disabled_begin(is_canceling);
if (m_imgui->button(_L("Cancel"))) {
if (m_imgui->button(_u8L("Cancel"))) {
if (m_state == State::settings) {
if (m_original_its.has_value()) {
set_its(*m_original_its);
@ -231,7 +296,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
bool is_processing = m_state != State::settings;
m_imgui->disabled_begin(is_processing);
if (m_imgui->button(_L("Apply"))) {
if (m_imgui->button(_u8L("Apply"))) {
if (!m_is_valid_result) {
m_state = State::close_on_end;
process();
@ -294,6 +359,7 @@ void GLGizmoSimplify::live_preview() {
// wait until cancel
if (m_worker.joinable()) {
m_state = State::canceling;
m_dealy_process_cv.notify_one();
m_worker.join();
}
}
@ -304,20 +370,52 @@ void GLGizmoSimplify::live_preview() {
void GLGizmoSimplify::process()
{
class SimplifyCanceledException : public std::exception {
public:
const char* what() const throw() { return L("Model simplification has been canceled"); }
};
if (m_volume == nullptr) return;
if (m_volume->mesh().its.indices.empty()) return;
size_t count_triangles = m_volume->mesh().its.indices.size();
// Is neccessary simplification
if ((m_configuration.use_count && m_configuration.wanted_count >= count_triangles) ||
(!m_configuration.use_count && m_configuration.max_error <= 0.f)) {
// Exist different original volume?
if (m_original_its.has_value() &&
m_original_its->indices.size() != count_triangles) {
indexed_triangle_set its = *m_original_its; // copy
set_its(its);
}
m_is_valid_result = true;
if (!m_original_its.has_value())
// re-render bargraph
set_dirty();
m_parent.schedule_extra_frame(0);
return;
}
// when not store original volume store it for cancelation
if (!m_original_its.has_value()) {
m_original_its = m_volume->mesh().its; // copy
auto plater = wxGetApp().plater();
plater->take_snapshot(_L("Simplify ") + m_volume->name);
plater->clear_before_change_mesh(m_obj_index);
// store previous state
auto plater = wxGetApp().plater();
plater->take_snapshot(_u8L("Simplify ") + m_volume->name);
plater->clear_before_change_mesh(m_obj_index);
}
m_progress = 0;
if (m_worker.joinable()) m_worker.join();
m_worker = std::thread([this]() {
m_worker = std::thread([this]() {
{// delay before process
std::unique_lock<std::mutex> lk(m_state_mutex);
auto is_modify = [this]() { return m_state == State::canceling; };
if (m_dealy_process_cv.wait_for(lk, m_gui_cfg->prcess_delay, is_modify)) {
// exist modification
m_state = State::settings;
request_rerender();
return;
}
}
// store original triangles
uint32_t triangle_count = (m_configuration.use_count) ? m_configuration.wanted_count : 0;
float max_error = (!m_configuration.use_count) ? m_configuration.max_error : std::numeric_limits<float>::max();
@ -357,6 +455,7 @@ void GLGizmoSimplify::process()
}
void GLGizmoSimplify::set_its(indexed_triangle_set &its) {
if (m_volume == nullptr) return; // could appear after process
m_volume->set_mesh(its);
m_volume->calculate_convex_hull();
m_volume->set_new_unique_id();
@ -440,6 +539,10 @@ void GLGizmoSimplify::request_rerender() {
});
}
void GLGizmoSimplify::set_center_position() {
m_move_to_center = true;
}
bool GLGizmoSimplify::exist_volume(ModelVolume *volume) {
auto objs = wxGetApp().plater()->model().objects;
for (const auto &obj : objs) {

View File

@ -7,17 +7,22 @@
#include "GLGizmoPainterBase.hpp" // for render wireframe
#include "admesh/stl.h" // indexed_triangle_set
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <optional>
#include <atomic>
#include <GL/glew.h> // GLUint
namespace Slic3r {
// for simplify suggestion
class ModelObjectPtrs; // std::vector<ModelObject*>
namespace Slic3r {
class ModelVolume;
namespace GUI {
class NotificationManager; // for simplify suggestion
class GLGizmoSimplify: public GLGizmoBase, public GLGizmoTransparentRender // GLGizmoBase
{
@ -25,6 +30,10 @@ public:
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoSimplify();
bool on_esc_key_down();
static void add_simplify_suggestion_notification(
const std::vector<size_t> &object_ids,
const ModelObjectPtrs & objects,
NotificationManager & manager);
protected:
virtual std::string on_get_name() const override;
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
@ -47,6 +56,8 @@ private:
void set_its(indexed_triangle_set &its);
void create_gui_cfg();
void request_rerender();
void set_center_position();
// move to global functions
static ModelVolume *get_volume(const Selection &selection, Model &model);
static const ModelVolume *get_volume(const GLVolume::CompositeID &cid, const Model &model);
@ -57,15 +68,21 @@ private:
std::atomic_bool m_is_valid_result; // differ what to do in apply
std::atomic_bool m_exist_preview; // set when process end
bool m_move_to_center; // opening gizmo
volatile int m_progress; // percent of done work
ModelVolume *m_volume; // keep pointer to actual working volume
size_t m_obj_index;
std::optional<indexed_triangle_set> m_original_its;
bool m_show_wireframe;
bool m_show_wireframe;
volatile bool m_need_reload; // after simplify, glReload must be on main thread
std::thread m_worker;
// wait before process
std::mutex m_state_mutex;
std::condition_variable m_dealy_process_cv;
enum class State {
settings,
@ -87,8 +104,13 @@ private:
void fix_count_by_ratio(size_t triangle_count)
{
wanted_count = static_cast<uint32_t>(
std::round(triangle_count * (100.f-decimate_ratio) / 100.f));
if (decimate_ratio <= 0.f)
wanted_count = static_cast<uint32_t>(triangle_count);
else if (decimate_ratio >= 100.f)
wanted_count = 0;
else
wanted_count = static_cast<uint32_t>(std::round(
triangle_count * (100.f - decimate_ratio) / 100.f));
}
} m_configuration;
@ -106,6 +128,10 @@ private:
// trunc model name when longer
size_t max_char_in_name = 30;
// to prevent freezing when move in gui
// delay before process in [ms]
std::chrono::duration<long int, std::milli> prcess_delay = std::chrono::milliseconds(250);
};
std::optional<GuiCfg> m_gui_cfg;
@ -122,6 +148,16 @@ private:
void free_gpu();
GLuint m_wireframe_VBO_id, m_wireframe_IBO_id;
size_t m_wireframe_IBO_size;
// cancel exception
class SimplifyCanceledException: public std::exception
{
public:
const char *what() const throw()
{
return L("Model simplification has been canceled");
}
};
};
} // namespace GUI

View File

@ -673,7 +673,7 @@ RENDER_AGAIN:
// - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene
// - take correct undo/redo snapshot after the user is done with moving the slider
float initial_value = m_new_point_head_diameter;
m_imgui->slider_float("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f");
m_imgui->slider_float("##head_diameter", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f");
if (ImGui::IsItemClicked()) {
if (m_old_point_head_diameter == 0.f)
m_old_point_head_diameter = initial_value;
@ -733,7 +733,7 @@ RENDER_AGAIN:
float density = static_cast<const ConfigOptionInt*>(opts[0])->value;
float minimal_point_distance = static_cast<const ConfigOptionFloat*>(opts[1])->value;
m_imgui->slider_float("", &minimal_point_distance, 0.f, 20.f, "%.f mm");
m_imgui->slider_float("##minimal_point_distance", &minimal_point_distance, 0.f, 20.f, "%.f mm");
bool slider_clicked = ImGui::IsItemClicked(); // someone clicked the slider
bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider
bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider
@ -742,7 +742,7 @@ RENDER_AGAIN:
m_imgui->text(m_desc.at("points_density"));
ImGui::SameLine(settings_sliders_left);
m_imgui->slider_float(" ", &density, 0.f, 200.f, "%.f %%");
m_imgui->slider_float("##points_density", &density, 0.f, 200.f, "%.f %%");
slider_clicked |= ImGui::IsItemClicked();
slider_edited |= ImGui::IsItemEdited();
slider_released |= ImGui::IsItemDeactivatedAfterEdit();
@ -802,7 +802,7 @@ RENDER_AGAIN:
ImGui::SameLine(clipping_slider_left);
ImGui::PushItemWidth(window_width - clipping_slider_left);
float clp_dist = m_c->object_clipper()->get_position();
if (m_imgui->slider_float(" ", &clp_dist, 0.f, 1.f, "%.2f"))
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true);

View File

@ -715,12 +715,22 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
}
else if (evt.LeftUp() && m_current == Flatten && m_gizmos[m_current]->get_hover_id() != -1) {
// to avoid to loose the selection when user clicks an the white faces of a different object while the Flatten gizmo is active
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
selection.stop_dragging();
wxGetApp().obj_manipul()->set_dirty();
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
processed = true;
}
else if (evt.RightUp() && (m_current == FdmSupports || m_current == Seam || m_current == MmuSegmentation) && !m_parent.is_mouse_dragging()) {
gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), control_down);
processed = true;
}
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
else if (evt.LeftUp()) {
selection.stop_dragging();
wxGetApp().obj_manipul()->set_dirty();
}
#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
}
else {
// mouse inside toolbar

View File

@ -280,7 +280,7 @@ void ImGuiWrapper::render()
m_new_frame_open = false;
}
ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width)
ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width) const
{
auto text_utf8 = into_u8(text);
ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, false, wrap_width);
@ -293,6 +293,22 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width)
return size;
}
ImVec2 ImGuiWrapper::calc_button_size(const wxString &text, const ImVec2 &button_size) const
{
const ImVec2 text_size = this->calc_text_size(text);
const ImGuiContext &g = *GImGui;
const ImGuiStyle &style = g.Style;
return ImGui::CalcItemSize(button_size, text_size.x + style.FramePadding.x * 2.0f, text_size.y + style.FramePadding.y * 2.0f);
}
ImVec2 ImGuiWrapper::get_item_spacing() const
{
const ImGuiContext &g = *GImGui;
const ImGuiStyle &style = g.Style;
return style.ItemSpacing;
}
float ImGuiWrapper::get_slider_float_height() const
{
const ImGuiContext& g = *GImGui;

View File

@ -56,9 +56,11 @@ public:
float scaled(float x) const { return x * m_font_size; }
ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); }
ImVec2 calc_text_size(const wxString &text, float wrap_width = -1.0f);
ImVec2 calc_text_size(const wxString &text, float wrap_width = -1.0f) const;
ImVec2 calc_button_size(const wxString &text, const ImVec2 &button_size = ImVec2(0, 0)) const;
float get_slider_float_height() const;
ImVec2 get_item_spacing() const;
float get_slider_float_height() const;
void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
void set_next_window_bg_alpha(float alpha);

View File

@ -193,7 +193,7 @@ void KBShortcutsDialog::fill_shortcuts()
m_full_shortcuts.push_back({ { _L("Gizmos"), _L("The following shortcuts are applicable when the specified gizmo is active") }, gizmos_shortcuts });
Shortcuts object_list_shortcuts = {
{ "P", L("Set selected items as Ptrintable/Unprintable") },
{ "P", L("Set selected items as Printable/Unprintable") },
{ "0", L("Set default extruder for the selected items") },
{ "1-9", L("Set extruder number for the selected items") },
};

View File

@ -53,6 +53,7 @@ public:
m_data[2] = norm_dir.z();
}
void set_offset(double offset) { m_data[3] = offset; }
double get_offset() const { return m_data[3]; }
Vec3d get_normal() const { return Vec3d(m_data[0], m_data[1], m_data[2]); }
bool is_active() const { return m_data[3] != DBL_MAX; }
static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); }

View File

@ -218,47 +218,12 @@ InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString&
: MsgDialog(parent, wxString::Format(_L("%s information"), SLIC3R_APP_NAME), title)
, msg(msg)
{
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
// Text shown as HTML, so that mouse selection and Ctrl-V to copy will work.
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
{
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
wxFont monospace = wxGetApp().code_font();
wxColour text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
const int font_size = font.GetPointSize() - 1;
int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size };
html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size);
html->SetBorders(2);
// calculate html page size from text
int lines = msg.Freq('\n');
if (msg.Contains("<tr>")) {
int pos = 0;
while (pos < (int)msg.Len() && pos != wxNOT_FOUND) {
pos = msg.find("<tr>", pos + 1);
lines+=2;
}
}
int page_height = std::min((font.GetPixelSize().y + 1) * lines, 68 * wxGetApp().em_unit());
wxSize page_size(68 * wxGetApp().em_unit(), page_height);
html->SetMinSize(page_size);
std::string msg_escaped = xml_escape(msg.ToUTF8().data(), true);
boost::replace_all(msg_escaped, "\r\n", "<br>");
boost::replace_all(msg_escaped, "\n", "<br>");
html->SetPage("<html><body bgcolor=\"" + bgr_clr_str + "\"><font color=\"" + text_clr_str + "\">" + wxString::FromUTF8(msg_escaped.data()) + "</font></body></html>");
content_sizer->Add(html, 1, wxEXPAND);
}
add_msg_content(this, content_sizer, msg);
// Set info bitmap
logo->SetBitmap(create_scaled_bitmap("info", this, 84));
wxGetApp().UpdateDlgDarkUI(this);
Fit();
}

View File

@ -1509,7 +1509,7 @@ void NotificationManager::push_notification(NotificationType type,
std::function<bool(wxEvtHandler*)> callback,
int timestamp)
{
int duration = get_standart_duration(level);
int duration = get_standard_duration(level);
push_notification_data({ type, level, duration, text, hypertext, callback }, timestamp);
}

View File

@ -713,7 +713,7 @@ private:
void sort_notifications();
// If there is some error notification active, then the "Export G-code" notification after the slicing is finished is suppressed.
bool has_slicing_error_notification();
size_t get_standart_duration(NotificationLevel level)
size_t get_standard_duration(NotificationLevel level)
{
switch (level) {

View File

@ -157,13 +157,16 @@ bool OpenGLManager::GLInfo::is_glsl_version_greater_or_equal_to(unsigned int maj
return version_greater_or_equal_to(m_glsl_version, major, minor);
}
std::string OpenGLManager::GLInfo::to_string(bool format_as_html, bool extensions) const
// If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
// Otherwise HTML formatted for the system info dialog.
std::string OpenGLManager::GLInfo::to_string(bool for_github) const
{
if (!m_detected)
detect();
std::stringstream out;
const bool format_as_html = ! for_github;
std::string h2_start = format_as_html ? "<b>" : "";
std::string h2_end = format_as_html ? "</b>" : "";
std::string b_start = format_as_html ? "<b>" : "";
@ -176,18 +179,24 @@ std::string OpenGLManager::GLInfo::to_string(bool format_as_html, bool extension
out << b_start << "Renderer: " << b_end << m_renderer << line_end;
out << b_start << "GLSL version: " << b_end << m_glsl_version << line_end;
if (extensions) {
{
std::vector<std::string> extensions_list;
std::string extensions_str = gl_get_string_safe(GL_EXTENSIONS, "");
boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off);
boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_on);
if (!extensions_list.empty()) {
out << h2_start << "Installed extensions:" << h2_end << line_end;
if (for_github)
out << "<details>\n<summary>Installed extensions:</summary>\n";
else
out << h2_start << "Installed extensions:" << h2_end << line_end;
std::sort(extensions_list.begin(), extensions_list.end());
for (const std::string& ext : extensions_list) {
out << ext << line_end;
}
for (const std::string& ext : extensions_list)
if (! ext.empty())
out << ext << line_end;
if (for_github)
out << "</details>\n";
}
}

View File

@ -46,7 +46,9 @@ public:
bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const;
bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const;
std::string to_string(bool format_as_html, bool extensions) const;
// If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
// Otherwise HTML formatted for the system info dialog.
std::string to_string(bool for_github) const;
private:
void detect() const;

View File

@ -201,7 +201,7 @@ void OptionsGroup::activate_line(Line& line)
if (line.is_separator())
return;
m_use_custom_ctrl_as_parent = false;
m_use_custom_ctrl_as_parent = false;
if (line.full_width && (
line.widget != nullptr ||

View File

@ -89,6 +89,7 @@
#include "PresetComboBoxes.hpp"
#include "MsgDialog.hpp"
#include "ProjectDirtyStateManager.hpp"
#include "Gizmos/GLGizmoSimplify.hpp" // create suggestion notification
#ifdef __APPLE__
#include "Gizmos/GLGizmosManager.hpp"
@ -204,7 +205,7 @@ void ObjectInfo::msw_rescale()
void ObjectInfo::update_warning_icon(const std::string& warning_icon_name)
{
if (showing_manifold_warning_icon = !warning_icon_name.empty()) {
if ((showing_manifold_warning_icon = !warning_icon_name.empty())) {
m_warning_icon_name = warning_icon_name;
manifold_warning_icon->SetBitmap(create_scaled_bitmap(m_warning_icon_name));
}
@ -1786,7 +1787,6 @@ struct Plater::priv
#endif // ENABLE_RELOAD_FROM_DISK_REPLACE_FILE
void replace_with_stl();
void reload_all_from_disk();
void create_simplify_notification(const std::vector<size_t>& obj_ids);
void set_current_panel(wxPanel* panel);
void on_select_preset(wxCommandEvent&);
@ -2571,8 +2571,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// this is required because the selected object changed and the flatten on face an sla support gizmos need to be updated accordingly
view3D->get_canvas3d()->update_gizmos_on_off_state();
}
create_simplify_notification(obj_idxs);
GLGizmoSimplify::add_simplify_suggestion_notification(
obj_idxs, model.objects, *notification_manager);
return obj_idxs;
}
@ -3017,9 +3018,9 @@ void Plater::priv::update_print_volume_state()
{
#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS
const ConfigOptionPoints* opt = dynamic_cast<const ConfigOptionPoints*>(this->config->option("bed_shape"));
const Polygon bed_poly = offset(Polygon::new_scale(opt->values), static_cast<float>(scale_(BedEpsilon))).front();
const Polygon bed_poly_convex = offset(Geometry::convex_hull(Polygon::new_scale(opt->values).points), static_cast<float>(scale_(BedEpsilon))).front();
const float bed_height = this->config->opt_float("max_print_height");
this->q->model().update_print_volume_state(bed_poly, bed_height);
this->q->model().update_print_volume_state(bed_poly_convex, bed_height);
#else
BoundingBox bed_box_2D = get_extents(Polygon::new_scale(this->config->opt<ConfigOptionPoints>("bed_shape")->values));
BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(this->config->opt_float("max_print_height"))));
@ -3762,53 +3763,6 @@ void Plater::priv::reload_all_from_disk()
}
}
void Plater::priv::create_simplify_notification(const std::vector<size_t>& obj_ids) {
const uint32_t triangles_to_suggest_simplify = 1000000;
std::vector<size_t> big_ids;
big_ids.reserve(obj_ids.size());
std::copy_if(obj_ids.begin(), obj_ids.end(), std::back_inserter(big_ids),
[this, triangles_to_suggest_simplify](size_t object_id) {
if (object_id >= model.objects.size()) return false; // out of object index
ModelVolumePtrs& volumes = model.objects[object_id]->volumes;
if (volumes.size() != 1) return false; // not only one volume
size_t triangle_count = volumes.front()->mesh().its.indices.size();
if (triangle_count < triangles_to_suggest_simplify) return false; // small volume
return true;
});
if (big_ids.empty()) return;
for (size_t object_id : big_ids) {
std::string t = _u8L(
"Processing model '@object_name' with more than 1M triangles "
"could be slow. It is highly recommend to reduce "
"amount of triangles.");
t.replace(t.find("@object_name"), sizeof("@object_name") - 1,
model.objects[object_id]->name);
//std::stringstream text;
//text << t << "\n";
std::string hypertext = _u8L("Simplify model");
std::function<bool(wxEvtHandler *)> open_simplify = [object_id](wxEvtHandler *) {
auto plater = wxGetApp().plater();
if (object_id >= plater->model().objects.size()) return true;
Selection &selection = plater->canvas3D()->get_selection();
selection.clear();
selection.add_object((unsigned int) object_id);
auto &manager = plater->canvas3D()->get_gizmos_manager();
manager.open_gizmo(GLGizmosManager::EType::Simplify);
return true;
};
notification_manager->push_simplify_suggestion_notification(t,
model.objects[object_id]->id(),
hypertext,
open_simplify);
}
}
void Plater::priv::set_current_panel(wxPanel* panel)
{
if (std::find(panels.begin(), panels.end(), panel) == panels.end())
@ -4231,8 +4185,11 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
wxMenu* menu = nullptr;
if (obj_idx == -1) { // no one or several object are selected
if (evt.data.second) // right button was clicked on empty space
if (evt.data.second) { // right button was clicked on empty space
if (!get_selection().is_empty()) // several objects are selected in 3DScene
return;
menu = menus.default_menu();
}
else
menu = menus.multi_selection_menu();
}

View File

@ -34,11 +34,17 @@ std::string PresetHints::cooling_description(const Preset &preset)
"so that no less than %3%s are spent on that layer "
"(however, speed will never be reduced below %4%mm/s)."),
slowdown_below_layer_time, max_fan_speed, slowdown_below_layer_time, min_print_speed);
if (fan_below_layer_time > slowdown_below_layer_time)
out += "\n" +
GUI::format(_L("If estimated layer time is greater, but still below ~%1%s, "
if (fan_below_layer_time > slowdown_below_layer_time) {
out += "\n";
if (min_fan_speed != max_fan_speed)
out += GUI::format(_L("If estimated layer time is greater, but still below ~%1%s, "
"fan will run at a proportionally decreasing speed between %2%%% and %3%%%."),
fan_below_layer_time, max_fan_speed, min_fan_speed);
else
out += GUI::format(_L("If estimated layer time is greater, but still below ~%1%s, "
"fan will run at %2%%%"),
fan_below_layer_time, min_fan_speed);
}
out += "\n";
}
if (preset.config.opt_bool("fan_always_on", 0)) {

View File

@ -67,8 +67,9 @@ public:
SendSystemInfoDialog(wxWindow* parent);
private:
bool send_info();
wxString send_info();
const std::string m_system_info_json;
wxButton* m_btn_show_data;
wxButton* m_btn_send;
wxButton* m_btn_dont_send;
wxButton* m_btn_ask_later;
@ -89,7 +90,7 @@ public:
auto* btn = new wxButton(this, wxID_CANCEL, _L("Cancel"));
auto* vsizer = new wxBoxSizer(wxVERTICAL);
auto *top_sizer = new wxBoxSizer(wxVERTICAL);
vsizer->Add(text, 1, wxEXPAND|wxALIGN_CENTER_HORIZONTAL);
vsizer->Add(text, 1, wxEXPAND);
vsizer->AddSpacer(5);
vsizer->Add(btn, 0, wxALIGN_CENTER_HORIZONTAL);
top_sizer->Add(vsizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT | wxBOTTOM, 10);
@ -548,7 +549,6 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
"installation are sent. PrusaSlicer is open source, if you want to "
"inspect the code actually performing the communication, see %1%."),
std::string("<i>") + filename + "</i>");
wxString label3 = _L("Show verbatim data that will be sent");
auto* html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(70*em, 34*em), wxHW_SCROLLBAR_NEVER);
wxString html = GUI::format_wxstr(
@ -560,17 +560,14 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
+ text1 + "<br /><br />"
"</td></tr></table>"
+ "<b>" + label2 + "</b><br />"
+ text2 + "<br /><br />"
+ "<b><a href=\"show\">" + label3 + "</a></b><br />"
+ text2
+ "</font></body></html>", bgr_clr_str, text_clr_str);
html_window->SetPage(html);
html_window->Bind(wxEVT_HTML_LINK_CLICKED, [this](wxHtmlLinkEvent&) {
ShowJsonDialog dlg(this, m_system_info_json, GetSize().Scale(0.9, 0.7));
dlg.ShowModal();
});
vsizer->Add(html_window, 1, wxEXPAND);
m_btn_show_data = new wxButton(this, wxID_ANY, _L("Show verbatim data that will be sent"));
m_btn_ask_later = new wxButton(this, wxID_ANY, _L("Ask me next time"));
m_btn_dont_send = new wxButton(this, wxID_ANY, _L("Do not send anything"));
m_btn_send = new wxButton(this, wxID_ANY, _L("Send system info"));
@ -582,6 +579,7 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
hsizer->AddSpacer(em);
hsizer->Add(m_btn_send);
vsizer->Add(m_btn_show_data, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 20);
vsizer->Add(hsizer, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10);
topSizer->Add(vsizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 10);
@ -599,9 +597,15 @@ SendSystemInfoDialog::SendSystemInfoDialog(wxWindow* parent)
CenterOnParent();
m_btn_show_data->Bind(wxEVT_BUTTON, [this](wxEvent&) {
ShowJsonDialog dlg(this, m_system_info_json, GetSize().Scale(0.9, 0.7));
dlg.ShowModal();
});
m_btn_send->Bind(wxEVT_BUTTON, [this](const wxEvent&)
{
if (send_info()) {
if (wxString out = send_info(); !out.IsEmpty()) {
InfoDialog(nullptr, wxEmptyString, out).ShowModal();
save_version();
EndModal(0);
}
@ -630,7 +634,7 @@ void SendSystemInfoDialog::on_dpi_changed(const wxRect&)
// This actually sends the info.
bool SendSystemInfoDialog::send_info()
wxString SendSystemInfoDialog::send_info()
{
std::atomic<int> job_done = false; // Flag to communicate between threads.
struct Result {
@ -674,11 +678,9 @@ bool SendSystemInfoDialog::send_info()
job_done = true; // In case the user closed the dialog, let the other thread know
sending_thread.join(); // and wait until it terminates.
if (result.value != Result::Cancelled) { // user knows he cancelled, no need to tell him.
InfoDialog info_dlg(wxGetApp().mainframe, wxEmptyString, result.str);
info_dlg.ShowModal();
}
return result.value == Result::Success;
if (result.value == Result::Cancelled)
return "";
return result.str;
}

View File

@ -158,7 +158,7 @@ SysInfoDialog::SysInfoDialog()
"</body>"
"</html>", bgr_clr_str, text_clr_str, text_clr_str,
blacklisted_libraries_message,
get_mem_info(true), wxGetApp().get_gl_info(true, true),
get_mem_info(true), wxGetApp().get_gl_info(false),
"<b>" + _L("Eigen vectorization supported:") + "</b> " + Eigen::SimdInstructionSetsInUse());
m_opengl_info_html->SetPage(text);
@ -215,7 +215,7 @@ void SysInfoDialog::on_dpi_changed(const wxRect &suggested_rect)
void SysInfoDialog::onCopyToClipboard(wxEvent &)
{
wxTheClipboard->Open();
const auto text = get_main_info(false) + "\n" + wxGetApp().get_gl_info(false, true);
const auto text = get_main_info(false) + "\n" + wxGetApp().get_gl_info(true);
wxTheClipboard->SetData(new wxTextDataObject(text));
wxTheClipboard->Close();
}

View File

@ -13,7 +13,7 @@ class AppConfig;
class PresetBundle;
class Semver;
const int SLIC3R_VERSION_BODY_MAX = 256;
static constexpr const int SLIC3R_VERSION_BODY_MAX = 256;
class PresetUpdater
{

View File

@ -220,7 +220,7 @@ SCENARIO("DynamicPrintConfig serialization", "[Config]") {
cereal::BinaryOutputArchive oarchive(ss);
oarchive(cfg);
serialized = ss.str();
} catch (std::runtime_error e) {
} catch (std::runtime_error &e) {
e.what();
}
@ -230,7 +230,7 @@ SCENARIO("DynamicPrintConfig serialization", "[Config]") {
std::stringstream ss(serialized);
cereal::BinaryInputArchive iarchive(ss);
iarchive(cfg2);
} catch (std::runtime_error e) {
} catch (std::runtime_error &e) {
e.what();
}
REQUIRE(cfg == cfg2);

View File

@ -19,6 +19,66 @@
using namespace Slic3r;
TEST_CASE("Line::parallel_to", "[Geometry]"){
Line l{ { 100000, 0 }, { 0, 0 } };
Line l2{ { 200000, 0 }, { 0, 0 } };
REQUIRE(l.parallel_to(l));
REQUIRE(l.parallel_to(l2));
Line l3(l2);
l3.rotate(0.9 * EPSILON, { 0, 0 });
REQUIRE(l.parallel_to(l3));
Line l4(l2);
l4.rotate(1.1 * EPSILON, { 0, 0 });
REQUIRE(! l.parallel_to(l4));
// The angle epsilon is so low that vectors shorter than 100um rotated by epsilon radians are not rotated at all.
Line l5{ { 20000, 0 }, { 0, 0 } };
l5.rotate(1.1 * EPSILON, { 0, 0 });
REQUIRE(l.parallel_to(l5));
l.rotate(1., { 0, 0 });
Point offset{ 342876, 97636249 };
l.translate(offset);
l3.rotate(1., { 0, 0 });
l3.translate(offset);
l4.rotate(1., { 0, 0 });
l4.translate(offset);
REQUIRE(l.parallel_to(l3));
REQUIRE(!l.parallel_to(l4));
}
TEST_CASE("Line::perpendicular_to", "[Geometry]") {
Line l{ { 100000, 0 }, { 0, 0 } };
Line l2{ { 0, 200000 }, { 0, 0 } };
REQUIRE(! l.perpendicular_to(l));
REQUIRE(l.perpendicular_to(l2));
Line l3(l2);
l3.rotate(0.9 * EPSILON, { 0, 0 });
REQUIRE(l.perpendicular_to(l3));
Line l4(l2);
l4.rotate(1.1 * EPSILON, { 0, 0 });
REQUIRE(! l.perpendicular_to(l4));
// The angle epsilon is so low that vectors shorter than 100um rotated by epsilon radians are not rotated at all.
Line l5{ { 0, 20000 }, { 0, 0 } };
l5.rotate(1.1 * EPSILON, { 0, 0 });
REQUIRE(l.perpendicular_to(l5));
l.rotate(1., { 0, 0 });
Point offset{ 342876, 97636249 };
l.translate(offset);
l3.rotate(1., { 0, 0 });
l3.translate(offset);
l4.rotate(1., { 0, 0 });
l4.translate(offset);
REQUIRE(l.perpendicular_to(l3));
REQUIRE(! l.perpendicular_to(l4));
}
TEST_CASE("Polygon::contains works properly", "[Geometry]"){
// this test was failing on Windows (GH #1950)
Slic3r::Polygon polygon(std::vector<Point>({
@ -468,7 +528,7 @@ TEST_CASE("Convex polygon intersection on two disjoint squares", "[Geometry][Rot
Polygon B = A;
B.translate(20 / SCALING_FACTOR, 0);
bool is_inters = Geometry::intersects(A, B);
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == false);
}
@ -480,7 +540,7 @@ TEST_CASE("Convex polygon intersection on two intersecting squares", "[Geometry]
Polygon B = A;
B.translate(5 / SCALING_FACTOR, 5 / SCALING_FACTOR);
bool is_inters = Geometry::intersects(A, B);
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == true);
}
@ -492,7 +552,7 @@ TEST_CASE("Convex polygon intersection on two squares touching one edge", "[Geom
Polygon B = A;
B.translate(10 / SCALING_FACTOR, 0);
bool is_inters = Geometry::intersects(A, B);
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == false);
}
@ -509,7 +569,7 @@ TEST_CASE("Convex polygon intersection on two squares touching one vertex", "[Ge
svg.draw(B, "green");
svg.Close();
bool is_inters = Geometry::intersects(A, B);
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == false);
}
@ -520,7 +580,7 @@ TEST_CASE("Convex polygon intersection on two overlapping squares", "[Geometry][
Polygon B = A;
bool is_inters = Geometry::intersects(A, B);
bool is_inters = Geometry::convex_polygons_intersect(A, B);
REQUIRE(is_inters == true);
}
@ -560,7 +620,7 @@ TEST_CASE("Convex polygon intersection on two overlapping squares", "[Geometry][
// bench.start();
// for (const auto &test : tests)
// results.emplace_back(Geometry::intersects(test.first, test.second));
// results.emplace_back(Geometry::convex_polygons_intersect(test.first, test.second));
// bench.stop();
// std::cout << "Test time: " << bench.getElapsedSec() << std::endl;
@ -611,7 +671,7 @@ TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcali
for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) {
Polygon P = PRINTER_PART_POLYGONS[i];
P = Geometry::convex_hull(P.points);
bool res = Geometry::intersects(P, P);
bool res = Geometry::convex_polygons_intersect(P, P);
if (!res) {
SVG svg{std::string("fail_self") + std::to_string(i) + ".svg"};
svg.draw(P, "green");
@ -644,7 +704,7 @@ TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcali
B.translate(bba.size() + bbb.size());
bool res = Geometry::intersects(A, B);
bool res = Geometry::convex_polygons_intersect(A, B);
bool ref = !intersection(A, B).empty();
if (res != ref) {
@ -669,7 +729,7 @@ TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcali
A.translate(-bba.center());
B.translate(-bbb.center());
bool res = Geometry::intersects(A, B);
bool res = Geometry::convex_polygons_intersect(A, B);
bool ref = !intersection(A, B).empty();
if (res != ref) {

View File

@ -3,7 +3,7 @@
set(SLIC3R_APP_NAME "PrusaSlicer")
set(SLIC3R_APP_KEY "PrusaSlicer")
set(SLIC3R_VERSION "2.4.0-alpha3")
set(SLIC3R_VERSION "2.4.0-beta1")
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")