diff --git a/build_win.bat b/build_win.bat index 600d6d6e6..c03ebf039 100644 --- a/build_win.bat +++ b/build_win.bat @@ -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 ( diff --git a/doc/How to build - Windows.md b/doc/How to build - Windows.md index 42d559c5a..d0da05d7b 100644 --- a/doc/How to build - Windows.md +++ b/doc/How to build - Windows.md @@ -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: diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index ee3a57fe8..cef8c654d 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -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. diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 7d4ab7ccb..e243963ce 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -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 diff --git a/resources/shaders/mm_gouraud.fs b/resources/shaders/mm_gouraud.fs index f7154b419..5932efe59 100644 --- a/resources/shaders/mm_gouraud.fs +++ b/resources/shaders/mm_gouraud.fs @@ -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); diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 0da5e7380..41d5231f1 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -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 &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")); diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 47ba7bbdc..00f6a999f 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -577,7 +577,7 @@ private: template - Shapes calcnfp(const Item &trsh, Level) + Shapes calcnfp(const Item &/*trsh*/, Level) { // Function for arbitrary level of nfp implementation // TODO: implement diff --git a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp index dc1bbd4f1..486996f0d 100644 --- a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp @@ -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) {} diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index a13d578a3..8f31b0695 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -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 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); } diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index d9320373f..7b2506a22 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -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 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::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().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::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]; diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 2f3508316..1c990f5ea 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -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 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. diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index b323145d3..3271e2c38 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -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); } } diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index bcc5c8b4a..235cdecb5 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -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; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ff0a7c027..8f8d8dbe0 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2479,57 +2479,49 @@ std::string GCode::change_layer(coordf_t print_z) +static std::unique_ptr calculate_layer_edge_grid(const Layer& layer) +{ + auto out = make_unique(); + + // 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 *lower_layer_edge_grid) { // get a copy; don't modify the orientation of the original loop object otherwise // next copies (if any) would not detect the correct orientation - if (m_layer->lower_layer != nullptr && lower_layer_edge_grid != nullptr) { - if (! *lower_layer_edge_grid) { - // Create the distance field for a layer below. - const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5); - *lower_layer_edge_grid = make_unique(); - (*lower_layer_edge_grid)->create(m_layer->lower_layer->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::vectorlower_layer && ! lower_layer_edge_grid) + lower_layer_edge_grid = calculate_layer_edge_grid(*m_layer->lower_layer); + + m_seam_placer.plan_perimeters(std::vector(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; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 29f7a58bd..cbffe652e 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -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& 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(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(1, m_layer_id); // calculates block cruise feedrate float min_feedrate_factor = 1.0f; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 659dd5654..022bbdfa9 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -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 diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 878a9ef58..6d082a431 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -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 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(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() - last_pos.cast()).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 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::max(); + std::vector 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() - lines[line_idx].b.cast()).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() + ((b->cast() - a->cast()) * ratio)).cast(); + } + } + } + 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 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() - (it - 1)->cast()).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() + (it->cast() - (it - 1)->cast()) * std::min(ratio, 1.)).cast(); + 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); } diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 0bae7af5a..57c3532c3 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -2,7 +2,9 @@ #define libslic3r_SeamPlacer_hpp_ #include +#include +#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 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; 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 m_plan; + size_t m_plan_idx; std::vector> m_enforcers; std::vector> m_blockers; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 4cf60dbe2..dfc463dde 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -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; diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 49886d4b8..d98cab9c7 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -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 diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 1a96b8b1f..773df3442 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -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(); + const Vec2d v2 = (line.b - line.a).cast(); + 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(); + const Vec2d v2 = (line.b - line.a).cast(); + 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 diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 4a33c7c9c..a4dcd827c 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -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; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index b36618dd1..2dcf34211 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -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; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 0a6a73cbc..e47ebda39 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -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); diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index b17ee000a..86b3488b0 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1324,6 +1324,18 @@ static inline std::vector> mmu_segmentation_top_and_bott } } + auto filter_out_small_polygons = [&num_extruders, &num_layers](std::vector> &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" }; diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index 03f7ff59c..aa8295098 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -47,9 +47,9 @@ void MultiPoint::rotate(double angle, const Point ¢er) 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; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index b34a6cb58..232e724e7 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -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::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) { diff --git a/src/libslic3r/SLA/ConcaveHull.cpp b/src/libslic3r/SLA/ConcaveHull.cpp index 172408989..08a2ff676 100644 --- a/src/libslic3r/SLA/ConcaveHull.cpp +++ b/src/libslic3r/SLA/ConcaveHull.cpp @@ -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 -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(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(paths), jointype, etClosedPolygon); - - Paths result; - offs.Execute(result, static_cast(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(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 diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 1bc548914..43ac958b5 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -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 + "}")); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 09cb89372..fb2621225 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -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 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 dst_vertices; - std::vector dst_facets; - // Map of QHull's vertex ID to our own vertex ID (pointing to dst_vertices). - std::vector 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 &pts) +{ + std::vector dst_vertices; + std::vector dst_facets; + + if (! pts.empty()) { + // The qhull call: + orgQhull::Qhull qhull; + qhull.disableOutputStream(); // we want qhull to be quiet + #if ! REALfloat + std::vector 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 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) diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index ec6401982..951e351fe 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -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 &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)}; } diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 0f3d0f5b6..83a2be322 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -1919,6 +1919,7 @@ void slice_mesh_slabs( #endif // EXPENSIVE_DEBUG_CHECKS std::vector vertices_transformed = transform_mesh_vertices_for_slicing(mesh, trafo); + const bool mirrored = trafo.matrix().determinant() < 0; std::vector 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(); const Point b = to_2d(fb).cast(); const Point c = to_2d(fc).cast(); - const int64_t d = cross2((b - a).cast(), (c - b).cast()); + const int64_t d = cross2((b - a).cast(), (c - b).cast()) * (mirrored ? -1 : 1); FaceOrientation fo = FaceOrientation::Vertical; if (d > 0) fo = FaceOrientation::Up; diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index f65292f03..169978688 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -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 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()} + trafo{trafo_.cast()}, + 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_, diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index b3c468a6e..b9f136c2e 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -4,6 +4,7 @@ // #define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +#include #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 &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> precompute_all_neighbors() const; void precompute_all_neighbors_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &neighbors_out, std::vector &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> &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; diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 18c017da1..8e6867311 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -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 vertex_distances; double avg_dist = 0.0; for (const Vec2d& pt : shape) { diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 26a64fdb7..c8e72ba39 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -40,12 +40,6 @@ #include -#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS -#include -#include -#include -#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 &src = this->indexed_vertex_array.vertices_and_normals_interleaved; + std::vector 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& vertices = mesh.its.vertices; - - // The qhull call: - orgQhull::Qhull qhull; - qhull.disableOutputStream(); // we want qhull to be quiet - std::vector 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 dst_vertices; - std::vector dst_facets; - // Map of QHull's vertex ID to our own vertex ID (pointing to dst_vertices). - std::vector 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(bed_box_2D.max.x()), unscale(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(), 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(), 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, ¢er, &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, ¢er, &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; diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index d0b29165c..fc47484b5 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -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()) { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2aea86f3f..f39649cd9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2581,12 +2581,15 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) if (camera_space) { Eigen::Matrix 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(); }; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 7d6940bd0..787382c23 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -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
. +// 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. diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index d350da969..ad42803f6 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -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
. + // 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(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 83f9d1ec1..43ebd23f2 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -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& added_volumes, ModelVolumeType type, bool from_galery/* = false* /) +void ObjectList::load_part(ModelObject& model_object, std::vector& added_volumes, ModelVolumeType type, bool from_galery = false) { if (type != ModelVolumeType::MODEL_PART) return; diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 09492db09..c279fad90 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -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")); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index f7feed44a..614e83811 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -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)); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index ee15dab88..4e86b562b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -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()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 8cfb3fbca..2825913d3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -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(); 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 TriangleSelectorGUI::get_seed_fill_color(const std::array &base_color) { return {base_color[0] * 0.75f, base_color[1] * 0.75f, base_color[2] * 0.75f, 1.f}; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 3093b0bec..eb09715da 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -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, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index c43f215d9..c72f744e1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -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 &object_ids, + const ModelObjectPtrs & objects, + NotificationManager & manager) +{ + std::vector 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 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(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 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::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) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index b609c5cdd..b978e9356 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -7,17 +7,22 @@ #include "GLGizmoPainterBase.hpp" // for render wireframe #include "admesh/stl.h" // indexed_triangle_set #include +#include +#include +#include #include #include #include // GLUint -namespace Slic3r { +// for simplify suggestion +class ModelObjectPtrs; // std::vector +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 &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 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( - std::round(triangle_count * (100.f-decimate_ratio) / 100.f)); + if (decimate_ratio <= 0.f) + wanted_count = static_cast(triangle_count); + else if (decimate_ratio >= 100.f) + wanted_count = 0; + else + wanted_count = static_cast(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 prcess_delay = std::chrono::milliseconds(250); }; std::optional 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 diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index b7a6d89fa..ccc67b630 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -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(opts[0])->value; float minimal_point_distance = static_cast(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); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 0c8f161a9..316f80036 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -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 diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 4cf87cd71..57e0ef5f5 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -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; diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 7f59b4f5b..7bc9267c1 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -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); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 992b0be13..ba3f6675f 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -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") }, }; diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 767abd7fd..ccdb83042 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -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); } diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 41846500d..9f3f34f84 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -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("")) { - int pos = 0; - while (pos < (int)msg.Len() && pos != wxNOT_FOUND) { - pos = msg.find("", 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", "
"); - boost::replace_all(msg_escaped, "\n", "
"); - html->SetPage("" + wxString::FromUTF8(msg_escaped.data()) + ""); - 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(); } diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 40f5c3116..a984b6ba7 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1509,7 +1509,7 @@ void NotificationManager::push_notification(NotificationType type, std::function 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); } diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 2031586b8..9adcc240d 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -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) { diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 3baa8a4da..6616cc20d 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -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
. +// 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 ? "" : ""; std::string h2_end = format_as_html ? "" : ""; std::string b_start = format_as_html ? "" : ""; @@ -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 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 << "
\nInstalled extensions:\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 << "
\n"; } } diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index 716256e40..72a4e6bc0 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -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
. + // Otherwise HTML formatted for the system info dialog. + std::string to_string(bool for_github) const; private: void detect() const; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index fd9f4b5e8..2e0d8384d 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -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 || diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6401ba4d3..d9daacdb5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -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& obj_ids); void set_current_panel(wxPanel* panel); void on_select_preset(wxCommandEvent&); @@ -2571,8 +2571,9 @@ std::vector Plater::priv::load_files(const std::vector& 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(this->config->option("bed_shape")); - const Polygon bed_poly = offset(Polygon::new_scale(opt->values), static_cast(scale_(BedEpsilon))).front(); + const Polygon bed_poly_convex = offset(Geometry::convex_hull(Polygon::new_scale(opt->values).points), static_cast(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("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& obj_ids) { - const uint32_t triangles_to_suggest_simplify = 1000000; - - std::vector 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 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(); } diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 5c5ed2612..ce709d9eb 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -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)) { diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index 106617e76..f92060bfc 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -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("") + filename + ""); - 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 + "

" "" + "" + label2 + "
" - + text2 + "

" - + "" + label3 + "
" + + text2 + "", 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 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; } diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 5475a36ea..1a8542438 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -158,7 +158,7 @@ SysInfoDialog::SysInfoDialog() "" "", 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), "" + _L("Eigen vectorization supported:") + " " + 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(); } diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index d0d18a7d8..085aaa4a6 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -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 { diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index 213105084..8dfda789e 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -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); diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp index b183de1b4..8261fe249 100644 --- a/tests/libslic3r/test_geometry.cpp +++ b/tests/libslic3r/test_geometry.cpp @@ -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({ @@ -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) { diff --git a/version.inc b/version.inc index fdbc2523f..27900ee80 100644 --- a/version.inc +++ b/version.inc @@ -3,7 +3,7 @@ set(SLIC3R_APP_NAME "PrusaSlicer") set(SLIC3R_APP_KEY "PrusaSlicer") -set(SLIC3R_VERSION "2.4.0-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")